在Kubernetes中,Pod携带的信息即为丰富,比如调度,镜像相关的字段,但是除了这些,还有些特殊的小点需要我们了解,从而加深对Pod的理解
比如,我们要讲的特殊的Volume,叫做Projected Volume,投射数据卷
在Kubernetes中,有几种特殊的Volume,其意义,就是为了存放容器内的数据,也不是为了进行容器和宿主机之间的数据交换,这些Volume,就是为容器提供预先定义好的数据,就好比是从Kubernetes投射进入容器中的,这就是Projected Volume的含义
到现在为止,常见的Projected Volume一共有四种
Secret
ConfigMap
Download API
ServiceAccountToken
首先是Secret,其作用是将Pod中想要访问的加密数据,放在Etcd中,这样,就可以在Pod中以挂载Volume的方式,访问到Secret里的信息了
Serect的典型使用场景,就是在存放数据库的登录信息
apiVersion: v1 kind: Pod metadata: name: test-projected-volume spec: containers: – name: test-secret-volume image: busybox args: – sleep – “86400” volumeMounts: – name: mysql-cred mountPath: “/projected-volume” readOnly: true volumes: – name: mysql-cred projected: sources: – secret: name: user – secret: name: pass |
定义了一个容器,挂载的Volume,不是常见的emptyDir或者hostPath类型,而是projected类型,这个volume的数据来源,是user和pass的Secret对象,对应的分别是数据库的用户名和密码
而这两个projected volume的来源,正是以Secret对象的方式交给Kubernetes保存的,完成这个操作的指令
$ cat ./username.txt
admin
$ cat ./password.txt
c1oudc0w!
$ kubectl create secret generic user –from-file=./username.txt
$ kubectl create secret generic pass –from-file=./password.txt
这样,就创建了两个secret对象,我们可以直接查看一下
kubectl get secrets
当然,还可以使用YAML的方式来创建Secret对象
apiVersion: v1 kind: Secret metadata: name: mysecret type: Opaque data: user: YWRtaW4= pass: MWYyZDFlMmU2N2Rm |
通过其编写YAML文件创建出的Secret对象只有一个,但是data字段,却利用key-value保存了两份Secret数据,其中user是第一份数据的key pass是第二份数据的key
这两个数据,是我们经过Base64转码得到的,这也是K8s要求的
当然,简单的转码并不能满足我们的加密需求,一般还需要开启专门的加密插件,先不说这个
然后我们创建最开始的挂载了Volume的这个Pod
kubectl create -f test-projected-volume.yaml
然后我们尝试获取到这个文本对象
当Pod变成Running状态之后了,我们验证一下Secret对象是不是已经在容器了
kubectl exec -it test-projected-volume — /bin/sh
/ # ls /projected-volume/
password.txt username.txt
/ # cd /projected-volume/
/projected-volume # ls
password.txt username.txt
/projected-volume # cat password.txt
wow
/projected-volume # cat username.txt
admin
/projected-volume #
这样,就是我们以文件的形式,展示了对应的用户名和密码
这些实际文件中的数据,就是保存在Etcd对应的用户和密码,以文件的形式保存在了这里,这个文件的名字,就是
kubectl create secret 指令的key或者secret对象的data字段的key
而且,一旦是以挂载的方式进入容器的secret,如果Etcd的数据被更新,那么Volume内的文件也会被更新,这是kubelet组件在定期的维护这些volume
这个更新可能会有一定延时,在编写应用程序时候,发起数据库连接的代码处理好
然后其次是ConfigMap,和Secret的区别在于,ConfigMap是提供不需要进行加密,应用所需要的配置信息,ConfigMap的用法和Secret完全相同,可以使用kubectl create configmap 从文件或者目录创建ConfigMap,可以直接编写对应的YAML文件
假设我们有一个properties类型的对象
cat ./hbase-1.3.5/conf/log4j.properties # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # “License”); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an “AS IS” BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Define some default values that can be overridden by system properties hbase.root.logger=INFO,console hbase.security.logger=INFO,console hbase.log.dir=. hbase.log.file=hbase.log # Define the root logger to the system property “hbase.root.logger”. log4j.rootLogger=${hbase.root.logger} # Logging Threshold log4j.threshold=ALL # # Daily Rolling File Appender # log4j.appender.DRFA=org.apache.log4j.DailyRollingFileAppender log4j.appender.DRFA.File=${hbase.log.dir}/${hbase.log.file} # Rollver at midnight log4j.appender.DRFA.DatePattern=.yyyy-MM-dd # 30-day backup #log4j.appender.DRFA.MaxBackupIndex=30 log4j.appender.DRFA.layout=org.apache.log4j.PatternLayout # Pattern format: Date LogLevel LoggerName LogMessage log4j.appender.DRFA.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2}: %m%n # Rolling File Appender properties hbase.log.maxfilesize=256MB hbase.log.maxbackupindex=20 # Rolling File Appender log4j.appender.RFA=org.apache.log4j.RollingFileAppender log4j.appender.RFA.File=${hbase.log.dir}/${hbase.log.file} log4j.appender.RFA.MaxFileSize=${hbase.log.maxfilesize} log4j.appender.RFA.MaxBackupIndex=${hbase.log.maxbackupindex} log4j.appender.RFA.layout=org.apache.log4j.PatternLayout log4j.appender.RFA.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2}: %m%n # # Security audit appender # hbase.security.log.file=SecurityAuth.audit hbase.security.log.maxfilesize=256MB hbase.security.log.maxbackupindex=20 log4j.appender.RFAS=org.apache.log4j.RollingFileAppender log4j.appender.RFAS.File=${hbase.log.dir}/${hbase.security.log.file} log4j.appender.RFAS.MaxFileSize=${hbase.security.log.maxfilesize} log4j.appender.RFAS.MaxBackupIndex=${hbase.security.log.maxbackupindex} log4j.appender.RFAS.layout=org.apache.log4j.PatternLayout log4j.appender.RFAS.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n log4j.category.SecurityLogger=${hbase.security.logger} log4j.additivity.SecurityLogger=false #log4j.logger.SecurityLogger.org.apache.hadoop.hbase.security.access.AccessController=TRACE #log4j.logger.SecurityLogger.org.apache.hadoop.hbase.security.visibility.VisibilityController=TRACE # # Null Appender # log4j.appender.NullAppender=org.apache.log4j.varia.NullAppender # # console # Add “console” to rootlogger above if you want to use this # log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.target=System.err log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2}: %m%n log4j.appender.asyncconsole=org.apache.hadoop.hbase.AsyncConsoleAppender log4j.appender.asyncconsole.target=System.err # Custom Logging levels log4j.logger.org.apache.zookeeper=INFO #log4j.logger.org.apache.hadoop.fs.FSNamesystem=DEBUG log4j.logger.org.apache.hadoop.hbase=INFO # Make these two classes INFO-level. Make them DEBUG to see more zk debug. log4j.logger.org.apache.hadoop.hbase.zookeeper.ZKUtil=INFO log4j.logger.org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher=INFO #log4j.logger.org.apache.hadoop.dfs=DEBUG # Set this class to log INFO only otherwise its OTT # Enable this to get detailed connection error/retry logging. # log4j.logger.org.apache.hadoop.hbase.client.HConnectionManager$HConnectionImplementation=TRACE # Uncomment this line to enable tracing on _every_ RPC call (this can be a lot of output) #log4j.logger.org.apache.hadoop.ipc.HBaseServer.trace=DEBUG # Uncomment the below if you want to remove logging of client region caching’ # and scan of hbase:meta messages # log4j.logger.org.apache.hadoop.hbase.client.HConnectionManager$HConnectionImplementation=INFO # log4j.logger.org.apache.hadoop.hbase.client.MetaScanner=INFO # Prevent metrics subsystem start/stop messages (HBASE-17722) log4j.logger.org.apache.hadoop.metrics2.impl.MetricsConfig=WARN log4j.logger.org.apache.hadoop.metrics2.impl.MetricsSinkAdapter=WARN log4j.logger.org.apache.hadoop.metrics2.impl.MetricsSystemImpl=WARN |
然后进行创建
kubectl create configmap hbasemap –from-file=./hbase-1.3.5/conf/log4j.properties
最后我们尝试获取到这个ConfigMap中保存的数据
kubectl get configmap -o yaml apiVersion: v1 items: – apiVersion: v1 data: log4j.properties: | # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # “License”); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an “AS IS” BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Define some default values that can be overridden by system properties hbase.root.logger=INFO,console hbase.security.logger=INFO,console hbase.log.dir=. hbase.log.file=hbase.log # Define the root logger to the system property “hbase.root.logger”. log4j.rootLogger=${hbase.root.logger} # Logging Threshold log4j.threshold=ALL # # Daily Rolling File Appender # log4j.appender.DRFA=org.apache.log4j.DailyRollingFileAppender log4j.appender.DRFA.File=${hbase.log.dir}/${hbase.log.file} # Rollver at midnight log4j.appender.DRFA.DatePattern=.yyyy-MM-dd # 30-day backup #log4j.appender.DRFA.MaxBackupIndex=30 log4j.appender.DRFA.layout=org.apache.log4j.PatternLayout # Pattern format: Date LogLevel LoggerName LogMessage log4j.appender.DRFA.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2}: %m%n # Rolling File Appender properties hbase.log.maxfilesize=256MB hbase.log.maxbackupindex=20 # Rolling File Appender log4j.appender.RFA=org.apache.log4j.RollingFileAppender log4j.appender.RFA.File=${hbase.log.dir}/${hbase.log.file} log4j.appender.RFA.MaxFileSize=${hbase.log.maxfilesize} log4j.appender.RFA.MaxBackupIndex=${hbase.log.maxbackupindex} log4j.appender.RFA.layout=org.apache.log4j.PatternLayout log4j.appender.RFA.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2}: %m%n # # Security audit appender # hbase.security.log.file=SecurityAuth.audit hbase.security.log.maxfilesize=256MB hbase.security.log.maxbackupindex=20 log4j.appender.RFAS=org.apache.log4j.RollingFileAppender log4j.appender.RFAS.File=${hbase.log.dir}/${hbase.security.log.file} log4j.appender.RFAS.MaxFileSize=${hbase.security.log.maxfilesize} log4j.appender.RFAS.MaxBackupIndex=${hbase.security.log.maxbackupindex} log4j.appender.RFAS.layout=org.apache.log4j.PatternLayout log4j.appender.RFAS.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n log4j.category.SecurityLogger=${hbase.security.logger} log4j.additivity.SecurityLogger=false #log4j.logger.SecurityLogger.org.apache.hadoop.hbase.security.access.AccessController=TRACE #log4j.logger.SecurityLogger.org.apache.hadoop.hbase.security.visibility.VisibilityController=TRACE # # Null Appender # log4j.appender.NullAppender=org.apache.log4j.varia.NullAppender # # console # Add “console” to rootlogger above if you want to use this # log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.target=System.err log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2}: %m%n log4j.appender.asyncconsole=org.apache.hadoop.hbase.AsyncConsoleAppender log4j.appender.asyncconsole.target=System.err # Custom Logging levels log4j.logger.org.apache.zookeeper=INFO #log4j.logger.org.apache.hadoop.fs.FSNamesystem=DEBUG log4j.logger.org.apache.hadoop.hbase=INFO # Make these two classes INFO-level. Make them DEBUG to see more zk debug. log4j.logger.org.apache.hadoop.hbase.zookeeper.ZKUtil=INFO log4j.logger.org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher=INFO #log4j.logger.org.apache.hadoop.dfs=DEBUG # Set this class to log INFO only otherwise its OTT # Enable this to get detailed connection error/retry logging. # log4j.logger.org.apache.hadoop.hbase.client.HConnectionManager$HConnectionImplementation=TRACE # Uncomment this line to enable tracing on _every_ RPC call (this can be a lot of output) #log4j.logger.org.apache.hadoop.ipc.HBaseServer.trace=DEBUG # Uncomment the below if you want to remove logging of client region caching’ # and scan of hbase:meta messages # log4j.logger.org.apache.hadoop.hbase.client.HConnectionManager$HConnectionImplementation=INFO # log4j.logger.org.apache.hadoop.hbase.client.MetaScanner=INFO # Prevent metrics subsystem start/stop messages (HBASE-17722) log4j.logger.org.apache.hadoop.metrics2.impl.MetricsConfig=WARN log4j.logger.org.apache.hadoop.metrics2.impl.MetricsSinkAdapter=WARN log4j.logger.org.apache.hadoop.metrics2.impl.MetricsSystemImpl=WARN kind: ConfigMap metadata: creationTimestamp: 2020-09-07T06:57:16Z name: hbasemap namespace: default resourceVersion: “911931” selfLink: /api/v1/namespaces/default/configmaps/hbasemap uid: 5da36979-f0d7-11ea-9934-000c29945dd1 kind: List metadata: resourceVersion: “” selfLink: “” |
最后是Downward API ,让Pod容器能直接获取到Pod Api对象本身的信息
我们尝试定义一个Pod
apiVersion: v1 kind: Pod metadata: name: test-downwardapi-volume labels: zone: us-est-coast cluster: test-cluster1 rack: rack-22 spec: containers: – name: client-container image: k8s.gcr.io/busybox command: [“sh”, “-c”] args: – while true; do if [[ -e /etc/podinfo/labels ]]; then echo -en ‘\n\n’; cat /etc/podinfo/labels; fi; sleep 5; done; volumeMounts: – name: podinfo mountPath: /etc/podinfo readOnly: false volumes: – name: podinfo projected: sources: – downwardAPI: items: – path: “labels” fieldRef: fieldPath: metadata.labels |
在这个Pod的Yaml文件中,定义了一个简单的容器,声明了一个projected类型的volume,不过这个volume的数据源,变成了Downward API,这个Downward API Volume,声明了要暴露的Pod的metedata.labels信息给容器
通过这样的声明让是,我们就能获取到Labels中的字段
这个容器,会不断的打印这个labels中的指令,所以,创建了Pod之后,就可以通过kubectl logs,查看这些Labels字段被打印出来
kubectl logs test-peojected-volume
1. 使用fieldRef可以声明使用: spec.nodeName – 宿主机名字 status.hostIP – 宿主机IP metadata.name – Pod的名字 metadata.namespace – Pod的Namespace status.podIP – Pod的IP spec.serviceAccountName – Pod的Service Account的名字 metadata.uid – Pod的UID metadata.labels[‘<KEY>’] – 指定<KEY>的Label值 metadata.annotations[‘<KEY>’] – 指定<KEY>的Annotation值 metadata.labels – Pod的所有Label metadata.annotations – Pod的所有Annotation 2. 使用resourceFieldRef可以声明使用: 容器的CPU limit 容器的CPU request 容器的memory limit 容器的memory request |
这个列表还在不断的增长中
通过这三个Volume定义的信息,其实大多还可以通过环境变量的方式来出现在容器中,但是一般通过环境变量的设置的,是不具有自动更新功能的,所以,还是建议通过Volume文件方式来获取信息
其次是Pod中一个密切相关的概念,Service Account
这个是用于一个概念,就是在Pod中提供Kubernetes的Api
Service Account的作用,就是Kubernetes内置的一种服务账户,是Kubernetes进行权限分配的对象,比如Service Account A,只被允许对Kubernetes Api进行 GET操作, Service Account B,可以进行所有操作
这个Service Account,其实本质上,是一个特殊的Secret对象,这个特殊的Secret对象,叫做ServiceAccountToken,
所以说,Kubernetes中的Projected Volume只有三种,第四种ServiceAccountToken,只是一种特殊的Secret而已
当然,为了方便使用,Kubernetes提供了一个默认的服务账户,在任何的Pod中,都可以默认的使用这个Service Account
这是如何做到的呢?
靠的还是Projected Volume机制
如果查看任何一个运行在K8S集群中的Pod,都会已经挂载了一个Volume
这个默认的Secret类型的Volume,正是默认Service Account对应的ServiceAccountToken,所以说,Kubernetes在每个Pod都创建的时候,再其spec.volumes部分添加上了默认ServiceAccountToken的定义,整个过程对用户来说是完全透明的
一旦Pod创建完成,容器内就会直接从这个默认的ServiceAccountToken中获取到授权信息
这个授权信息的路径在K8S中是固定的,即为/var/run/secrets/kubernetes/serviceaccount
其中的内容如下
这样,只要加载这些授权文件,就可以访问操作K8s的API了,如果是官方的client包,可以自动加载这个目录下的文件,不需要做任何的配置或者编码操作
这种将Kubernetes客户端以容器的方式运行在集群,然后进行管理的方式,叫做InClusterConfig,也是极好的处理方式
当然,可以选择设置默认不为Pod挂载这个Volume
在Kubernetes中,可以为Pod中的容器定义一个健康检查的探针,这个机制叫做Probe,这样,kubelet会根据Probe的返回值来确定这个容器的状态,而不是根据Docker状态来确定
这种机制,非常适合生产环境去保证健康存活
apiVersion: v1 kind: Pod metadata: labels: test: liveness name: test-liveness-exec spec: containers: – name: liveness image: busybox args: – /bin/sh – -c – touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600 livenessProbe: exec: command: – cat – /tmp/healthy initialDelaySeconds: 5 periodSeconds: 5 |
我们尝试定义一个容器,在启动之后做的第一件事,就是在/tmp目录下创建了一个healthy的文件,作为自己正常运行的标志,30s后,进行删除
然后,在下面,我们定义了一个livenessProbe健康检查,类型是exec,在启动之后,容器内执行一条执行的命令,比如 cat上面的healthy文件,查看返回值是否为0来判断是否是健康的,这个健康检查,在容器启动5s后执行,每5秒执行一次
接下来我们试验下这个pod
kubectl create -f test-liveness-exec.yaml
然后查看这个Pod状态
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
test-liveness-exec 1/1 Running 0 10s
然后过了30秒,查看这个Pod的Events
$ kubectl describe pod test-liveness-exec
会发现在Eevets中报告了一个异常
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
——— ——– —– —- ————- ——– —— ——-
2s 2s 1 {kubelet worker0} spec.containers{liveness} Warning Unhealthy Liveness probe failed: cat: can’t open ‘/tmp/healthy’: No such file or directory
但是,这个Pod状态仍然是Running
这是因为这个异常的容器已经被Kubernetes重启了,这个过程中,Pod保持Running不变
而这个重启,实际上是重新创建了容器
这就是Kubernetes中的Pods恢复机制,默认的值是Always,一旦发生了异常,就一定会被重新创建
Pod的恢复过程,永远都在当前节点上,而不会跑到别的节点上,所以当节点宕机了,也不会主动的迁移到别的节点
如果是希望,可以出现在别的节点上,就需要使用一个Deployment这样的控制器
而且恢复机制restartPolicy,除了Always,还有OnFailure和Never两种情况
Always,在任何的情况下,只要容器不再运行,就自动重启
OnFailure,在容器异常时候才会自动重启容器
Never,从来不重启容器
总结下来,
对于Pod的restartPolicy指定的策略允许重启异常的容器,那么这个Pod就会一直保持Running状态,进行容器重启,不然,Pod就会进入Failed状态
对于多个容器的Pod,只有所有的容器都进入了异常状态后,Pod才会进入Failed状态,不然就是Running状态,Pod的Ready会显示正常容器的个数
$ kubectl get pod test-liveness-exec
NAME READY STATUS RESTARTS AGE
liveness-exec 0/1 Running 1 1m
而且,除了在容器内执行命令外,livenessProbe定义发起HTTP或者TCP请求,例如
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
– name: X-Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
或者
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
Pod可以暴露一个健康检查的URL,来检查对应的服务状态
最后,说一下Kubernetes的默认模板
对于Kuberenetes中,我们提交了一个基本的简单的Pod,由K8s来对Pod加上对应的必要信息,比如labels,annotations,volumes
这样,就降低了Pod Yaml的编写门槛,这个机制叫做PodPreset
我们,直接看一下PodPreset对象的定义,就是在开发人员编写的Pod里追加的字段,都可以提前定义好
apiVersion: settings.k8s.io/v1alpha1 kind: PodPreset metadata: name: allow-database spec: selector: matchLabels: role: frontend env: – name: DB_PORT value: “6379” volumeMounts: – mountPath: /cache name: cache-volume volumes: – name: cache-volume emptyDir: {} |
在这个PodPreset中,首先是一个selector,意味着这个后面追加的请求,只会作用于selector定义的,带有role:frontend标签Pod对象,防止误伤
然后我们定义了一组Pod中的字段,来进行挂载
env定义了环境变量
volumeMounts定义了容器volume的挂载目录
volumes定义了一个emptyDir的Volume
这样我们创建这个PodPreset对象
kubectl create -f preset.yaml
这样,后面带有selector中匹配的上的 role: frontend的Pod就会被修改上
要说明的是,Pod API对象被创建之前追加到这个对象本身上,而不会影响到任何Pod的控制器的定义
比如提交的是一个deployment,那么这个Deployement本身不会被PodPresent改变,被修改的是Deployment创建出来的所有Pod
而且,如果定义了多个同时作用于同一个Pod对象的多个podPresent,会发生什么?
会进行合并多个PodPresent做的修改,但是修改有冲突的话,冲突字段不会被修改
本章总结一下
我们说了Pod对象中的特殊对象,和一些关于默认Pod的设置
在此过程中
我们学习了Secret对象保存应用的密码,保存配置信息的是ConfigMap对象
最后是自动填充的PodPreset对Pod进行批量化,自动化修改的工具对象
思考题:
在没有Kubernetes的时候,是通过什么方式进行的健康检查的,Kubernetes的livenessProbe是否足够了
之前利用shell脚本来进行过探测,也是利用的健康监测接口,一旦返回值不是200,直接杀死进程,然后java -jar命令尝试重启
probe原理是sidecar容器来实现的
不是的,是使用的exec
而且Deployment中,其重启策略一直是always的