使用YAML创建一个 Kubernetes Depolyment

在之前的文章中,我们已经提到过如何使用Kubernetes去创建资源。到目前为止,我们一直仅仅通过命令行去执行,但是这里有一个更加简单有效的方式去创建资源:通过使用YAML创建一个配置文件。在这篇文章,我们将会关注YAML的工作方式以及如何使用YAML创建一个Kubernetes Pod,然后使用Kubernetes创建一个Depolyment。当然您如果是新手可以先了解Kubernetes上运行你的第一个容器

YAML 基础

如果你正在做的事与很多软件领域相关,那么将很难不涉及到YAML,特别是Kubernetes,SDN,和OpenStack。YAML,它代表着另一种标志语言,或者YAML不是标志语言(取决于你问谁)而是特定配置类型基于人类可读的文本格式的信息,例如,在本文中,我们将会分开说说明YAML定义创建Pod和使用Kubernetes创建一个Depolyment。

使用YAML用于k8s的定义将给你一些好处,包括:

  • 便捷性:你将不再需要添加大量的参数到命令行中执行命令
  • 可维护性:YAML文件可以通过源头控制,可以跟踪每次的操作
  • 灵活性:通过YAML你将可以创建比命令行更加复杂的结构

YAML是一个JSON的超集,意味着任何有效JSON文件也都是一个有效的YAML文件。所以一方面,如果你知道JSON,你只是要去写自己的YAML(而不是阅读别人的)也就可以了。另一方面,不太可能,不幸的是,尽管你尝试去网上找到例子,但是他们通常都不是JSON,所以我们可能需要去习惯它。不过,有JSON的情况下可能会更方便,这样你将会很开心你懂得JSON。

幸运的是,YAML只有两种结构类型你需要知道:

  • Lists
  • Maps

那就是说,将有可能存在lists的maps和maps的lists,等等,但是,你只要掌握了这两种结构也就可以了。这并不是说你不能做更加复杂的事,但是通常,这些也就够了。

YAML Maps

我们先开始看YAML maps。Maps让你将键值组合,你就可以更加方便的去设置配置信息。例如,你可能有以下这样一个配置信息:

---
apiVersion: v1
kind: Pod

第一行是分隔符,并且是可选的,除非你试图在单个文件中定义多个结构。从这里你可以看到,我们有两个值,V1和Pod,对应他们的键是apiVersion和kind。

这种比较简单,当然你也可以将之转换为json格式,如下:

{
   "apiVersion": "v1",
   "kind": "Pod"
}

注意,在我们的YAML版本中,引号是可选的,处理器可以知道你正在查看基于格式化的字符串。

你也可以指定一个复杂的结构,创建一个key其对应的值不是字符串而是一个maps如下所示:

---
apiVersion: v1
kind: Pod
metadata:
  name: rss-site
  labels:
    app: web

这种情况下,我们有metadata这个key对应的值中又有两个key分别为name和labels。labels 这个key的值又是一个map。你可以根据场景进行多层嵌套。

YAML处理器可以根据行缩进来知道内容之间的关联。在这个例子中我用了两个空格使之可读,但是空格的数量不重要,但是至少要求一个,并且所有缩进都保持一致的空格数。例如,name和labels是相同缩进级别,因此YAML处理器知道他们属于同一map;它知道app是lables的值因为app的缩进更大。

注意:在YAML文件中绝对不要使用tab键

因此,如果我们将上述内容翻译成JSON,它看起来结果如下所示:

{
  "apiVersion": "v1",
  "kind": "Pod",
  "metadata": {
               "name": "rss-site",
               "labels": {
                          "app": "web"
                         }
              }
}

现在让我们来看看lists。

YAML lists

YAML lists 简直是一个序列的对象,例如:

args
  - sleep
  - "1000"
  - message
  - "Bring back Firefly!"

正如你可以看到,你可以有任何数量的项在列表中,项的定义以破折号(-)开头,并且与父元素之间存在缩进。在JSON格式中,它将表示如下:

{
   "args": ["sleep", "1000", "message", "Bring back Firefly!"]
}

当然,list的子项也可以是maps,maps的子项也可以是list如下所示:

---
apiVersion: v1
kind: Pod
metadata:
  name: rss-site
  labels:
    app: web
spec:
  containers:
    - name: front-end
      image: nginx
      ports:
        - containerPort: 80
    - name: rss-reader
      image: nickchase/rss-php-nginx:v1
      ports:
        - containerPort: 88

正如你所看到的,我们有一个叫container的list对象,每个子项都由name、image、ports组成,每个ports都由一个key为containerPort map组成

如下所示,是上述内容的JSON格式:

{
   "apiVersion": "v1",
   "kind": "Pod",
   "metadata": {
                 "name": "rss-site",
                 "labels": {
                             "app": "web"
                           }
               },
    "spec": {
       "containers": [{
                       "name": "front-end",
                       "image": "nginx",
                       "ports": [{
                                  "containerPort": "80"
                                 }]
                      }, 
                      {
                       "name": "rss-reader",
                       "image": "nickchase/rss-php-nginx:v1",
                       "ports": [{
                                  "containerPort": "88"
                                 }]
                      }]
            }
}

正如你所看到的,我们写的内容开始变的复杂,甚至我们还没有进入任何特别复杂!难怪YAML代替JSON如此之快。

现在然我们复习一下,我们有:

  • maps,键值对
  • lists,单独的项
  • maps的maps
  • maps的lists
  • lists的lists
  • lists的maps

基本上,无论你想要什么样结构,你都可以通过这两个结构去组合实现。

使用YAML创建Pod

所以现在我们已经有了基础知识,让我们将他们用起来。我们将使用YAML创建一个Pod然后创建Deployment。

如果你还没有搭建起集群和kubectl,那么在你继续下去之前需要阅读之前关于如何搭建kubernets集群的文章,等搭建完成了我们再继续下去。
回来了?好的!让我们开始创建一个Pod。

创建Pod文件

在之前的例子中,我们通过YAML描述了一个简单的Pod文件内容:


apiVersion: v1
kind: Pod
metadata:
name: rss-site
labels:
app: web
spec:
containers:
– name: front-end
image: nginx
ports:
– containerPort: 80
– name: rss-reader
image: nickchase/rss-php-nginx:v1
ports:
– containerPort: 88

让我们分开来解析这些内容,先从apiVersion 这个关键字开始;这里它的值是v1.(当我们要创建Depolyment的时候,这个值将会指定一个不一样的值,因为Depolyment没有v1版本。)

接下来,我们指定我们要创建的是一个Pod;根据我们想要得到的结果,我们将会指定Pod而不是Deployment,Job,Services等等。

最后我们将指定组成pod的实际对象。spec属性包括一些containers,storage,volumes,或者其他Kubernetes需要知道的参数,以及诸如是否在容器失败时重新启动容器的属性。你可以在特定Kubernetes API找到完整的Kubernetes Pod的属性,让我们进一步来看一个典型的容器的定义:


spec:
containers:
– name: front-end
image: nginx
ports:
– containerPort: 80
– name: rss-reader

在这个例子中,这是一个简单的最小定义:一个名字(front-end),基于nginx的镜像,以及容器 将会监听的一个端口(80)。在这些当中,只有名字是非常需要的,但是通常如果你想做更多的事情,你需要更多的定义。

你也可以指定一个更加复杂的属性,例如在容器启动时运行的命令,应使用的参数,工作目录,或每次实例化时是否拉取映像的新副本。 您还可以指定更深入的信息,例如容器的退出日志的位置。

以下有一些容器可以设置的属性:

  • name
  • image
  • command
  • args
  • workingDir
  • ports
  • env
  • resources
  • volumeMounts
  • livenessProbe
  • readinessProbe
  • livecycle
  • terminationMessagePath
  • imagePullPolicy
  • securityContext
  • stdin
  • stdinOnce
  • tty

现在让我继续真正的去创建Pod。

使用YAML文件创建Pod
第一步,创建一个文本文件,叫做pod.yaml,然后添加我们之前定义的内容,如下所示:


apiVersion: v1
kind: Pod
metadata:
name: rss-site
labels:
app: web
spec:
containers:
– name: front-end
image: nginx
ports:
– containerPort: 80
– name: rss-reader
image: nickchase/rss-php-nginx:v1
ports:
– containerPort: 88

保存这个文件,让Kubernetes去执行内容所描述的动作:

> kubectl create -f pod.yaml
pod "rss-site" created

正如你所见,k8s 引用了你给pod的命名。如果你想看pods列表可以通过以下方式:

> kubectl get pods
NAME READY STATUS RESTARTS AGE
rss-site 0/2 ContainerCreating 0 6s

如果你更早的查看的话,你可以看到它仍然处于created的状态,大概几秒之后,你就可以看到容器处于running状态:

> kubectl get pods
NAME       READY     STATUS    RESTARTS   AGE
rss-site   2/2       Running   0          14s

至此,你可以测试以下Pod(正如我们之前文章做的),但是最后我们要创建一个Deployment,因此我们先删除这个pod以免发生名字冲突:

> kubectl delete pod rss-site
pod "rss-site" deleted

pod创建故障定位

有时候,事情并不会如我们所想的发展下去。可能你会遇到一个网络问题,或者在YAML文件用错了内容。你可能会看到以下类似以下的错误:

> kubectl get pods
NAME       READY     STATUS         RESTARTS   AGE
rss-site   1/2       ErrImagePull   0          9s

在这个情况下,我们可以看到我们其中一个容器启动正常,但是另一个出现了问题。我们可以用以下方式去查看pod的详细信息并定位这个问题:

> kubectl describe pod rss-site
Name:           rss-site
Namespace:      default
Node:           10.0.10.7/10.0.10.7
Start Time:     Sun, 08 Jan 2017 08:36:47 +0000
Labels:         app=web
Status:         Pending
IP:             10.200.18.2
Controllers:    <none>
Containers:
  front-end:
    Container ID:               docker://a42edaa6dfbfdf161f3df5bc6af05e740b97fd9ac3d35317a6dcda77b0310759
    Image:                      nginx
    Image ID:                   docker://sha256:01f818af747d88b4ebca7cdabd0c581e406e0e790be72678d257735fad84a15f
    Port:                       80/TCP
    State:                      Running
      Started:                  Sun, 08 Jan 2017 08:36:49 +0000
    Ready:                      True
    Restart Count:              0
    Environment Variables:      <none>
  rss-reader:
    Container ID:
    Image:                      nickchase/rss-php-nginx
    Image ID:
    Port:                       88/TCP
    State:                      Waiting
      Reason:                   ErrImagePull
    Ready:                      False
    Restart Count:              0
    Environment Variables:      <none>
Conditions:
  Type          Status
  Initialized   True
  Ready         False
  PodScheduled  True
No volumes.
QoS Tier:       BestEffort
Events:
  FirstSeen     LastSeen        Count   From                    SubobjectPath  Type             Reason                  Message
  ---------     --------        -----   ----                    -------------  -------- ------                  -------
  45s           45s             1       {default-scheduler }                   Normal           Scheduled               Successfully assigned rss-site to 10.0.10.7
  44s           44s             1       {kubelet 10.0.10.7}     spec.containers{front-end}      Normal          Pulling                 pulling image "nginx"
  45s           43s             2       {kubelet 10.0.10.7}                    Warning          MissingClusterDNS       kubelet does not have ClusterDNS IP configured and cannot create Pod using "ClusterFirst" policy. Falling back to DNSDefault policy.
  43s           43s             1       {kubelet 10.0.10.7}     spec.containers{front-end}      Normal          Pulled                  Successfully pulled image "nginx"
  43s           43s             1       {kubelet 10.0.10.7}     spec.containers{front-end}      Normal          Created                 Created container with docker id a42edaa6dfbf
  43s           43s             1       {kubelet 10.0.10.7}     spec.containers{front-end}      Normal          Started                 Started container with docker id a42edaa6dfbf
  43s           29s             2       {kubelet 10.0.10.7}     spec.containers{rss-reader}     Normal          Pulling                 pulling image "nickchase/rss-php-nginx"
  42s           26s             2       {kubelet 10.0.10.7}     spec.containers{rss-reader}     Warning         Failed                  Failed to pull image "nickchase/rss-php-nginx": Tag latest not found in repository docker.io/nickchase/rss-php-nginx
  42s           26s             2       {kubelet 10.0.10.7}                    Warning          FailedSync              Error syncing pod, skipping: failed to "StartContainer" for "rss-reader" with ErrImagePull: "Tag latest not found in repository docker.io/nickchase/rss-php-nginx"


  41s   12s     2       {kubelet 10.0.10.7}     spec.containers{rss-reader}    Normal   BackOff         Back-off pulling image "nickchase/rss-php-nginx"
  41s   12s     2       {kubelet 10.0.10.7}                                    Warning  FailedSync      Error syncing pod, skipping: failed to "StartContainer" for "rss-reader" with ImagePullBackOff: "Back-off pulling image \"nickchase/rss-php-nginx\""

正如你所见,这里有很多信息,但是最有用的信息在Events中,详细的显示出了曾经出现的警告和错误信息。从这里我们可以很快的看出,我忘记将:有v1 标签的镜像,所以它用了最新的镜像,但是它不存在。

为了解决这个问题,我先删了这个Pod,然后修复YAML文件,重新启动。我修复了之后Kubernetes 将能够从镜像库中到到,之后也就能够安装下去了。

现在我们已经成功运行了一个Pod,接下让让我们同样的运行Deployment。

使用YAML文件创建Deployment
最后,我们将要进行真正的创建Depolyment。在那之前,我们必须了解它实际上是在做什么。

k8s,记住和管理基于容器的资源。在这个Deployment的例子中,你正在创建一组要管理的资源。例如,在之前的例子中,我们给Pod创建了一个实例,我们将创建一个Deployment去让Kubernetes管理一组Pod的副本,也就是副本集,保证一定数量的副本一直可用。因此我们可以这样定义一个Deployment:


apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: rss-site
spec:
replicas: 2

这里我们指定了apiVersion为extensions/v1beta1,记住,Deployments并不像Pods是v1,我们这里想要的是Deployments。接下来我们指定名字,我们也可以指定我们想要的元数据,但是我们现在尽量保持简单。

最后,我们进入配置spec项,我们之前在其中指定我们想要在Pod中做的事;同样的我们也需要在其中指定Deployment中做的事。我们将开始,在这种情况下,通过说我们部署的任何Pod,我们总是想有2个副本。 当然,你可以设置这个数字,你还可以设置属性,如定义受此Deployment影响的Pod的选择器,或者一个Pod在没有任何错误变成准备的情况下必须达到的最小秒数。

你可以在Kubernetes v1beta1 API参考中找到一个完整的Depolyment可指定的参数列表。

因此目前我们想要创建两个副本,我们需要知道我们做的是什么的副本?他们可以通过以下模板定义:


apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: rss-site
spec:
replicas: 2
template:
metadata:
labels:
app: web
spec:
containers:
– name: front-end
image: nginx
ports:
– containerPort: 80
– name: rss-reader
image: nickchase/rss-php-nginx:v1
ports:
– containerPort: 88

看起来很熟悉?没错,它和我们之前的Pod定义很类似,它就是这么设计的。模板是对要复制的对象的简单定义,在其他情况下,可以通过自己创建的对象。

现在让我们继续创建Deployment。添加一个叫做deployment.yaml文件并执行以下命令:

> kubectl create -f deployment.yaml
deployment "rss-site" created

想要查看它正在做的情况,我们可以检查Deployment的列表:

> kubectl get deployments
NAME       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
rss-site   2         2         2            1           7s

正如你所看到的,Kubernetes启动了所有的副本,但是只有一个可用。你可以如之前一样查看event日志对Deployment的描述:

> kubectl describe deployment rss-site
Name:                   rss-site
Namespace:              default
CreationTimestamp:      Mon, 09 Jan 2017 17:42:14 +0000=
Labels:                 app=web
Selector:               app=web
Replicas:               2 updated | 2 total | 1 available | 1 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  1 max unavailable, 1 max surge
OldReplicaSets:         <none>
NewReplicaSet:          rss-site-4056856218 (2/2 replicas created)
Events:
  FirstSeen     LastSeen        Count   From                            SubobjectPath   Type            Reason                  Message
  ---------     --------        -----   ----                            -------------   --------        ------                  -------
  46s           46s             1       {deployment-controller }               Normal           ScalingReplicaSet       Scaled up replica set rss-site-4056856218 to 2

你可以看到,并没有什么问题,他只是还没创建好副本。再过一会之后,我们发现所有的Pods正在运行:

> kubectl get deployments
NAME       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
rss-site   2         2         2            2           1m

到目前为止我们所看到的

让我们复习以下,我们基本上涵盖了三个主题:

  1. YAML是一种基于人类可读的基于文本的格式,通过使用名称 – 值对映射和项目列表(以及每个项目的嵌套版本)的组合,您可以轻松地指定配置类型信息。
  2. YAML是使用Kubernetes对象的最方便的方法,在本文中,我们考虑创建Pods和Deployments。
  3. 您可以通过要求Kubernetes描述它们来获取关于运行(或应该运行)对象的更多信息。

这是我们的基本YAML教程。我们将在未来几个月内处理大量与Kubernetes相关的内容。

本文为Mirantis官网原创文章,原文链接:https://www.mirantis.com/blog/introduction-to-yaml-creating-a-kubernetes-deployment/