Kubernetes — 我的第一个容器化应用

Stella981
• 阅读 782

而在这篇文章中,我们就来扮演一个应用开发者的角色,使用这个 Kubernetes 集群发布第一个容器化应用。

在开始实践之前,我先给你讲解一下 Kubernetes 里面与开发者关系最密切的几个概念。

作为一个应用开发者,你首先要做的,是制作容器的镜像。而有了容器镜像之后,你需要按照+Kubernetes+项目的规范和要求,将你的镜像组织为它能够“认识”的方式,然后提交上去。

那么,什么才是 Kubernetes 项目能“认识”的方式呢?

这就是使用 Kubernetes 的必备技能:编写配置文件。

备注:这些配置文件可以是 YAML 或者 JSON 格式的。为方便阅读与理解,在后面的讲解中,我会统一使用 YAML 文件来指代它们。 Kubernetes 跟 Docker 等很多项目最大的不同,就在于它不推荐你使用命令行的方式直接运行容器(虽然 Kubernetes 项目也支持这种方式,比如:kubectl run),而是希望你用 YAML 文件的方式,即:把容器的定义、参数、配置,统统记录在一个 YAML 文件中,然后用这样一句指令把它运行起来:

$ kubectl create -f 我的配置文件

小案例 发布一个nginx应用

这么做最直接的好处是,你会有一个文件能记录下 Kubernetes 到底“run”了什么。比如下面这个例子:

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 具体的又长什么样子呢?

为此,我定义了一个 Pod 模版(spec.template),这个模版描述了我想要创建的 Pod 的细节。在上面的例子里,这个 Pod 里只有一个容器,这个容器的镜像(spec.containers.image)是nginx=1.7.9,这个容器监听端口(containerPort)是 80。

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

 需要注意的是,像这样使用一种 API 对象(Deployment)管理另一种 API 对象(Pod)的方法,在 Kubernetes 中,叫作“控制器”模式(controller pattern)。在我们的例子中,Deployment 扮演的正是 Pod 的控制器的角色。

 你可能还注意到,这样的每一个 API 对象都有一个叫作 Metadata 的字段,这个字段就是 API 对象的“标识”,即元数据,它也是我们从 Kubernetes 里找到这个对象的主要依据。这其中最主要使用到的字段是 Labels。

顾名思义,Labels 就是一组 key-value 格式的标签。而像 Deployment 这样的控制器对象,就可以通过这个 Labels 字段从 Kubernetes 中过滤出它所关心的被控制对象。

比如,在上面这个 YAML 文件中,Deployment 会把所有正在运行的、携带“app=nginx”标签的 Pod 识别为被管理的对象,并确保这些 Pod 的总数严格等于两个。

而这个过滤规则的定义,是在 Deployment 的“spec.selector.matchLabels”字段。我们一般称之为:Label+Selector。

另外,在 Metadata 中,还有一个与 Labels 格式、层级完全相同的字段叫 Annotations,它专门用来携带 key-value 格式的内部信息。所谓内部信息,指的是对这些信息感兴趣的,是 Kubernetes 组件本身,而不是用户。所以大多数 Annotations,都是在 Kubernetes 运行过程中,被自动加在这个 API 对象上。

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

 在了解了上述 Kubernetes 配置文件的基本知识之后,我们现在就可以把这个 YAML 文件“运行”起来。正如前所述,你可以使用 kubectl create 指令完成这个操作:

kubectl create -f nginx-deployment.yaml

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

kubectl get pods -l app=nginx

Kubernetes — 我的第一个容器化应用

kubectl get 指令的作用,就是从 Kubernetes 里面获取(GET)指定的 API 对象。可以看到,在这里我还加上了一个 -l 参数,即获取所有匹配 app=nginx 标签的 Pod。需要注意的是,在命令行中,所有 key-value 格式的参数,都使用“=”而非“:”表示。 从这条指令返回的结果中,我们可以看到现在有两个 Pod 处于 Running 状态,也就意味着我们这个 Deployment 所管理的 Pod 都处于预期的状态。

此外, 你还可以使用 kubectl describe 命令,查看一个 API 对象的细节,比如:

kubectl describe pods nginx-deployment-5c678cfb6d-44pjt

详细的输出信息如下

Name:               nginx-deployment-5c678cfb6d-44pjt
Namespace:          default
Priority:           0
PriorityClassName:  <none>
Node:               localhost.localdomain/10.104.204.21
Start Time:         Mon, 18 Mar 2019 01:33:58 -0400
Labels:             app=nginx
                    pod-template-hash=1723479628
Annotations:        <none>
Status:             Running
IP:                 10.32.0.13
Controlled By:      ReplicaSet/nginx-deployment-5c678cfb6d
Containers:
  nginx:
    Container ID:   docker://8b93348544c4407829c670040e32dd5613e16037ad4ed444767bc7dd3dbbcc43
    Image:          nginx:1.8
    Image ID:       docker-pullable://nginx@sha256:c97ee70c4048fe79765f7c2ec0931957c2898f47400128f4f3640d0ae5d60d10
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Mon, 18 Mar 2019 01:33:58 -0400
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /usr/share/nginx/html from nginx-vol (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-9mzlh (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  nginx-vol:
    Type:    EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:  
  default-token-9mzlh:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-9mzlh
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age   From                            Message
  ----    ------     ----  ----                            -------
  Normal  Scheduled  35m   default-scheduler               Successfully assigned default/nginx-deployment-5c678cfb6d-44pjt to localhost.localdomain
  Normal  Pulled     35m   kubelet, localhost.localdomain  Container image "nginx:1.8" already present on machine
  Normal  Created    35m   kubelet, localhost.localdomain  Created container
  Normal  Started    35m   kubelet, localhost.localdomain  Started container

在 kubectl describe 命令返回的结果中,你可以清楚地看到这个 Pod 的详细信息,比如它的 IP 地址等等。其中,有一个部分值得你特别关注,它就是Events(事件)。

在 Kubernetes 执行的过程中,对 API 对象的所有重要操作,都会被记录在这个对象的 Events 里,并且显示在 kubectl describe 指令返回的结果中。

比如,对于这个 Pod,我们可以看到它被创建之后,被调度器调度(Successfully assigned)到了 node-1,拉取了指定的镜像(pulling image),然后启动了 Pod 里定义的容器(Started container)。

所以,这个部分正是我们将来进行 Debug 的重要依据。如果有异常发生,你一定要第一时间查看这些Events,往往可以看到非常详细的错误信息。

对nginx应用进行升级

接下来,如果我们要对这个 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+指令来完成这个更新:

kubectl replace -f nginx-deployment.yaml

不过,在本专栏里,我推荐你使用 kubectl apply 命令,来统一进行 Kubernetes 对象的创建和更新操作,具体做法如下所示:  

kubectl apply -f nginx-deployment.yaml

# 修改 nginx-deployment.yaml 的内容

kubectl apply -f nginx-deployment.yaml

这样的操作方法,是 Kubernetes“声明式 API”所推荐的使用方法。也就是说,作为用户,你不必关心当前的操作是创建,还是更新,你执行的命令始终是 kubectl apply,而 Kubernetes 则会根据 YAML 文件的内容变化,自动进行具体的处理。

而这个流程的好处是,它有助于帮助开发和运维人员,围绕着可以版本化管理的 YAML 文件,而不是“行踪不定”的命令行进行协作,从而大大降低开发人员和运维人员之间的沟通成本。

举个例子,一位开发人员开发好一个应用,制作好了容器镜像。那么他就可以在应用的发布目录里附带上一个 Deployment 的 YAML 文件。

而运维人员,拿到这个应用的发布目录后,就可以直接用这个 YAML 文件执行 kubectl apply 操作把它运行起来。

这时候,如果开发人员修改了应用,生成了新的发布内容,那么这个 YAML 文件,也就需要被修改,并且成为这次变更的一部分。

而接下来,运维人员可以使用 git diff 命令查看到这个 YAML 文件本身的变化,然后继续用 kubectl apply 命令更新这个应用。

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

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

以上,就是 Kubernetes 发布应用的最基本操作了。

接下来,我们再在这个 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 目录,比如:/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
exit

此外,你想要从 Kubernetes 集群中删除这个 Nginx Deployment 的话,直接执行:

kubectl delete -f nginx-deployment.yaml

备忘

部署和更新一个pod(应用)

kubectl apply -f ****.yaml

查看一个具体应用的名字和我们预期的是否一致

kubectl get pods -l app=name
# 如
kubectl get pods -l app=nginx

查看一个 API 对象的细节,比如:  

kubectl describe pods nginx-deployment-5c678cfb6d-44pjt

此外,你想要从 Kubernetes 集群中删除这个 Nginx Deployment 的话,直接执行:  

kubectl delete -f nginx-deployment.yaml
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这