Kubernetes生产落地实践01
Pod 实现原理
-
什么是 Pod
Pod 的共享上下文包括一组 Linux 名字空间、控制组(cgroup)和可能一些其他的隔离 方面,即用来隔离 Docker 容器的技术。 在 Pod 的上下文中,每个独立的应用可能会进一步实施隔离。就 Docker 概念的术语而言,Pod 类似于共享名字空间和文件系统卷的一组 Docker 容器。说明: 除了 Docker 之外,Kubernetes 支持 很多其他容器运行时, Docker 是最有名的运行时, 使用 Docker 的术语来描述 Pod 会很有帮助。
-
Pod 生命期
和一个个独立的应用容器一样,Pod 也被认为是相对临时性(而不是长期存在)的实体。 Pod 会被创建、赋予一个唯一的 ID(UID), 并被调度到节点,并在终止(根据重启策略)或删除之前一直运行在该节点。
如果一个节点死掉了,调度到该节点 的 Pod 也被计划在预定超时期限结束后删除。
-
使用 Pod
通常你不需要直接创建 Pod,甚至单实例 Pod。 相反,你会使用诸如 Deployment 或 Job 这类工作负载资源 来创建 Pod。如果 Pod 需要跟踪状态, 可以考虑 StatefulSet 资源。
Kubernetes 集群中的 Pod 主要有两种用法:
- 运行单个容器的 Pod。"每个 Pod 一个容器"模型是最常见的 Kubernetes 用例; 在这种情况下,可以将 Pod 看作单个容器的包装器,并且 Kubernetes 直接管理 Pod,而不是容器。
- 运行多个协同工作的容器的 Pod。 Pod 可能封装由多个紧密耦合且需要共享资源的共处容器组成的应用程序。 这些位于同一位置的容器可能形成单个内聚的服务单元 —— 一个容器将文件从共享卷提供给公众, 而另一个单独的“边车”(sidecar)容器则刷新或更新这些文件。 Pod 将这些容器和存储资源打包为一个可管理的实体。
使用Job创建一个Pod,打印信息后会暂停
apiVersion: batch/v1
kind: Job
metadata:
name: hello
spec:
completions: 5
template:
spec:
containers:
- name: hello
image: nginx
command: ['sh', '-c', 'echo "Hello, Kubernetes! " && sleep 2']
restartPolicy: OnFailure
kubectl create -f hello-job.yaml
#查看job
kubectl get jobs
kubectl delete jobs hello
Pod 的生命周期
容器阶段 Phase
-
Pending(挂起) Pod 已被 Kubernetes 系统接受,但有一个或者多个容器尚未创建亦未运行。此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间。
-
Running(运行中) Pod 已经绑定到了某个节点,Pod 中所有的容器都已被创建。至少有一个容器仍在运行,或者正处于启动或重启状态。
-
Succeeded(成功) Pod 中的所有容器都已成功终止,并且不会再重启。
-
Failed(失败) Pod 中的所有容器都已终止,并且至少有一个容器是因为失败终止。也就是说,容器以非 0 状态退出或者被系统终止。
-
Unknown(未知) 因为某些原因无法取得 Pod 的状态。这种情况通常是因为与 Pod 所在主机通信失败。
容器状态 Status
一旦调度器将 Pod 分派给某个节点,kubelet 就通过 容器运行时 开始为 Pod 创建容器。 容器的状态有三种:Waiting(等待)、Running(运行中)和 Terminated(已终止)。
kubectl describe pod <pod 名称> -n namespace
-
Waiting (等待)
如果容器并不处在 Running 或 Terminated 状态之一,它就处在 Waiting 状态。 处于 Waiting 状态的容器仍在运行它完成启动所需要的操作:例如,从某个容器镜像 仓库拉取容器镜像,或者向容器应用 Secret 数据等等。 当你使用 kubectl 来查询包含 Waiting 状态的容器的 Pod 时,你也会看到一个 Reason 字段,其中给出了容器处于等待状态的原因。
-
Running(运行中)
Running 状态表明容器正在执行状态并且没有问题发生。 如果配置了 postStart 回调,那么该回调已经执行完成。 如果你使用 kubectl 来查询包含 Running 状态的容器的 Pod 时,你也会看到 关于容器进入 Running 状态的信息。
-
Terminated(已终止)
处于 Terminated 状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。 如果你使用kubectl 来查询包含 Terminated 状态的容器的 Pod 时,你会看到 容器进入此状态的原因、退出代码以及容器执行期间的起止时间。
为容器的生命周期事件设置处理函数
定义 postStart 和 preStop 处理函数
apiVersion: v1
kind: Pod
metadata:
name: lifecycle
labels:
name: lifecycle
spec:
containers:
- name: lifecycle
image: nginx
lifecycle:
postStart:
exec:
command: ["sh","-c","echo Hello from the postStart handler > /usr/share/message"]
preStop:
exec:
command: ["sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done"]
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 80
在上述配置文件中,可以看到 postStart 命令在容器的 /usr/share 目录下写入文件 message。 命令preStop 负责优雅地终止 nginx 服务。当因为失效而导致容器终止时,这一处理方式很有用。
创建 Pod:
kubectl create -f lifecycle.yaml
验证 Pod 中的容器已经运行:
kubectl get pod lifecycle
使用 shell 连接到你的 Pod 里的容器:
kubectl exec -it lifecycle -- /bin/bash
在 shell 中,验证 postStart 处理函数创建了 message 文件:
cat /usr/share/message
Hello from the postStart handler
创建包含Init 容器的 Pod
Init容器
每个 Pod 中可以包含多个容器, 应用运行在这些容器里面,同时 Pod 也可以有一个或多个先于应用容器启动的 Init 容器。
Init容器具有以下两个特点
-
它们总是运行到完成
-
每个必须在下一个启动前完成
如果Pod的Init容器完成失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止。 然而,如果 Pod 对应的 restartPolicy 值为 Never,Kubernetes 不会重新启动 Pod。
与普通容器的不同之处
Init 容器支持应用容器的全部属性和特性,包括资源限制、数据卷和安全设置。
同时 Init 容器不支持 lifecycle、livenessProbe、readinessProbe 和 startupProbe, 因为它们必须在 Pod 就绪之前运行完成。
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
name: myapp
spec:
containers:
- name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'date && sleep 3600']
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 80
initContainers:
- name: init-container
image: busybox:1.28
command: ['sh', '-c', "date && sleep 10"]
查看Pod中的容器
kubectl logs myapp-pod -c init-contai ner
使用探针检查Pod的健康性
探针的作用
探针 是由 kubelet 对容器执行的定期诊断。 要执行诊断,kubelet 调用由容器实现的 Handler (处理程序)。有三种类型的处理程序:
ExecAction: 在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
TCPSocketAction: 对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。
HTTPGetAction: 对容器的 IP 地址上指定端口和路径执行 HTTP Get 请求。如果响应的状态码大于等于200 且小于 400,则诊断被认为是成功的。
每次探测都将获得以下三种结果之一:
Success(成功):容器通过了诊断。
Failure(失败):容器未通过诊断。
Unknown(未知):诊断失败,因此不会采取任何行动。
何时该使用启动探针
对于所包含的容器需要较长时间才能启动就绪的 Pod 而言,启动探针是有用的。 你不再需要配置一个较长的存活态探测时间间隔,只需要设置另一个独立的配置选定, 对启动期间的容器执行探测,从而允许使用远远超出存活态时间间隔所允许的时长。
如果你的容器启动时间通常超出 initialDelaySeconds + failureThreshold × periodSeconds 总值,你应该设置一个启动探测,对存活态探针所使用的同一端点执行检查。 periodSeconds 的默认值是 30 秒。你应该将其 failureThreshold 设置得足够高, 以便容器有充足的时间完成启动,并且避免更改存活态探针所使用的默认值。 这一设置有助于减少死锁状况的发生。
HTTP 请求探针
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
name: myapp
spec:
containers:
- name: myapp
image: registry.cn-beijing.aliyuncs.com/qingfeng666/nginx:latest
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 3
在这个配置文件中,可以看到 Pod 也只有一个容器。 periodSeconds 字段指定了 kubelet 每隔 3 秒执行一次存活探测。 initialDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 3 秒。 kubelet会向容器内运行的服务(服务会监听 8080 端口)发送一个 HTTP GET 请求来执行探测。 如果服务器上/healthz 路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。 如果处理程序返回失败代码,则 kubelet 会杀死这个容器并且重新启动它。
任何大于或等于 200 并且小于 400 的返回代码标示成功,其它返回代码都标示失败
为容器设置启动时要执行的命令和参数
打印环境变量
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
name: myapp
spec:
containers:
- name: myapp
image: debian
resources:
limits:
memory: "128Mi"
cpu: "500m"
command: ["printenv"]
args: ["HOSTNAME", "KUBERNETES_PORT"]
restartPolicy: OnFailure
使用环境变量来设置参数
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
name: myapp
spec:
containers:
- name: myapp
image: debian
env:
- name: MESSAGE
value: "hello world"
resources:
limits:
memory: "128Mi"
cpu: "500m"
command: ["/bin/echo"]
args: ["$(MESSAGE)"]
restartPolicy: OnFailure
这意味着你可以将那些用来设置环境变量的方法应用于设置命令的参数,其中包括了 ConfifigMaps与Secrets
环境变量需要加上括号,类似于 "$(VAR)" 。这是在 command 或 args 字段使用变量的格
式要求。
为容器定义相互依赖的环境变量
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
name: myapp
spec:
containers:
- name: myapp
image: busybox
resources:
limits:
memory: "128Mi"
cpu: "500m"
command:
- "sh"
- "-c"
args:
- printf UNCHANGED_REFERENCE=$UNCHANGED_REFERENCE'\n'; printf SERVICE=$SERVICE'\n'; printf ESCAPED_SERVICE=$ESCAPED_SERVICE; sleep 120
env:
- name: SERVICE_IP
value: "192.168.99.102"
- name: SERVICE_PORT
value: "31111"
- name: UNCHANGED_REFERENCE
value: $(PROTOCAL)://$(SERVICE_IP):$(SERVICE_PORT)
- name: PROTOCAL
value: "https"
- name: SERVICE
value: $(PROTOCAL)://$(SERVICE_IP):$(SERVICE_PORT)
- name: ESCAPED_SERVICE
value: $$(PROTOCAL)://$(SERVICE_IP):$(SERVICE_PORT)
你已经定义了 SERVICE_ADDRESS 的正确依赖引用, UNCHANGED_REFERENCE 的错误依赖引
用, 并跳过了 ESCAPED_REFERENCE 的依赖引用。
如果环境变量被引用时已事先定义,则引用可以正确解析, 比如 SERVICE_ADDRESS 的例子。
当环境变量未定义或仅包含部分变量时,未定义的变量会被当做普通字符串对待, 比如UNCHANGED_REFERENCE 的例子。 注意,解析不正确的环境变量通常不会阻止容器启动。
$(VAR_NAME) 这样的语法可以用两个 $ 转义,既: $$(VAR_NAME) 。 无论引用的变量是否定义,转义的引用永远不会展开。
为容器和 Pods 分配 CPU 资源
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
name: myapp
spec:
containers:
- name: myapp
image: registry.cn-beijing.aliyuncs.com/qingfeng666/stress
resources:
requests:
# 请求预留0.5个cpu
cpu: "0.5"
limits:
memory: "128Mi"
cpu: "1"
ports:
- containerPort: 80
args:
- -cpus
- "2"
配置文件的 args 部分提供了容器启动时的参数。 -cpus "2" 参数告诉容器尝试使用 2 个 CPU。
发现通过容器启动参数设置cpu数十没有用的,使用的 cpu 被配置文件限制。
用节点亲和性把 Pods 分配到节点
列出集群中的节点及其标签:
kubectl get nodes --show-labels
为其中一个节点加上标签
kubectl label nodes <your-node-name> disktype=ssd
依据强制的节点亲和性调度 Pod
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
name: myapp
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
containers:
- name: myapp
image: <Image>
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 80
将 ConfifigMap 中的键值对配置为容器环境变量
创建一个包含多个键值对的 ConfifigMap。
apiVersion: v1
kind: ConfigMap
metadata:
name: my-db-config
data:
db-url: localhost
---
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
name: myapp
spec:
containers:
- name: myapp
image: busybox
command: ["sh","-c","env"]
envFrom:
- configMapRef:
name: my-db-config
resources:
limits:
memory: "128Mi"
cpu: "500m"
容器Root用户和特权用户
Docker允许隔离其主机OS上的进程,功能和文件系统,并且出于实际原因,大多数容器默认实际上以root用户身份运行。
启用 privileged:
docker run -it --privileged busybox sh
Kubernetes通过Security Context提供了相同的功能:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
name: nginx
spec:
containers:
- name: nginx
image: nginx
securityContext:
privileged: true
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 80
为 Pod 创建非 root 用户运行
要为 Pod 设置安全性设置,可在 Pod 规约中包含 securityContext 字段。 securityContext 字段值是一个 PodSecurityContext
对象。你为 Pod 所设置的安全性配置会应用到 Pod 中所有 Container 上。下面是一个 Pod 的配置文件,该 Pod 定义了 securityContext 和一个 emptyDir 卷。
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
name: myapp
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
volumes:
- name: security
emptyDir: {}
containers:
- name: myapp
image: busybox
command: ["sh","-c","sleep 1h"]
volumeMounts:
- name: security
mountPath: /data/demo
securityContext:
allowPrivilegeEscalation: false #是否允许权限溢出
resources:
limits:
memory: "128Mi"
cpu: "500m"
在配置文件中, runAsUser 字段指定 Pod 中的所有容器内的进程都使用用户 ID 1000 来运行。 runAsGroup 字段指定所有容器中的进程都以主组 ID 3000 来运行。 如果忽略此字段,则容器的主组 ID 将是root(0)。 当 runAsGroup 被设置时,所有创建的文件也会划归用户 1000 和组 3000。 由于 fsGroup被设置,容器中所有进程也会是附组 ID 2000 的一部分。 卷 /data/demo 及在该卷中创建的任何文件的属主都会是组 ID 2000。
评论区