k8s 学习笔记
常用的kubectl命令
kubectl run kubia --image=luksa/kubia --port=8080 --generator=run/v1
--image 指定镜像
--port 是告诉kubernetes 应用监听8080端口
--generator 通常不会用到,它让kubernetes创建一个replicationController . 一般不带这个参数 ,创建的是depoly资源,deploy 在调用replicaset资源,replicaset 和replicationController很像,好像是它的升级版本有更强的selector表达能力,而且这里的--generator=run/v1 并不是创建出的rc名称是run/v1,创建出的rc就是kubia, run/v1可能是标示rc的版本吧,具体不知道,反正后面不用这个,知道有这么个东西即可
后续学习中,得知--generator=run/v1 就是告诉kubernetes需要创建一个rc来管理pod
如果是:kubectcl run dnsutils --image=tutum/dnsutils --generator=run-pod/v1 --command -- sleep infinity
这里的--generator=run-pod/v1选项就是让kubectl直接创建pod,而不需要通过replicationController之类的资源来创建。
kubectl get pods
kubectl expose rc kubia --type=loadBalancer --name kubia-http
暴露 名称为kubia的rc 为名称kubia-http 的service,并且使用loadbalnacer,会将创建kubia时 port端口8080 映射出来
kubectl get service
kubectl get repliactioncontrollers
kubectl scale rc kubia --replicas=3 扩容为3个pod
kubectl get pods -o wide 查看pod在哪个node节点上
kubectl describe pod pod-id
查看进群状态
kubectl cluster-info
使用kubectl explain 来发现可能的API对象字段,如:
kubectl explain pods
想查看某个对象下某个具体字段的使用方法
kubectl explain pod.spec
使用kubectl create 来创建pod
kubectl create -f kubia-manual.yaml
得到运行中pod的完整定义
kubectl get pod kubia-manual -o yaml
kubectl get pod kubia-manual -o json
查看应用程序日志
kubectl logs kubia-manual
当一个pod中有多个容器时
kubectl logs kubia-manual -c kubia
在不通过service外界相与pod通信,可以使用port-forward命令将短裤转发到指定pod
以下命令会将机器的本地端口8888转发到我们的kubia-manual pod的端口8080:
kubectl port-forward kubia-manual 8888:8080
在kubernetes中 标签时可以组织kubernetes所有资源。
kubernetes 中创建出来的具体的对象都是资源。某一个资源 属于某一个资源类如 pod资源类,或者对象。
查看所有pod上有什么标签
kubectl get po --show-labels
如果你想将标签作为显示列的列头可以使用-L
kubectl get po -L env,app
添加pod标签
kubectl label po kubia-manual env=test
修改pod的现有标签
kubectl label po kubia-manual env=debug --overwrite
通过标签选择器列出pod
kubectl get po -l env=debug
列出包含evn标签的pod,不管env是什么值
kubectl get po -l env
列出不含evn标签的pod
kubectl get po -l '!env' (确保是单引号)
env !=
env in (test,debug)
env not in (prod,devel)
kubernetes中调用pod到哪个节点上是无关紧要的,但由于实际情况,每台node的硬件环境不一致,所以某些情况要求将不同pod调到指定节点上运行。也可以通过label实现。
kubectl label node node-id gpu=true
将pod调用到此节点只需要在yaml中描述到
apiVersion: v1
kind: pod
metadata:
kubia-gpu
spec:
nodeSelector:
gpu: "true"
containers:
- image: luksa/kubia
name: kubia
探针:对于pod中容器要进行监控,可以用探针。
3种探针方式:
http get 返回2xx,3xx 也就是没错误4xx,5xx
tcp 套接字 能建立连接正常,反之不正常
exec 执行某个命令,成功即0,否则失败
http get的描述方法:
apiVersion: v1
kind: Pod
metadata:
name: kubia-liveneess
spec:
containers:
- image: luksa/kubia-unhealthy
name: kubia
livenessProbe:
httpGet:
path: /
port: 8080
luksa/kubia-unhealthy 这个镜像为node.js,提供web服务,其中应用中添加了让此web服务只在前5次正常返回,5次后就会返回错误。
一般错误3次后,就会重启容器,那么你想看容器错误日志就要看上次的日志因此要用 --previous参数
kuectl logs mypod --previous
使用kubectl describe po pod-id能看到具体的错误代码,以及在底部显示了容器为什么终止,--kubernetes发现容器不健康,所以终止并重新创建
默认在描述了探针,pod在启动的时候就会在刚刚启动时进行一次检测,因此最好的做法是给检测加一个初始延迟。
apiVersion: v1
kind: Pod
metadata:
name: kubia-liveneess
spec:
containers:
- image: luksa/kubia-unhealthy
name: kubia
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 15
初始延迟为15秒
replicationcontroller
选择器
模版
副本数
如果更改选择器,则会创建新的pod
如果更改pod的标签,那么也会创建新的pod进行替换,但是老的pod不会被删除
如果更改模版,比如给加入新标签,那么将在下次替换时用到新模版,这个可以用于升级pod使用
kubectl edit rc kubia 这将在你的默认编辑器中打开Replicationcontroller 的YAML配置。
使用export KUBE_EDITOR="/usr/bin/nano"设置默认编辑器
kubectl delete rc kubia 这是删除replicationcontroller命令,删除rc的同时,pod也会被删除。我们知道rc只是pod的管理器,rc创建的pod不是rc的组成部分,所以我们也可以只删除rc而不删除pod,使用--cascade=false 参数、
kubectl delete rc kubia --cascade=false
最初replicationcontroller 是用于复制和在异常时重新调度节点的唯一kubernetes组建,后来被replicaSet代替了。现在基本上见不到replicationcontroller,但是有的环境还是有可能会有,所以我们还是知道的好。
replicaset我们通常也不会直接创建,而是在创建最高层级的deployment资源时自动创建。
replicaset 与replicationcontroller的区别
replicaset 标签选择器的能力更强。
replicationcontroller只能指定标签名:标签值
replicaset 可以指定env=pro,env=devel ,也可以指定只要包含env标签就行,理解为env=*
虽说不用直接创建,为了学习我们手动创建,
定义replicaset
apiVersion: apps/v1beta2
kind: ReplicaSet
metadata:
name: kubia
spec:
replicas: 3
selector:
matchLables:
app: kubia
template:
metadata:
lables:
app:kubia
spec:
containers:
- name:kubia
image: luska/kubia
template部分和replicationController 一样
selector处不一样。replicationController 用selector ,replicaSet用 selector.matchLables选择器 更简单,更具有表达力
replicaSet 的简写 rs
kubectl get rs
kubectl describe rs
rs 的matchlables 是精确匹配,说rs支持更强表达能力的选择器,是因为rs还有matchExpressions选择器
selector:
matchExpressions:
- key: app
operator: In
values:
- kubia
operator(运算符)有四个
In
NotIn
Exists: 必须包含某个key,值不重要,values不应指定
DoesNotExists: 必须不包含某个key, values不应指定
当你的replicaSet 的选择器同时定义了matchLables和 matchExpressions ,必须两个同时满足才能是pod和选择器匹配
kubectl delete rs kubia 删除rs的同时pod也被删除,删除前建议列出pod进行确认
rc,rs 都用于在kubernetes集群上运行指定数量的pod,但他们不关心在哪个节点上运行。有种情况是让每个节点都运行某一个pod比如日志收集,和监控应用。这时候要用到另外一个控制器了DaemonSet.
DaemonSet也使用Pod模版,默认是将模版中的pod,在每个节点中创建pod,但也有特殊情况,只在某特定节点上创建pod,比如不同的硬盘类型。这可以用pod模版的nodeSelector属性指定
apiVersion: apps/v1beta2
kind: DaemonSet
metadata:
name: ssd-monitor
spec:
selector:
matchLables:
app: ssd-monitor
template:
metadata:
lables:
app: ssd-monitor
spec:
nodeSelector:
disk: ssd
containers:
- name: main
image: luksa/ssd-monitor
DaemonSet 的简写ds
kubectl get ds
同样删除ds,同时也会删除pod
rc,rs,ds都是持续运行pod也就是pod执行结束或者手动删除pod,这些控制器都会重启pod,有些时候需要让pod运行完成退出后不在重启,并且保证pod如果在运行时异常退出了可以重启的情况。这就需要用到job了。
job管理的pod,正常完成退出后不重启,当节点故障时,会按照replicaSet的pod方式,重新安排到一个新节点。也可以配置job,如果进程异常退出返回错误码时,重启容器。
apiVersion: batch/v1
kind: Job
metadata:
name: batch-job
spec:
template:
metadata:
lables:
app: batch-job
spec:
restartPolicy: onFailure Job不能使用Always为默认的重启策略
containers:
- name: main
image: luksa/batch-job
这里么有定义标签选择器,它将根据pod模版中的标签自动创建标签选择器
onFailure 当容器出错时重启容器,如果时Never将永远不重启
kubectl get jobs
kubectl get po -a 查看被删除的pod
可以配置job 按顺序运行几次
apiVersion: batch/v1
kind: Job
metadata:
name: batch-job
spec:
completions: 5
template:
... ...
也可以声明可以并行的数量
apiVersion: batch/v1
kind: Job
metadata:
name: batch-job
spec:
completions: 5 共计运行5次
parallelism: 2 可以并行2个
template:
... ...
parallelism也可以像replicaSet一样扩缩容
kubectl scale job batch-job --replicas 3
job是一次性任务,万一运行卡住了,你永远不知道它的状态,所以你可以限制它运行的时长。超过时长标记为失败,这就需要用到pod中activeDeadlineSeconds 属性来定义运行时长。
同时可以定义job 中spec.backoffLimit配置job被标记为失败之前可以重试的次数。默认为6.
job创建后,立即会运行,但有些时候我们希望定时运行job,这就需要用到kubernetes的CronJob资源
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: batch-job-every-fifteen-minutes
spec:
schedule: "0,15,30,45 * * * *" 每15分钟执行一次并且在0,15,30,45
JobTemplate:
spec:
template:
matedata:
lables:
app: periodic-batch-job
spec:
restartPolicy: OnFailure
containers:
- name: main
image: luksa/batch-job
假如我们的任务运行时间要求非常准确,不希望原本定在10:30:00运行的任务拖到10:30:16运行,可能超过15秒运行结果就有偏差了。这时可以设置startingDeadlineSeconds。
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: batch-job-every-fifteen-minutes
spec:
schedule: "0,15,30,45 * * * *" 每15分钟执行一次并且在0,15,30,45
startingDeadlineSeconds: 15
JobTemplate:
replicationController,replicaSet,DaemonSet, Job, CronJob 这几种管理pod的控制器的基本内容就这些了。高级用法碰到在了解。
kubernetes service资源
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: kubia
kubectl get svc
kubectl exec kubia-id -- curl -s http://service_ip
双横缸代表着kubectl 命令项的结束,下面的是容器内部执行的命令
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
sessionAffinity: ClientIP
sessionAffinity属性默认为None,ClientIP 是保证特定客户端产生的请求每次都指向同一个pod
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
ports:
- name: http
port: 80
targetPort: 8080
- name: https
port: 443
target: 8443
selector:
app: kubia
标签选择器应用于整个服务,不能对每个端口做单独的配置
上面是采用端口号进行映射,还有一种方式给端口命名,这样在做映射的时候直接指向名称。好处是pod的端口随便改,而不用改service的配置如下:
kind: Pod
metadata:
name: kubia
spec:
containers:
- name: kubia
ports:
- name: http
containerPort: 8080
- name: https
containerPort: 8443
apiVersion: v1
kind: Service
spec:
ports:
- name: http
port: 80
targetPort: http
- name: https
port: 443
targetPort: https
selector:
app: kubia
kubectl delete po --all 删除所有pod ,而不管pod的id
kubectl delete all --all all代表所有资源,--all代表所有资源对象
backend-database.default.svc.cluster.local
backend-database 服务名称
default 命名空间
svc.cluaster.local是在所有集群本地服务名称中使用的可配置集群域后缀
kubectl exec -ti kubia-3inly bash 运行bash很像 docker exec -ti id bash
不要ping kubernetes中创建的服务名称,这是因为服务的ip是一个虚拟的IP,只有在与服务端口结合时才有意义
endpoint资源
kubernetes service不仅可以暴露pod给外部,同样也可以把外部服务创建为服务让内部pod进行访问。服务并不是和pod直接相连的,有一种资源-endpoint介于两者之间。
endpoint资源就是暴露一个服务的IP地址和端口的列表,endpoint资源和其他kubernetes资源一样,所以可以使用kubectl info 来获取它的基本信息
kubectl describe svc kubia 执行此命令能看到endpoint资源
kubectl get endpoints kubia
我知道在创建service时定义了selector pod选择器,但在重定向传入连接时不会直接使用它。
选择器用于构建IP和端口列表,然后存储在EndPoint资源中。当客户端连接到服务时,服务代理选择这些IP和端口对中的一个,并将传入连接重定向到改位置监听的服务器。
EndPoint是一个单独的资源,而不是service的属性,所以我们可以单独的创建endpoint资源对象。
我们在创建service时不声明pod选择器就不会创建endpoint
apiVersion: v1
kind: Service
metadata:
name: external-service
spec:
ports:
- port: 80
这里并没有定义selector
下面我们手动创建endpoint
apiVersion: v1
kind: Endpoints
metadata:
name: external-service 这里的名称一定和service的一致
subsets:
- addresses:
- ip: 1.1.1.1
- ip: 2.2.2.2
ports:
- port: 80 这里的port是endpoint的目标端口,是service中的targetPort
以上就做了一个将外部服务通过service让内部pod可以访问。
还有一种简单的方式,给外部服务创建一个别名服务。
apiVersion: v1
kind: Service
matedata:
name: external-service
spec:
type: ExternalName 代码的type被设置成ExternalName
externalName: someapi.somecompany.com 实际服务的完全限定名
ports:
- port: 80
内部就可以使用external-service来访问服务了
kubectl get po --all-namespaces 非常好用
kubernetes关于pod挂载卷的知识
首先要知道卷是pod资源的属性,pv,pvc是单独的资源。pod 资源的volumes属性有多种type,其中就包含有挂载pvc的类型。这也帮我理清了之间的关系。
pv一般是系统管理员做的
pvc 是一般k8s用户声明的,大概意思就是说我需要一个 什么权限的,多少存储空间的卷,然后k8s api收到这个请求就去找pv资源对象,如果两者相匹配,那么pv就和pvc绑定了。
从这里我们也想到了,pv如果是手动创建的话,那就麻烦大了。几个,几十个还好说,上万个,管理员该如何创建这么多。所以才有了动态创建pv的需求。这就引出另外一个资源 storageClass ,这个资源声明后端挂载什么样的存储服务,比如nfs,chef等,有了这个一般用户在定义pvc的时候,在提出存储空间和读写权限的同时声明用那个storageClass了,如下:
apiVersion: v1
kind: PersistentVolumeClaim
metadata: mysql-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: managed-nfs-storage (注意这里)
resources:
requests:
storage: 5Gi
以上便是pvc使用storageClass资源对象创建pv,pvc的绑定了
api收到对storageClass声明后,就会调用这个storageClass的生产者进行生产。
我们就会想到,在创建storageClass资源对象的时候肯定会指定生产者。
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: example-nfs
provisioner: example.com/nfs 这里指定生产者,其中example.com/nfs只是一个标签,你在deploymend里定义什么这里就写什么。
mountOptions:
- vers=4.1
那么问题又来了。在k8s中生产者组件不支持nfs,所以才会安装第三方组件。第三方组件的安装就需要创建相关的rbac的角色和账户。第三方组件是用deploymend的资源托管相关组件pod的。
那么通过deploy部署的pod怎么就是provisioner了?这个我不清楚,后面学习后在总结吧。
回到正题上。
volumes 的4种
1. emptyDir
2. gitRepo
3. hostpath
4.PersistentVolumeClaim
5.configMap,secret
6. 各种云平台的存储磁盘卷如google的gce,aws的ebs,azure的azureDisk
其实4只是一个概括,nfs,chef 这些网络存储通通可以单独来使用。但我觉得实际使用中还是讲这些网络存储转化成pv,pvc
从简单开始学习
emptyDir 两种应用场景: 1. 同一个pod中,各个容器间共享文件。 2. 当程序对大数据处理时内存空间不够时临时写入文件(当然也可以使用宿主主机的内存)
例子:
apiVersion: v1
kind: Pod
metadata:
name: fortune
spec:
containers:
- image: luksa/fortune
name: html-generator
volumeMounts:
- name: html
mountPath: /var/htdocs
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPaht: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
emptyDir: {} (为{}表示使用节点服务器的文件系统)
- name: html-2
emptyDir:
medium: Memory (使用节点服务器的内存)
以上就是emptyDir的用法。gitRepo其实是依赖于emptyDir的,你可以把它理解成,声明了一个emptyDir然后在把gitrepo下载填充到emptyDir
例子:
apiVersion: v1
kind: Pod
metadata:
name: gitrepo-volume-pod
spec:
containers:
- image: nginx: alpine
name: web-nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
gitRepo:
revision: master
directory: . (这个.很重要,表示在当前emptyDir目录下不然就会创建一个kubia-website-example目录)
以上就是gitRepo的用法,其中有个问题就是pod中的gitrepo并不会及时更新。如果想要及时更新需要用到sidecar ,加入到pod中,在Docker Hub搜索 “git ryc”获取相关镜像。
还有一个情况不得不使用sidecar container 。gitrepo 不支持连接私有库,也就是不能是ssh密钥连接,也不可以有用户名和密码。这时候就需要使用支持验证连接的sidecar container来实现了。具体怎么使用,用到的时候在研究吧.
至此gitRepo卷类型算是简单介绍了,下面学习hostpath
大多数时候pod应该忽略他们的主机节点,因此他们也不需要访问节点文件系统上的文件。但是某些系统级别的pod(通常由DaemonSet管理)确实需要读取节点的文件或使用节点文件系统来访问节设备,这时候就需要用到hostPath
hostPath 算是第一个持久化存储卷了,但是这个很少用到,因为这个卷只能在某一单一节点上,pod重启后很可能在另外一个节点上。当然可以使用NodeSelector但这样看起来也不高明。所以建议使用网络存储。hostPath过
接下来是网络存储和云平台提供的存储磁盘卷。这两种在用到的时候找相关的属性进行配置即可。也没什么要注意的,实际应用场景用到最多的持久存储是pv,pvc,storageClass
configmap
kubectl create configmap fortune-config --from-literal=sleep-interval=25
一般configmap包含多个映射条目所以创建时可以多次使用--from-literal参数
kubectl create conigmap fortune-config --from-literal=foo=bar --from-literal=bar=baz --from-literal=one=two
kubectl get configmap fortune-config -o yaml
configmap同样可以存储粗力度的配置数据,比如完整的配置文件。kubectl create configmap 命令支持从硬盘上读取文件,并将文件内容单独存储为ConfigMap中的条目:
kubectl create configmap my-config --from-file=config-file.conf
运行此命令,kubectl 会在当前目录下找config-file.conf文件,并将文件内容存储在configmap中以config-file.conf 为键名的条目下。当然也可以手动指定键名
kubectl create configmap my-config --from-file=customkey=config-file.conf
kubectl create configmap my-config --from-file=/path/to/dir
这时候,kubectl会为文件中每个文件单独创建条目,仅限文件名可作为合法ConfigMap键名的文件。
当让也可以将上面的参数混合使用
configmap设置完成了,如何将映射的值传递给pod的容器?三种方法
一,设置环境变量,例子如下:
apiVersion: v1
kind: Pod
metadata:
name: fortune-env-from-configmap
spec:
containers:
- image: luksa/fortune:env
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
很好奇当pod 容器中引用不存在的configmap会发生什么?
pod会尝试运行所有容器,只有这个容器启动失败,其他正常(假如有多个容器),当你事后创建处这个configmap,无需重启pod,容器就会成功。当然也可以引用为可选的,设置configMapKeyRef.optional: true即可,这样即使ConfigMap不存在,容器也能正常启动。
如果configmap中条目很多,用env属性列出麻烦且容易出错。那么有没有办法一次导入呢。用envFrom, 例如configmap中有三个条目FOO、BAR和FOO-BAR
spec:
containers:
- image: some-image
envFrom:
- prefix: CONFIG_ 所有环境变量均包含前缀CONFIG_ ,不设置就将引用configmap中的键名
configMapRef:
name: my-config-map
如此,容器中多出两个变量 CONFIG_FOO 、CONFIG_BAR
为什么是两个,因为另外一个FOO-BAR包含破折号,不是一个合法的环境变量名称。被忽略了,所以我们应该注意创建configmap时 不要使用-
上面我们学习了如何将configmap中的条目以变量的形式传入到容器中
那么如何将configmap中的条目作为容器运行的参数args呢?例子:
apiVersion: v1
kind: Pod
metadata:
name: fortune-env-from-configmap
spec:
containers:
- image: luksa/fortune:env
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
args: ["$(INTERVAL)"]
环境变量和参数都适合于变量值较短的场景。configmap是可以包含完整配置文件内容的,当你想要将其暴露给容器时,可以使用上一章中提到的卷的类型。configmap卷会将ConfigMap中的每个条目均暴露成一个文件。运行在容器的进程通过读取文件内容获得对应的条目值。
configmap-files为一个目录
kubectl create configmap fortune-config --from-file=configmap-files
例子
apiVersion: v1
kind: Pod
metadata:
name: fortune-configmap-volume
spec:
containers:
- image: nginx:alpine
name: web-server
volumeMounts:
...
- name: config
mountPaht: /etc/nginx/config.d
readOnly: true
....
volumes:
...
- name: config
configMap:
name: fortune-config
...
一种特殊情况,当你只想暴露configmap-files目录下的某一个配置文件,该如何做:
volumes:
- name: config
configmap:
name: fortune-config
items:
- key: my-nginx-config.conf
path: gzip.conf
这样配置后,挂载fortune-config 卷后,就只有my-nginx-config.conf 并且挂载后的名称为gzip.conf
另一种特殊情况,我们通过上述挂载configmap卷后会发现,被挂载的目录之前的文件都被隐藏掉了。那么如果你需求不想隐藏之前的文件,该如何做:
spec:
containers:
- image: some/image
volumeMounts:
- name: myvolume
mountPath: /etc/someconfig.conf 挂载到指定的某一个文件,而不是某个文件夹
subPath: myconfig.conf 挂载指定的条目,而不是完整的卷
为configMap卷中的文件设置权限
volumes:
- name: config
configmap:
name: fortune-config
defaultMode: "6600"
更新应用配置且不重启应用程序
使用环境变量或命令行参数作为配置源的弊端在于无法在进程运行时更新配置。将ConfigMap暴露为卷可以达到配置热更新的效果,无需重新创建pod或重启容器。
ConfigMap被更新后,卷中引用它的文件也会相应更新,进程发现文件被改变后进行重载。kubernetes同样支持文件更新后手动通知容器。但要注意的是,更新configmap之后对应文件的更新会相当耗时。
我们使用kubectl edit configmap fortune-config 来更改某一个值。
然后执行
kubectl exec fortune-configmap-volume -c web-server -- cat /etc/nginx/config.d/my-nginx-config.conf
查看文件是不是被修改了,如果没有稍等一会再查看。在确认更改后,我们来手动通知容器进行重载。
kubectl exec fortune-configmap-volume -c web-server -- nginx -s reload
你可能会疑惑 Kubernetes更新完configmap卷中的所有文件之前(所有的文件被更新而不是一部分),应用是否会监听到文件编号并主动进行重载。答案是不会,会在所有的文件更新后,一次性更新到pod容器中。kubernetes通过富豪链接做到这一点的。
kubectl exec -ti fortune-configmap-volume -c web-server -- ls -lA /etc/nginx/config.d
..4989_09_04_15_06.028485
..data -> ..4989_09_04_15_06.028485
my-nginx-config.conf -> ..data/my-nginx-config.conf
sleep-interval -> ..data/sleep-interval
可以看到,被挂载configMap卷中的文件是..data文件夹中文件的符号链接,而..data又是连接到 ..4989的符号链接,每当ConfigMap被更新后,Kubernetes 会创建一个这样的文件夹,卸任所有文件并重新将符号..data链接至新文件夹,通过这种方式可以一次性修改所有文件。
如果挂载configmap中的某一个文件,而不是文件夹,configmap更新之后对应的文件不会被更新。如果现在你需要挂载单个文件且在修改Configmap后想自动更新,可以将该卷挂载到其他文件夹,然后做一个软链接。
至于如何实现应用重载配置 ,需要应用自己实现了。
configmap都是以名文存储的,有些信息比较敏感,就需要用秘文存储了。
这就需要使用kubernetes 的secret资源了。
首先有一个情况我们需要了解
使用kubectl describe pod pod-id 查看任何一个pod你都会发现默认有挂载一个secret卷。
Volumes:
default-token-ps7ff:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-ps7ff
Optional: false
这个卷里的内容我们可以使用kubectl describe secrets查看
# kubectl describe secrets default-token-ps7ff
Name: default-token-ps7ff
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name: default
kubernetes.io/service-account.uid: 6efa7f7c-6a61-11e9-bfdb-0a382f97318e
Type: kubernetes.io/service-account-token
Data
====
ca.crt: 1359 bytes
namespace: 7 bytes
token: eyJhbGciOiJSUzI1NiIsImtp...
可以看出这个Secret包含是哪个条目ca.crt 、namespace与token
包含了从pod内部安全访问Kubernetes API服务器所需的全部信息。尽管我们希望做到应用对kubernetes无感知,但是直连Kubernetes没有其他方法,你只能使用secret卷提供的文件来访问kubernetes API。
使用kubectl describe pod命令显示secret卷北挂载的位置:
mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-ps7ff (ro)
注意: default-token Secret默认会被挂载至每个容器。可以通过设置pod定义中的automountServiceAccountToken字段为false,或者设置pod使用的服务账户中的相同字段为false来关闭这种默认行为。
查看容器中挂载了哪些条目
# kubectl exec nginx-dnm9n -- ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt
namespace
token
创建Secret
openssl genrsa -out https.key 2048
openssl req -new -x509 -key https.key -out https.cert -days 3650 -subj /CN=www.kubia-example.com
# kubectl create secret generic fortune-https --from-file=https.key --from-file=https.cert --from-file=foo
secret/fortune-https created
也可以使用 --from-file=fortune-https 囊括这个歌文件夹中的文件
# kubectl get secret fortune-https -o yaml
# kubectl describe secret fortune-https
对比configmap与secret
secret与configMap有比较大的区别,这也是为何kubernetes开发者们在支持了Secret一段时间之后会选择创建ConfigMap。穿件的Secret的YAML格式定义显示
# kubectl get secret fortune-https -o yaml
apiVersion: v1
data:
foo: YmFyCg==
https.cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFekNDQWZ1Z0F3SUJBZ0lKQU96Y00rNzI3RWJHTUEwR0NTcUdTSWIzRFFFQkN3VUFNQ0F4SGpBY0JnTlYKQkF
https.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBeFYvUVJiazJiRm8zRmdZdWpaTWxPVGg3MUxpY3AyUS9pL2pib2E1SExlUlpSTDBi
kind: Secret
. . .
将其与CoinfigMap的Yaml格式定义做对比
apiVersion: v1
data:
bar: baz
foo: bar
one: two
kind: ConfigMap
注意到Secret条目的内容会被以Base64个市编译,而ConfigMap直接以纯文本展示。这种区别导致在处理YAML和JSON格式的Secret时会稍许有些麻烦,需要在设置和读取相关条目时对内容进行编解码。
这个具体使用中是这样的,
比如你现在想把一个配置文件加入到Secret中,那么你首先将配置文件中的内容通过BASE64进行编码后才能作为条目。
当然你会问难道kubernetes不提供base64编码?提供,只能对字符串,不能接受文件。如下
kund: Secret
apiVersion: v1
stringData:
foo: plain text
data:
https.cert: lksjkaldjldasjladgjsjl...
https.key: lsiodjsdlfjahcdo...
创建后使用kubectl get secret -o yaml会看到stringData字段中的所有条目会被Base64编码后展示在data字段下。所以stringData字段是只写不可读的。
如何在pod中使用Secret
apiVersion: v1
kind: Pod
metadata:
name: fortune-https
spec:
containers:
- image: luksa/fortune:env
name: html-generator
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
volumeMounts:
- name: html
mountPaht: /var/htdocs
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
- name: config
mountPath: /etc/nginx/conf.d
readOnly: true
- name: certs
mountPath: /etc/nginx/certs/
readOnly: true
ports:
- containerPort: 80
- containerPort: 443
volumes:
- name: html
emptyDir: {}
- name: config
configmap:
name: fortune-config
items:
- key: my-nginx-config.conf
path: https.conf
- name: certs
secret:
secretname: fortune-https
简单点的实例:
apiVersion: v1
kind: Pod
metadata:
name: fortune-https
spec:
containers:
- image: nginx:alpine
name: web-server
volumeMounts:
- name: certs
mountPath: /etc/nginx/certs/
readOnly: true
ports:
- containerPort: 80
- containerPort: 443
volumes:
- name: certs
secret:
secretName: fortune-https
当然也可以将secret条目暴露为环境变量。但不建议这么做,应用通常会在错误报告时转储环境变量,或者是启动时打印在应用日志中,无意中就暴露Secret信息。另外,子进程会继承父进程的所有环境变量,如果是通过第三方二进制程序启动应用,你并不知道它使用敏感数据做了什么。所以不建议用环境变量,建议使用secret卷的形式挂载到pod.
env:
- name: FOO_SECRET
valueFrom:
secretKeyRef:
name: fortune-https
key: foo
学会了secret,接下来就有一个比较常用的secret实际应用,dockerhub
kubectl create secret docker-registry mydockerhubsecret --docker-username=myusername --docker-password=mypassword --docker-email=my.email@provider.com
使用:
apiVersion: v1
kind: Pod
metadata:
name: private-pod
spec:
imagePullSecrets:
- name: mydockerhubsecret
containers:
- image: username/private:tag
name: main
假设系统通常运行大量Pod,你可能会好奇是否需要为每个Pod都添加相同的镜像拉取Secret.并不是,可以通过添加Secret至ServiceAccount 使所有pod都能自动添加上镜像拉取的Secret.
Deployment 声明式地升级应用
现在你已经知道如何将应用程序组件打包进容器,将他们分组到pod中,并为它们提供临时或者持久存储,将密钥或配置文件注入,并可以使pod之间互相通信。这就是微服务化:如何将一个大规模系统拆分成哥哥独立运行的组件。之后,你将会需要升级你的应用程序。如何升级再kubernetes集群中运行的程序,以及kubernetes如何帮助你实现真正的零停机升级过程。升级操作可以通过使用replicationController 或者 replicaSet实现,但Kubernetes提供了另一种基于ReplicaSet的资源Deployment,并支持声明式地更新应用程序。
看一个简单的例子:
现有一个应用 ,版本为V1 ,运行在Kubernetes的pod中,现在应用的镜像有更新,标记为v2,那么如何将新版本运行的pod替换掉V1版本的pod.
有以下两种方法更新pod:
1. 直接删除所有现有的pod,然后创建新pod
2. 新创建一组pod,等待运行后删除旧pod. 可以一次性创建,一次性删除,也可以一部分一部分操作。
这两种各有优缺点:第一种方法将会导致应用程序在一定的时间内不可用。第二种方法会导致新旧版本同时在线,如果对统一个数据的处理方式不一样,将会给系统带来数据损坏。
暂且不管优缺点,先来看看如何实现。
我们用replicationController来托管pod v1,可以直接通过将pod模版修改为v2,然后再删除旧pod,这时候rc就会按照新模版创建pod.
如果你可以接受短暂的服务不可用,那这是最简单更新pod的方法。
接下来我们用第二种方法。首先要保持两个版本同时在线,所以要双倍的硬件资源。
前提我们是用service来暴露pod的。保持rc v1不变,然后创建 rc v2 ,rc v2的pod模版选择新镜像标签V2. 创建rc v2 ,rc v2将运行pod v2 。等待pod v2运行正常后,我们使用 kubectl set selector 命令来修改service的pod选择器。
这种一次性从v1切到v2的方法即为蓝绿部署,主要问题就是硬件资源要两倍。如果我们不是一次切换,而是一点点切,资源将不需要两倍。比如你创建rc v2,成功后,缩容 rc v1 ,更改service的pod选择器。然后再扩容rc v2,在缩容 v1 这就是滚动更新。那么这个就要求你的应用允许存在两个不同版本,具体要根据实际需求来操作了。
以上第二种方法的两种情况,我们大概知道是怎么回事了。但我们在实际操作中,要创建两个rc,并且要修改service的pod selector. 这要是用滚动更新,pod副本越多,我们手动操作的次数就越多,手动操作越多越容易出现问题。那么问题来了,难道kubernetes没有提供自动完成这些操作的方法吗?答案是提供了。k8s中使用kubectl rolling-update kubia-v1 kubia-v2 --image=luksa/kubia:v2
我们来通过一个例子了解下:
用rc创建一个应用v1 ,并使用service的 loadbalancer将服务对外暴露。
apiVersion: v1
kind: ReplicationController
metadata:
name: kubia-v1
spec:
replicas: 3
template:
metadata:
name: kubia
labels:
app: kubia
spec:
containers:
- image: luksa/kubia:v1
name: nodejs
---
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
type: LoadBalancer
selector:
app: kubia
ports:
- port: 80
targetPort: 8080
查看loaderbalancer的IP
#kubectl get svc kubia
访问测试
# while true; do curl http://IP; done
this is v1 running in pod 主机名
接下来用v2版本升级,this is v2 running in pod 主机名
kubectl rolling-update kubia-v1 kubia-v2 --image=luksa/kubia:v2
使用 kubia v2版本应用替换运行着kubia-v1 的replicationController,将新的复制控制器命名为kubia-v2,并使用luksa/kubia:v2作为容器镜像。
kubectl 通过复制kubia-v1 的replicationController并在其pod模版中改变镜像版本。如果仔细观察控制器的标签选择器,会阿贤它也被做了修改。它不仅包含一个简单的app=kubia标签,而且还包含一个额外的deployment标签,为了由这个ReplicationController管理,pod必须具备这个标签。这也是为了避免使用新的和旧的RC来管理同一组Pod.但是即使新创建的pod添加了deployment标签,pod中还是有app=kubia标签,所以不仅新的RC要加deployment标签,旧的RC同样也要加上deployment标签,标签的值为 一个镜像hash(先这样理解)。要保证旧的RC添加deployment标签后依然可以管理之前创建的pod,因此也要修改旧pod,进行添加标签,而实际上正是在修改旧RC之前,kubectl修改了旧pod的标签:
kubectl get po --show-labels 进行查看标签
设置完成后,就开始执行更新操作了,过程就是我们上面描述的滚动更新过程。
为什么 kubectl rolling-update已经过时
我们可以在使用rolling-update命令的时候添加 --v 6 来提供日志级别,使得所有kubectl 发起的到API服务器的请求都会被输出。
你会看到一个put请求:
/api/v1/namespace/default/replicationcontrollers/kubia-v1
它是表示kubia-v1 ReplicationController资源的RESTful URL.这些请求减少了RC的副本数,这表明伸缩的请求是由kubectl 客户端执行的,而不是kubernetes master执行的。那么当kubectl执行升级时失去了网络连接,升级过程就会中断。对于中断后的结果处理起来将很麻烦。所以我们想尽量把这个过程让master负责。这就引出了DeployMent资源。
Deployment是一个高阶资源,replicationController和replicaSet都被认为是底层的概念。当创建一个Deployment时,ReplicaSet资源就会被创建,实际的pod是由Deployment的Replicaset创建和管理的,而不是由Deployment直接创建和管理的。
接下来我们创建一个简单的deployment,将上面的replicationController稍作修改:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: kubia
spec:
replicas: 3
template:
metadata:
name: kubia
labels:
app: kubia
spec:
containers:
- image: luksa/kubia:v1
name: nodejs
因为之前RC只维护和管理了一个特定的版本的pod,并需要命名为kubia-v1,而一个deployment资源高于版本本身。可以同时管理多个版本的pod,所以在命名时不需要指定应用的版本号。
kubectl create -f kubia-deployment-v1.yaml --record
使用 --record选项会记录历史版本号,在之后操作中非常有用。
kubectl get deployment kubia
kubectl describe deployment kubia
还有一个命令,专门用于查看部署状态:
kubectl rollout status deployment kubia
查看pod,会发现使用deployment资源部署的pod名称有一定的规律,
deployment的名字 + replicaset pod模版的hash + 随机数, 如:
kubia-15098375-otnsd
kubia-15098375-djc6s
而rc创建出来的名称是 : rc名称 + 随机数
kubia-v1-kgysg
可以通过查看replicaSet来确认pod模版hash
kubectl get replicasets
kubia-15098375 ...
deployment正是根据pod模版的hash值,对给定版本的pod模版创建相同的(或使用已有的)ReplicaSet.
Deployment的高明之处
有不同的更新策略
Recreate 策略在删除旧的Pod之后才开始创建新的Pod。如果不允许多个版本共存,使用这个,但会有短暂的不可用。
RollingUpdate 策略会渐进地删除旧的pod,于此同时创建新的pod.这是deployment默认使用的策略。如果支持多个版本共存,推荐使用这个。
我们来演示下deployment滚动升级的过程。
在演示之前我们先减慢滚动升级的速度,以方便我们观察
kubectl path deployment kubia -p '{"spec": {"minReadySeconds": 10} }'
使用path对于修改单个或少量资源属性非常有用,不需要在通过编辑器编辑,使用minReadySeconds 配置正常检查后多少时间才属于正常。
注:使用patch命令更改Deployment的自有属性,并不会导致pod的任何更新,因为pod模版并没有被修改。更改其他Deployment的属性,比如所需要的副本数或部署策略,也不会触发滚动升级,现有运行中的pod也不会受影响。
触发滚动升级
kubectl set image deployment kubia nodejs=luksa/kubia:v2
执行完成后pod模版的镜像会被更改为luksa/kubia:v2
deployment的优点
使用控制器接管整个升级过程,不用担心网络中断。
仅仅更改pod模版即可。
注:如果Deployment中的pod模版引用了一个ConfigMap(或secret),那么更改ConfigMap字眼本身将不会触发升级操作。如果真的需要修改应用程序的配置并想触发更新的话,可以通过创建一个新的ConfigMap并修改pod模版引用新的ConfigMap.
Deployment背后完成的升级过程和kubectl rolling-update命令相似。
一个新的ReplicaSet会被创建然后慢慢扩容,同时之前版本的Replicaset会慢慢缩容至0
deployment的另外一个好处是可以回滚
kubectl rollout undo deployment kubia
deployment会被回滚到上一个版本
undo命令也可以在滚动升级过程中运行,并直接停止滚动升级。在升级过程中一创建的pod会被删除并被老版本的pod替代。
显示deployment的滚动升级历史
kubectl rollout history deployment kubia
reversion change-cause
2 kubectl set image deployment kubia nodejs=luksa/kubia:v2
3 kubectl set image deployment kubia nodejs=luksa/kubia:v3
还记得创建Deployment的时候--record 参数吗?如果不给这个参数,版本历史中的Change-cause这一栏会空。这也会使用户很难辨别每次的版本做了哪些修改。
回滚到一个特定的Deployment版本
kubectl rollout undo deployment kubia --to-reversion=1
这些历史版本的replicaset都用特定的版本号保存Deployment的完整的信息,所以不应该手动删除ReplicaSet。如果删除会丢失Deployment的历史版本记录而导致无法回滚。
ReplicaSet默认保留最近的两个版本,可以通过制定Deployment的reversionHistoryLimit属性来限制历史版本数量。apps/v1beta2 版本的Deployment默认值为10.
控制滚动更新的速率
maxSurge 最多超过期望副本数多少个pod,默认为25%,可以设置为整数
maxUanavailable 最多允许期望副本数内多少个pod为不可用状态。
看图比较容易理解
暂停滚动更新:
kubectl rollout pause deployment kubia
恢复滚动更新:
kubectl rollout resume deployment kubia
阻止出错版本的滚动升级
使用minReadySeconds属性指定至少要成功运行多久之后,才能将其视为可用。在pod可用之前,滚动升级的过程不会继续。是由maxSurge属性和maxUanavailable属性决定。
例子:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: kubia
spec:
replicas:3
minReadySeconds: 10
strategy:
rollingUpdate:
maxSurge: 1
maxUnavilable: 0
type: RollingUpdate
template:
metadata:
name: kubia
labels:
app: kubia
spec:
containers:
- image: luksa/kubia:v3
name: nodejs
readinessProbe:
periodSeconds: 1
httpGet:
path: /
port: 8080
kubectl apply -f kubia-deployment-v3-with-readinesscheck.yaml
kubectl rollout status deployment kubia
就绪探针是如何阻止出错版本的滚动升级的
首先luksa/kubia:v3镜像应用前5个应用是正常反馈,后面会500状态返回。因此pod会从service的endpoint中移除。当执行curl时,pod已经被标记为未就绪。这就解释了为什么请求不会到新的pod上了。
使用kubectl rollout status deployment kubia查看状体,发现只显示一个新副本启动,之后滚动升级便没有进行下去,因为新的pod一直处于不可用状态。即使变为就绪状态后,也至少需要保持10秒,才是真正可用。在这之前滚动升级过程将不再创建任何新的pod,因为当前maxUnavailable属性设置为0,所以不会删除任何原始的pod。如果没有定义minReadySeconds,一旦有一次就绪探针调用成功,便会认为新的pod已经处于可用状态。因此最好适当的设置minReadySeconds.另外就绪探针默认间隔为1秒。
deployment资源介绍完结。
复制有状态的Pod
replicaSet通过一个pod模版创建多个pod副本。这些副本除了它们的名字和IP地址不同外,没有被的差异。如果pod模版里描述了一个关联到特定持久卷声明的数据卷,那么ReplicaSet的所有副本都将共享这个持久卷声明,也就是绑定到同一个持久卷声明。
因为是在pod模版里关联持久卷声明的,又会依据pod模版创建多个副本,则不能对每个副本都指定独立的持久卷声明。所以也不能通过一个ReplicaSet来运行一个每个实例都需要独立存储的分布式数据存储服务,至少通过单个ReplicaSet是做不到的。老实说,之前你学习到的所有API对象都不能提供这样的数据存储服务,还需要一个其他的对象--StatefulSet
我们先看不使用StatefulSet的情况下有没有方法实现多个副本有自己的持久卷声明。
三种取巧的方法。
第一种方法,不使用ReplicaSet,使用Pod创建多个pod,每个pod都有独立的持久卷声明。需要手动创建它们,当有的pod消失后(节点故障),需要手动创建它们。因此不是一个好方法。
第二种方法,多个replicaSet ,每个rs只有一个pod副本。但这看起来很笨重,而且没办法扩缩容。
第三种方法,使用同一个ReplicaSet,大家也都挂载同一个持久卷声明,应用内部做好互斥,创建多个data数据目录,每一个pod用一个标记为在用,后面应用不能选被标记为在用的目录。这样做很难保证协调的一点没问题,同时大家用同一个持久卷,读写io将成为整个应用的瓶颈。
除了上面的存储需求,集群应用也会要求每一个实例拥有生命周期内唯一标识。pod可以随时被删掉,然后被新的pod替代。当一个ReplicaSet中的pod被替换时,尽管新的pod也可能使用被删除pod数据卷中的数据,但它却是拥有全新主机名和IP的崭新pod.在一些应用中,当启动的实例拥有完全新的网络标识,但还使用旧实例的数据时,很可能引起问题,比如etcd存储服务。
当然也可以创建多个service ,每一个replicaset对应一个service,那么一样很笨重,且显得很低级。辛运的是,Kubernetes为我们提供了这类需求的完美解决方案--StatefulSet.
了解StatefulSet
可以创建一个StatefulSet资源代替ReplicaSet来运行这类pod.它们是专门定制的一类应用,这类应用中每一个实例都是不可替代的。
码农小明