上一回,我们搭建了kubernetes集群,现在我们使用kubernetes集群发布第一个容器化应用。在这之前,我们要制作容器的镜像。

使用kubernetes的必备技能:编写配置文件(YAML)。

Kubernetes跟docker项目不同,它不建议我们使用命令行的方式运行容器,而是编写yaml文件,把容器的定义、参数、配置,统统记录在一个yaml文件中,然后用这样一句指令把它运行起来:

kubectl create -f 配置文件

配置文件例子:

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

kubernetes负责把这些对象所定义的容器或其他资源创建出来。

其中,deployment是一个定义多副本(上面时两个副本)应用的对象,负责在pod定义发生变化时,对每个副本进行更新。上面定义了一个pod模版,镜像时nginx:1.7.9,监听端口时80。

pod就是kubernetes世界里的“应用”;而一个应用,可以由多个容器组成。

每个API对象都有一个叫作Metadata的字段,这个字段就是API对象的标识,即元数据,它也是我们从kubernetes里找到这个对象的主要依据。这其中最主要使用到的字段是Labels。而像deployment这样的控制器对象,就可以通过这个labels字段从kubernetes中过滤出它所关心的被控制对象。

比如,在这个YAML中,deployment会把所有正在运行的、携带"app:nginx"标签的pod识别为被管理的对象,并确保这些pod的总数严格等于两个。

spec.selector.matchlabels字段,我们一般称为:Label Selector。

一个 Kubernetes 的 API 对象的定义,大多可以分为metadata和spec两个部分。前者存放的是这个对象的元数据,对所有API对象来说,这一部分的字段和格式基本上是一样的;而后者存放的,则是属于这个对象独有的定义,用来描述它所要表达的功能。

可以使用kubectl create指令完成这个操作:

kubectl create -f nginx-deployment.yaml

然后,通过kubectl get 命令检查这个YAML运行起来的状态是不是与我们预期的一致:

$ kubectl get pods -l app=nginx
NAME                                READY     STATUS    RESTARTS   AGE
nginx-deployment-67594d6bf6-9gdvr   1/1       Running   0          10m
nginx-deployment-67594d6bf6-v6j7w   1/1       Running   0          10m

在命令行中,所有key-value格式,都使用"="表示。kubectl describe查看API对象的详情

$ kubectl describe pod nginx-deployment-67594d6bf6-9gdvr
Name:               nginx-deployment-67594d6bf6-9gdvr
Namespace:          default
Priority:           0
PriorityClassName:  <none>
Node:               node-1/10.168.0.3
Start Time:         Thu, 16 Aug 2018 08:48:42 +0000
Labels:             app=nginx
                    pod-template-hash=2315082692
Annotations:        <none>
Status:             Running
IP:                 10.32.0.23
Controlled By:      ReplicaSet/nginx-deployment-67594d6bf6
...
Events:

  Type     Reason                  Age                From               Message

  ----     ------                  ----               ----               -------

  Normal   Scheduled               1m                 default-scheduler  Successfully assigned default/nginx-deployment-67594d6bf6-9gdvr to node-1
  Normal   Pulling                 25s                kubelet, node-1    pulling image "nginx:1.7.9"
  Normal   Pulled                  17s                kubelet, node-1    Successfully pulled image "nginx:1.7.9"
  Normal   Created                 17s                kubelet, node-1    Created container
  Normal   Started                 17s                kubelet, node-1    Started container

在kubernetes执行过程中,对API对象的所有重要操作,都会被记录在这个对象的events里,并且显示在kubectl describe指令返回的结果里。这部分是我们将来进行debug的重要依据。如果有异常发生,你一定要第一时间查看这些events,往往可以看到非常详细的错误信息。

如果要对这个nginx服务进行升级,把它的镜像从1.7.9升级为1.8,要怎么做?

只需要修改YAML文件即可。

...
    spec:
      containers:
      - name: nginx
        image: nginx:1.8 # 这里被从 1.7.9 修改为 1.8
        ports:
      - containerPort: 80

可是,这个修改目前只发生在本地,如何让这个更新在kubernetes里也生效呢?

 $ kubectl replace -f nginx-deployment.yaml

不过,推荐使用kubectl apply命令,来统一进行kubernetes对象的创建和更新操作。

$ kubectl apply -f nginx-deployment.yaml

# 修改 nginx-deployment.yaml 的内容

$ kubectl apply -f nginx-deployment.yaml

作为用户,我们不必关心当前的操作是创建还是更新,你执行的命令都是kubectl apply,kubernetes会根据配置文件里的内容自动进行具体的处理。

所以说,如果通过容器镜像,我们能够保证应用本身在开发与部署环境里的一致性的话,那么现在,kubernetes项目通过这些YAML文件,就保证了应用的“部署参数”在开发与部署环境中的一致性。

而当应用本身发生变化时,开发人员和运维人员可以依靠容器镜像来进行同步;当应用部署参数发生变化时,这些YAML文件就是他们相互沟通和信任的媒介。

接下来,我们再在这个deployment中尝试声明一个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。

在上述修改完成后,我们还是使用kubectl apply指令,更新这个Deployment:。

$ kubectl apply -f nginx-deployment.yaml

接下来,可以通过kubectl get 指令,查看两个pod被逐一更新的过程:

$ kubectl get pods
NAME                                READY     STATUS              RESTARTS   AGE
nginx-deployment-5c678cfb6d-v5dlh   0/1       ContainerCreating   0          4s
nginx-deployment-67594d6bf6-9gdvr   1/1       Running             0          10m
nginx-deployment-67594d6bf6-v6j7w   1/1       Running             0          10m
$ kubectl get pods
NAME                                READY     STATUS    RESTARTS   AGE
nginx-deployment-5c678cfb6d-lg9lw   1/1       Running   0          8s
nginx-deployment-5c678cfb6d-v5dlh   1/1       Running   0          19s

从结果看,新旧两个pod,被交替创建、删除,最后剩下的就是最新版本的pod。使用kubectl describe查看最新的pod,就会发现volume信息已经出现在了container里:

...
Containers:
  nginx:
    Container ID:   docker://07b4f89248791c2aa47787e3da3cc94b48576cd173018356a6ec8db2b6041343
    Image:          nginx:1.8
    ...
    Environment:    <none>
    Mounts:
      /usr/share/nginx/html from nginx-vol (rw)
...
Volumes:
  nginx-vol:
    Type:    EmptyDir (a temporary directory that shares a pod's lifetime)

最后,可以使用kubectl exec 进入到这个pod中(即容器的namespace中)查看这个volume目录:

$ kubectl exec -it nginx-deployment-5c678cfb6d-lg9lw -- /bin/bash
# ls /usr/share/nginx/html

此外,你想要从kubernetes集群中删除这个nginx deployment的话,执行:

$ kubectl delete -f nginx-deployment.yaml

总结

kubernetes里“最小”的API对象是pod。pod可以等价为一个应用,所以,pod可以由多个紧密协作的容器组成。

在kubernetes中,我们经常会看到它通过一种API对象来管理另一种API对象,这种组合方式,正是kubernetes进行容器编排的重要模式。

每个kubernetes API对象,往往由Metadata和Spec两部分组成,其中Metadata里的Labels是kubernetes过滤对象的重要手段。

容器要想使用数据卷,volume,正是pod的spec字段的一部分。而pod里的每个容器,则需要显式的声明自己要挂载那个volume。

如果你想要快速熟悉kubernetes,流程:

1)首先,在本顶通过docker测试代码,制作镜像;

2)然后,选择合适的API对象,编写对应YAML文件;

3)最后,在kubernetes上部署这个YAML文件。

注意:以后通过YAML文件来实现所有操作,尽量不要使用docker的命令运行了。

07-19 05:22