0.1. 部署Kubernetes 0.2. Kubeadm原理 0.3. kubeadm步骤 0.3.1. kubeadm init 0.3.1.1. 第一步:Preflight Checks 0.3.1.2. 第二步:生成证书 0.3.1.3. 第三步:生成conf文件 0.3.1.4. 第四步:生成yaml文件 0.3.2. kubeadm join 0.3.3. 配置kubeadm参数 0.4. Kubeadm源代码 0.5. 生产环境部署 0.6. 制作证书的方法 Linux容器相关的技术可以帮助我们快速定位问题,并解决问题。 要真正发挥容器技术的实力的关键在于如何使用这些技术“容器化”应用。 单单通过Docker把一个应用的镜像跑起来,并没有什么用。关键是处理好容器之间的编排关系。比如: 主从容器如何区分? 容器之间的自动发现和通信如何完成? 容器的持久化数据如何保持? 0.1. 部署Kubernetes 主流云厂商使用SaltStack、Ansible等运维工具自动化地执行安装脚本和配置文件。但是,这些工具的学习成本比kubernetes项目还高。 社区开发了一个独立部署的工具:kubeadm,执行以下两条命令就可以部署一个集群: # 创建一个 Master 节点 kubeadm init # 将一个 Node 节点加入到当前集群中 kubeadm join <Master节点的IP和端口> 0.2. Kubeadm原理 传统部署方式:在部署Kubernetes时,它的每一个组件都是一个需要被执行的、单独的二进制文件。使用SaltStack这样的运维工具或者社区维护的脚本,就需要把这些二进制文件传输到指定的节点上,然后编写控制脚本来启停这些组件。 容器化部署方式:给每个组件做一个容器镜像,然后在每台宿主机上运行docker run命令来启动这些组件容器。 容器化部署的一个问题:如何容器化kubelet? Kubelet是Kubernetes项目用来操作Docker等容器运行时的核心组件,除了和容器运行时打交道之外,kubelet在配置容器网络、管理容器数据卷都需要直接操作宿主机。 如果kubelet本身就运行在一个容器中,那么直接操作宿主机就会变得很麻烦。 对于配置网络:kubelet容器可以通过不开启Network Namespace(即Docker的host network模式)的方式,直接共享宿主机的网络栈。 对于操作文件系统:让kubelet隔着容器的Mount Namespace和文件系统,操作宿主机的文件系统,就有点难了。 举个例子:用户想要使用NFS做容器的持久化数据卷,那么kubelet就需要在容器进行绑定挂载前,在宿主机的指定目录上,先挂载NFS的远程目录。那么问题来了,由于现在kubelet是运行在容器里的,这就意味着它要做的这个mount -F nfs命令,被隔离在了一个单独的Mount Namespace中,即kubelet做的挂载操作,不能被“传播”到宿主机上。 因此,妥协的方案就是,kubelet直接运行在宿主机上,然后使用容器部署其他的kubernetes组件。 0.3. kubeadm步骤 使用kubeadm的第一步,在机器上手动安装 kubeadm、kubelet、kubectl 这三个二进制文件。kubeadm已经为各个发行版的Linux准备好了安装包,所以只需要执行如下命令: apt-get install kubeadm #Debain or Ubuntu yum install kubeadm #Redhat or Centos kubeadm init #部署Master 节点 0.3.1. kubeadm init 0.3.1.1. 第一步:Preflight Checks 确定服务器是否可以用来部署kubernetes。 主要包括: Linux内核的版本必须是否是3.10以上? Linux Cgroup模块是否可用? 服务器的hostname是否标准? 在kubernetes项目中,主机名以及一切存储在Etcd中的API对象,都必须使用标准的DNS命名(RFC1123)。 安装的kubeadm和kubelet的版本是否匹配? 服务器上是否已经安装了 Kubernetes的二进制文件? Kubernetes的工作端口10250/10251/10252端口是不是已经被占用? ip、mount等Linux指令是否存在? Docker是否已经安装? 。。。。。。 0.3.1.2. 第二步:生成证书 当通过了Preflight Checks后,kubeadm会生成Kubernetes对外提供服务所需的各种证书和对应的目录。 Kubernetes对外提供服务时,除非专门开启“不安全模式”,否则都需要通过HTTPS才能访问kube-apiserver,这就需要为Kubernetes集群配置好证书文件。 证书存放在Master节点的/etc/kubernetes/pki目录下,其中最主要的证书是ca.crt和对应的私钥ca.key。 用户使用kubectl获取容器日志等streaming操作时,需要通过kube-apiserver向kubelet发起请求,这个链接也必须是安全的。kubeadm 为上述操作生成的是apiserver-kubelet-client.crt文件,对应的私钥是apiserver-kubelet-client.key。 其他的如Aggregate APIServer 等特性,也需要生成专门的证书,同时也可以选择不让kubeadm生成证书,而是拷贝现成的证书到指定的目录中/etc/kubernetes/pki/ca.{crt,key}。那么,此时kubeadm会跳过生成证书的步骤。 0.3.1.3. 第三步:生成conf文件 证书生成后,kubeadm接下来会为其他组件生成访问kube-apiserver所需的配置文件。配置文件的路径是:/etc/kubernetes/xxx.conf ls /etc/kubernetes/ admin.conf controller-manager.conf kubelet.conf scheduler.conf 这些文件里记录的是,当前这个Master节点的 服务器地址、监听端口、证书目录 等信息。这样,对应的客户端(如scheduler、kubelet等),可以直接加载相应的文件,使用里面的信息与kube-apiserver建立安全连接。 0.3.1.4. 第四步:生成yaml文件 kubeadm为Master组件(kube-apiserver、kube-controller-manager、kube-scheduler)生成pod yaml文件,它们都以pod的方式部署起来。 在kubernetes中,有一种特殊的容器启动方法叫 “Static Pod” 。它允许你把要部署的pod的yaml文件放在一个指定的目录里。这样,当这台服务器上的kubelet启动时,它会自动检查这个目录,加载所有的pod yaml文件,然后在这台服务器上启动它们。 在kubeadm中,Master组件的yaml文件会被生成在/etc/kubernetes/manifests路径下。如果需要修改已有集群的kubernetes组件的配置,需要修改对应的yaml文件。 同样通过 Static pod 的方式启动Etcd,所以,Master组件的pod文如下: ls /etc/kubernetes/manifests/ etcd.yaml kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml 一旦这些文件出现在被kubelet监视的/etc/kubernetes/manifests目录下,kubelet就会自动创建这些yaml文件中定义的pod,(即Master组件的容器)。 Master组件的容器启动后,kubeadm会通过检查localhost:6443/healthz这个Master组件的健康检查URL,等待Master组件完全运行起来。 kubeadm为集群生成bootstrap token,持有这个token的任何一个kubelet和kubeadm节点,都可以通过kubeadm join加入到这个集群中。(token的值和使用方法会在kubeadm init结束后打印出来。) token生成后,kubeadm会将ca.crt等Master节点的重要信息,通过ConfigMap的方式保存在Etcd中,供后续部署Node节点使用。(这个ConfigMap的名字 cluster-info) kubernetes默认kube-proxy和DNS这两个插件是必须安装的,提供集群的服务发现和DNS功能,这两个插件也是两个容器镜像,创建两个pod即可。 0.3.2. kubeadm join 使用kubeadm init生成的bootstrap token 在安装了kubeadm和kubelet的服务器上执行kubeadm join。 bootstrap token的作用: 一台服务器想要成为kubernetes集群中的节点,就必须在集群的kube-apiserver上注册。 想要与apiserver通信,这台服务器必须获取相应的证书文件(CA文件)。 为了一键安装,就不能手动去拷贝证书文件。 kubeadm至少要发起一次“不安全模式”的访问到kube-apiserver,从而拿到保存在ConfigMap中的cluster-info(这里保存了APIServer的授权信息)。 bootstrap token扮演的就是这个过程中的安全验证的角色,有了cluster-info里的kube-apiserver的地址、端口、证书,kubelet就可以“安全模式”连接到apiserver上。 0.3.3. 配置kubeadm参数 可以通过 --config 参数指定启动时读取的配置文件: kubeadm init --config kubeadm.yaml 这样可以给kubeadm提供一个yaml文件,例如: apiVersion: kubeadm.k8s.io/v1alpha2 kind: MasterConfiguration kubernetesVersion: v1.11.0 api: advertiseAddress: 192.168.0.102 bindPort: 6443 ... etcd: local: dataDir: /var/lib/etcd image: "" imageRepository: k8s.gcr.io kubeProxy: config: bindAddress: 0.0.0.0 ... kubeletConfiguration: baseConfig: address: 0.0.0.0 ... networking: dnsDomain: cluster.local podSubnet: "" serviceSubnet: 10.96.0.0/12 nodeRegistration: criSocket: /var/run/dockershim.sock ... 通过指定这样一个配置文件,可以方便地在文件里填写各种自定义的部署参数。 比如,要自动化kube-apiserver的参数,添加如下信息: ... apiServerExtraArgs: advertise-address: 192.168.0.103 anonymous-auth: false enable-admission-plugins: AlwaysPullImages,DefaultStorageClass audit-log-path: /home/johndoe/audit.log 然后,kubeadm就会使用上面的信息替换/etc/kubernetes/manifests/kube-apiserver.yaml里的command字段里的参数。 更具体的: 修改kubelet的配置 修改kube-proxy 的配置 修改kubernetes使用的基础镜像的URL(默认的k8s.gcr.io/xxx镜像URL在国内不能访问) 指定自己的证书文件 指定特殊的容器运行时 0.4. Kubeadm源代码 源代码在kubernetes/cmd/kubeadm目录下,其中app/phases文件夹下的代码就是上述的步骤。 0.5. 生产环境部署 部署规模化的生产环境,推荐使用: kops saltstack ansible playbook kubespray K8S实验平台 谷歌镜像 0.6. 制作证书的方法 CFSSL OpenSSL easyrsa GnuGPG keybase
0.1. YAML示例 0.2. API对象 0.2.1. Metadata 0.2.2. Label Selector 0.2.3. Annotations 0.3. 运行API对象 0.3.1. 示例 0.4. 挂载volume 0.5. 进入容器 使用Kubernetes的必备技能:编写配置文件。这些配置文件可以是 YAML 或者 JSON 格式的,一般都是用YAML格式。 Kubernetes不推荐直接使用命令行的方式运行容器,而是使用YAML文件的方式,即:把容器的定义、参数、配置都记录在一个YAML文件中,然后使用如下命令: kubectl create -f <xxx.yaml> 这么做的最大好处,有一个文件能记录Kubernetes到底运行了什么。 0.1. YAML示例 apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80 这个 YAML 文件,对应到 Kubernetes 中,就是一个 API Object(API 对象)。 为这个对象的各个字段填好值并提交给Kubernetes之后,Kubernetes就会负责创建出这些对象所定义的容器或者其他类型的API 资源。 可以看到,这个 YAML 文件中的 Kind 字段,指定了这个API对象的类型(Type),是一个Deployment。 所谓 Deployment,是一个定义多副本应用(即多个副本 Pod)的对象,此外,Deployment 还负责在 Pod 定义发生变化时,对每个副本进行滚动更新(Rolling Update)。 这个 YAML 文件中: 定义的 Pod 副本个数 (spec.replicas) 是:2 定义了一个 Pod 模版(spec.template),这个模版描述了要创建的 Pod 的细节 这个 Pod 里只有一个容器,这个容器的镜像(spec.containers.image)是nginx:1.7.9,这个容器监听端口(containerPort)是 80 0.2. API对象 Pod就是Kubernetes世界里的“应用”,而一个应用,可以由多个容器组成。 像这样使用一种 API 对象(Deployment)管理另一种 API 对象(Pod)的方法,在 Kubernetes 中,叫作“控制器”模式(controller pattern)。 在上面的例子中,Deployment扮演的正是 Pod 的控制器的角色。 0.2.1. Metadata 这样的每一个 API 对象都有一个叫作 Metadata 的字段,这个字段就是 API 对象的“标识”,即元数据,它也是从 Kubernetes 里找到这个对象的主要依据,这其中最主要使用到的字段是Labels。 Labels 就是一组 key-value 格式的标签。而像 Deployment 这样的控制器对象,就可以通过这个 Labels 字段从 Kubernetes 中过滤出它所关心的被控制对象。 比如,在上面这个 YAML 文件中,Deployment 会把所有正在运行的、携带“app: nginx”标签的Pod 识别为被管理的对象,并确保这些 Pod 的总数严格等于两个。 0.2.2. Label Selector 过滤规则的定义,是在 Deployment 的“spec.selector.matchLabels”字段,一般称之为:Label Selector。 0.2.3. Annotations 在 Metadata中,还有一个与Labels格式、层级完全相同的字段叫Annotations,它专门用来携带 key-value 格式的内部信息。 内部信息,指的是对这些信息感兴趣的是Kubernetes 组件本身而不是用户。所以大多数Annotations,都是在 Kubernetes 运行过程中,被自动加在这个 API 对象上。 一个 Kubernetes 的 API 对象的定义,大多可以分为 Metadata 和 Spec 两个部分: 前者存放的是这个对象的元数据,对所有 API 对象来说,这一部分的字段和格式基本上是一样的 后者存放的是属于这个对象独有的定义,用来描述它所要表达的功能 0.3. 运行API对象 # 创建API对象 kubectl create -f nginx-deployment.yaml # 查看API对象,-l参数获取所有匹配标签的pod kubectl get pods -l app=nginx # 查看一个 API 对象的细节 kubectl describe pod nginx kubectl get 指令的作用,就是从 Kubernetes 里面获取(GET)指定的 API 对象。需要注意的是,在命令行中,所有 key-value格式的参数,都使用“=”而非“:”表示。 kubectl describe 命令返回的结果中,可以清楚地看到这个 Pod 的详细信息,比如它的 IP 地址等等。其中,有一个部分值得特别关注,就是Events(事件)。 在 Kubernetes 执行的过程中,对 API 对象的所有重要操作,都会被记录在这个对象的 Events 里,并且显示在 kubectl describe 指令返回的结果中。这个部分正是我们将来进行 Debug 的重要依据。如果有异常发生,要第一时间查看这些 Events,往往可以看到非常详细的错误信息。 0.3.1. 示例 上述deployment中的pod运行的是1.7.9的nginx容器,如何升级成1.8? 只要修改刚才的YAML文件即可: ... spec: containers: - name: nginx image: nginx:1.8 # 这里从 1.7.9 修改为 1.8 ports: - containerPort: 80 这样对YAML配置文件的本地修改就完成了,通过如下命令更新到kubernetes集群中: kubectl replace -f nginx-deployment.yaml # 1 kubectl create -f file.yaml # 2 kubectl replace -f file.yaml # 3 kubectl apply -f file.yaml # 上述的命令1和2可以用3替换掉,这也是kubernetes“声明式API”推荐的做法。 通过容器镜像,保证了应用本身在开发和部署环境里的一致性(当应用发生变化时,开发和运维可以依靠容器进行同步) 通过YAML配置文件,保证了应用“部署参数”在开发和部署环境中的一致性(当应用部署参数发生变化时,开发和运维可以依靠YAML配置文件进行沟通) 0.4. 挂载volume 在 Kubernetes 中,Volume 是属于 Pod 对象的一部分。所以,我们就需要修改这个 YAML 文件里的 template.spec 字段,如下所示: apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.8 ports: - containerPort: 80 volumeMounts: - mountPath: "/usr/share/nginx/html" name: nginx-vol volumes: - name: nginx-vol emptyDir: {} 在 Deployment 的 Pod 模板部分添加了一个 volumes 字段,定义了这个 Pod 声明的所有 Volume。它的名字叫作 nginx-vol,类型是 emptyDir。 那什么是 emptyDir类型呢? 它其实就等同于Docker的隐式Volume参数,即:不显式声明宿主机目录的Volume。所以,Kubernetes也会在宿主机上创建一个临时目录,这个目录将来就会被绑定挂载到容器所声明的 Volume 目录上。 Kubernetes 的 emptyDir类型,只是把Kubernetes 创建的临时目录作为Volume的宿主机目录交给Docker,因为 Kubernetes 不想依赖 Docker 创建的 _data 目录。 而 Pod 中的容器,使用的是 volumeMounts 字段来声明自己要挂载哪个 Volume,并通过mountPath 字段来定义容器内的 Volume 目录,比如:/usr/share/nginx/html。 当然,Kubernetes 也提供了显式的 Volume 定义,它叫做 hostPath。比如下面的这个 YAML 文件: ... volumes: - name: nginx-vol hostPath: path: /var/data 这样volume挂载的宿主机目录,就变成了/var/data。 0.5. 进入容器 使用如下命令,进入到Pod中,即容器的Namespace中: kubectl exec -it nginx-deployment-5c678cfb6d-lg9lw -- /bin/bash ls /usr/share/nginx/html 从Kubernetes集群中删除部署的Deployment的命令: kubectl delete -f nginx-deployment.yaml