关注公众号“风色年代”

在之前的文章中,我们已经提到过如何使用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和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.yamlpod "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 podsNAME READY STATUS RESTARTS AGE
rss-site 2/2 Running 0 14s
至此,你可以测试以下Pod(正如我们之前文章做的),但是最后我们要创建一个Deployment,因此我们先删除这个pod以免发生名字冲突:
> kubectl delete pod rss-sitepod "rss-site" deleted
pod创建故障定位
有时候,事情并不会如我们所想的发展下去。可能你会遇到一个网络问题,或者在YAML文件用错了内容。你可能会看到以下类似以下的错误:
> kubectl get podsNAME READY STATUS RESTARTS AGE
rss-site 1/2 ErrImagePull 0 9s
在这个情况下,我们可以看到我们其中一个容器启动正常,但是另一个出现了问题。我们可以用以下方式去查看pod的详细信息并定位这个问题:
> kubectl describe pod rss-siteName: 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.yamldeployment "rss-site" created
想要查看它正在做的情况,我们可以检查Deployment的列表:
> kubectl get deploymentsNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
rss-site 2 2 2 1 7s
正如你所看到的,Kubernetes启动了所有的副本,但是只有一个可用。你可以如之前一样查看event日志对Deployment的描述:
> kubectl describe deployment rss-siteName: 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 deploymentsNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
rss-site 2 2 2 2 1m
到目前为止我们所看到的
让我们复习以下,我们基本上涵盖了三个主题:
- YAML是一种基于人类可读的基于文本的格式,通过使用名称 – 值对映射和项目列表(以及每个项目的嵌套版本)的组合,您可以轻松地指定配置类型信息。
- YAML是使用Kubernetes对象的最方便的方法,在本文中,我们考虑创建Pods和Deployments。
- 您可以通过要求Kubernetes描述它们来获取关于运行(或应该运行)对象的更多信息。
这是我们的基本YAML教程。我们将在未来几个月内处理大量与Kubernetes相关的内容。
本文为Mirantis官网原创文章,https://www.mirantis.com/blog/introduction-to-yaml-creating-a-kubernetes-deployment/

.yaml 完整格式详解:
# yaml格式的pod定义文件完整内容:
apiVersion: v1 #必选,版本号,例如v1
kind: Pod #必选,Pod
metadata: #必选,元数据
name: string #必选,Pod名称
namespace: string #必选,Pod所属的命名空间
labels: #自定义标签
- name: string #自定义标签名字
annotations: #自定义注释列表
- name: string
spec: #必选,Pod中容器的详细定义
containers: #必选,Pod中容器列表
- name: string #必选,容器名称
image: string #必选,容器的镜像名称
imagePullPolicy: [Always | Never | IfNotPresent] #获取镜像的策略 Alawys表示下载镜像 IfnotPresent表示优先使用本地镜像,否则下载镜像,Nerver表示仅使用本地镜像
command: [string] #容器的启动命令列表,如不指定,使用打包时使用的启动命令
args: [string] #容器的启动命令参数列表
workingDir: string #容器的工作目录
volumeMounts: #挂载到容器内部的存储卷配置
- name: string #引用pod定义的共享存储卷的名称,需用volumes[]部分定义的的卷名
mountPath: string #存储卷在容器内mount的绝对路径,应少于512字符
readOnly: boolean #是否为只读模式
ports: #需要暴露的端口库号列表
- name: string #端口号名称
containerPort: int #容器需要监听的端口号
hostPort: int #容器所在主机需要监听的端口号,默认与Container相同
protocol: string #端口协议,支持TCP和UDP,默认TCP
env: #容器运行前需设置的环境变量列表
- name: string #环境变量名称
value: string #环境变量的值
resources: #资源限制和请求的设置
limits: #资源限制的设置
cpu: string #Cpu的限制,单位为core数,将用于docker run --cpu-shares参数
memory: string #内存限制,单位可以为Mib/Gib,将用于docker run --memory参数
requests: #资源请求的设置
cpu: string #Cpu请求,容器启动的初始可用数量
memory: string #内存清楚,容器启动的初始可用数量
livenessProbe: #对Pod内个容器健康检查的设置,当探测无响应几次后将自动重启该容器,检查方法有exec、httpGet和tcpSocket,对一个容器只需设置其中一种方法即可
exec: #对Pod容器内检查方式设置为exec方式
command: [string] #exec方式需要制定的命令或脚本
httpGet: #对Pod内个容器健康检查方法设置为HttpGet,需要制定Path、port
path: string
port: number
host: string
scheme: string
HttpHeaders:
- name: string
value: string
tcpSocket: #对Pod内个容器健康检查方式设置为tcpSocket方式
port: number
initialDelaySeconds: 0 #容器启动完成后首次探测的时间,单位为秒
timeoutSeconds: 0 #对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
periodSeconds: 0 #对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
successThreshold: 0
failureThreshold: 0
securityContext:
privileged:false
restartPolicy: [Always | Never | OnFailure]#Pod的重启策略,Always表示一旦不管以何种方式终止运行,kubelet都将重启,OnFailure表示只有Pod以非0退出码退出才重启,Nerver表示不再重启该Pod
nodeSelector: obeject #设置NodeSelector表示将该Pod调度到包含这个label的node上,以key:value的格式指定
imagePullSecrets: #Pull镜像时使用的secret名称,以key:secretkey格式指定
- name: string
hostNetwork:false #是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
volumes: #在该pod上定义共享存储卷列表
- name: string #共享存储卷名称 (volumes类型有很多种)
emptyDir: {} #类型为emtyDir的存储卷,与Pod同生命周期的一个临时目录。为空值
hostPath: string #类型为hostPath的存储卷,表示挂载Pod所在宿主机的目录
path: string #Pod所在宿主机的目录,将被用于同期中mount的目录
secret: #类型为secret的存储卷,挂载集群与定义的secre对象到容器内部
scretname: string
items:
- key: string
path: string
configMap: #类型为configMap的存储卷,挂载预定义的configMap对象到容器内部
name: string
items:
- key: string
path: string