5-1 创建Nginx Pod


Kubectl 创建 Nginx Pod

  • 编写nginx.yaml文件

    1. apiVersion: v1
    2. kind: Pod
    3. metadata:
    4. name: my-nginx
    5. labels:
    6. name: my-nginx
    7. spec:
    8. containers:
    9. - name: my-nginx
    10. image: nginx
    11. resources:
    12. limits:
    13. memory: "128Mi"
    14. cpu: "500m"
    15. ports:
    16. - containerPort: 80
  • 创建Pod kubectl create -f nginx.yaml

    1. [root@master ~]# kubectl get pod
    2. NAME READY STATUS RESTARTS AGE
    3. my-nginx 1/1 Running 0 54s
    4. [root@master ~]#
    5. [root@master ~]# kubectl describe pod my-nginx

    image.png

  • 登录到kubernetes-dashboard就能看到创建的Nginx Pod的状态

image.png

5-2 Pod实现原理


什么是 Pod?

Pod的共享上下文包括一组Linux命名空间、控制组(cgroup)和可能一些其他的隔离方面,即用来隔离 Docker 容器的技术。 在 Pod 的上下文中,每个独立的应用可能会进一步实施隔离。
就 Docker 概念的术语而言,Pod 类似于共享命名空间和文件系统卷的一组 Docker 容器。

说明:除了Docker之外,Kubernetes支持很多其他容器运行时,Docker是最有名的运行时, 使用 Docker 的术语来描述 Pod 会很有帮助。

Pod 生命期

和一个个独立的应用容器一样,Pod 也被认为是相对临时性(而不是长期存在)的实体。Pod会被创建、赋予一个唯一的ID(UID) , 并被调度到节点,并在终止(根据重启策略)或删除之前一直
运行在该节点。
如果一个节点死掉了,调度到该节点 的 Pod 也被计划在预定超时期限结束后删除。

Pod 结构图例

一个包含多个容器的 Pod 中包含一个用来拉取文件的程序和一个Web 服务器, 均使用持久卷作为容器间共享的存储。
image.png

使用Pod

通常你不需要直接创建 Pod,甚至单实例 Pod。相反,你会使用诸如 Deployment 或 Job 这类工作负载资源 来创建 Pod。如果 Pod 需要跟踪状态,可以考虑StatefulSet 资源。

Kubernetes 集群中的 Pod 主要有两种用法:

  1. 运行单个容器的 Pod。”每个Pod一个容器” 模型是最常见的Kubernetes 用例;在这种情况下,可以将 Pod 看作单个容器的包装器,并且 Kubernetes 直接管理 Pod,而不是容器。
  2. 运行多个协同工作的容器的 Pod。Pod 可能封装由多个紧密耦合且需要共享资源的共处容器组成的应用程序。这些位于同一位置的容器可能形成单个内聚的服务单元一个容器将文件从共享卷提供给公众,而另一个单独的“边车”(sidecar) 容器则刷新或更新这些文件。Pod 将这些容器和存储资源打包为一个可管理的实体。
  • 演示:使用Job 创建一个 Pod,打印信息后会暂停。

hello-job.yaml 文件

  1. apiVersion: batch/v1
  2. kind: Job
  3. metadata:
  4. name: hello
  5. spec:
  6. completions: 5
  7. template:
  8. # 这里是pod模板
  9. spec:
  10. containers:
  11. - name: hello
  12. image: nginx
  13. command: ['sh', '-c', 'echo "Hello, Kubernetes! " && sleep 1']
  14. restartPolicy: OnFailure
  15. # 以上为Pod模板
  • 创建job kubectl create -f hello-job.yaml
  • 查看pod和job状态,使用 -w 即watch参数实时观察pod状态

    1. [root@node1 ~]# kubectl get job
    2. NAME COMPLETIONS DURATION AGE
    3. hello 5/5 75s 2m44s
    4. [root@node1 ~]#
    5. [root@node1 ~]# kubectl get po -w
    6. NAME READY STATUS RESTARTS AGE
    7. hello-f4nt8 0/1 Completed 0 2m26s
    8. hello-jrhwx 0/1 Completed 0 111s
    9. hello-nlzv4 0/1 Completed 0 2m44s
    10. hello-vqsnf 0/1 Completed 0 2m8s
    11. hello-zfslx 0/1 Completed 0 2m48s
    12. [root@node1 ~]# kubectl logs -f hello-f4nt8
    13. Hello, Kubernetes!
  • 删除job

  1. [root@node1 ~]# kubectl delete job hello
  2. job.batch "hello" deleted
  3. [root@node1 ~]#
  4. [root@node1 ~]# kubectl get job
  5. No resources found in default namespace.

Pod 网络

每个Pod都在每个地址族中获得一个唯一的IP 地址。Pod中的每个容器共享网络名字空间,包括 IP 地址和网络端口。 Pod 内的容器可以使用 localhost 互相通信。 当 Pod 中的容器与 Pod 之外的实体通信时,它们必须协调如何使用共享的网络资源(例如端口)。

5-3 Pod 的生命周期


容器阶段 Phase

  1. Pending(挂起)Pod已被Kubernetes系统接受,但有一个或者多个容器尚未创建亦未运行。此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间。
  2. Running(运行中) Pod已经绑定到了某个节点,Pod 中所有的容器都已被创建。至少有一个容器仍在运行,或者正处于启动或重启状态。
  3. Succeeded(成功)Pod 中的所有容器都已成功终止,并且不会再重启。
  4. Failed(失败)Pod中的所有容器都已终止,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。
  5. Unknown(未知)因为某些原因无法取得Pod的状态。这种情况通常是因为与Pod所在主机通信失败。

容器状态 Status

一旦调度器将 Pod 分派给某个节点,kubelet 就通过容器运行时开始为 Pod 创建容器。 容器的状态有三种:Waiting(等待)、Running(运行中)和Terminated(已终止)
kubectl describe pod <pod 名称>

  1. [root@node1 ~]# kubectl describe pod hello-hdm2p
  2. Name: hello-hdm2p
  3. Namespace: default
  4. Priority: 0
  5. Node: node1/192.168.56.102
  6. Start Time: Fri, 21 Jan 2022 23:28:29 +0800
  7. Labels: controller-uid=0d4d7a48-ff45-4b24-b737-56ebe0123ec2
  8. job-name=hello
  9. Annotations: <none>
  10. Status: Succeeded
  11. IP: 10.244.1.9
  12. IPs:
  13. IP: 10.244.1.9
  14. Controlled By: Job/hello
  15. Containers:
  16. hello:
  17. Container ID: docker://b83e3e4634b180fa64a19f17cee8dc908e3c8430fa22419ed85838f1a12b1519
  18. Image: nginx
  19. Image ID: docker-pullable://nginx@sha256:0d17b565c37bcbd895e9d92315a05c1c3c9a29f762b011a10c54a66cd53c9b31
  20. Port: <none>
  21. Host Port: <none>
  22. Command:
  23. sh
  24. -c
  25. echo "Hello, Kubernetes! " && sleep 1
  26. State: Terminated
  27. Reason: Completed
  28. Exit Code: 0
  29. Started: Fri, 21 Jan 2022 23:28:46 +0800
  30. Finished: Fri, 21 Jan 2022 23:28:47 +0800
  31. Ready: False
  32. Restart Count: 0
  33. Environment: <none>
  34. Mounts:
  35. /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-pp7dx (ro)
  36. Conditions:
  37. Type Status
  38. Initialized True
  39. Ready False
  40. ContainersReady False
  41. PodScheduled True
  42. Volumes:
  43. kube-api-access-pp7dx:
  44. Type: Projected (a volume that contains injected data from multiple sources)
  45. TokenExpirationSeconds: 3607
  46. ConfigMapName: kube-root-ca.crt
  47. ConfigMapOptional: <nil>
  48. DownwardAPI: true
  49. QoS Class: BestEffort
  50. Node-Selectors: <none>
  51. Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
  52. node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
  53. Events:
  54. Type Reason Age From Message
  55. ---- ------ ---- ---- -------
  56. Normal Scheduled <invalid> default-scheduler Successfully assigned default/hello-hdm2p to node1
  57. Normal Pulling 3m14s kubelet Pulling image "nginx"
  58. Normal Pulled 2m58s kubelet Successfully pulled image "nginx" in 15.628833599s
  59. Normal Created 2m58s kubelet Created container hello
  60. Normal Started 2m58s kubelet Started container hello
  1. Waiting(等待)

如果容器并不处在Running或Terminated状态之一,它就处在Waiting状态。处于Waiting状态的容器仍在运行它完成启动所需要的操作:例如,从某个容器镜像仓库拉取容器镜像,或者向容器应用Secret 数据等等。 当你使用 kubectl 来查询包含 Waiting 状态的容器的 Pod 时,你也会看到一个Reason 字段,其中给出了容器处于等待状态的原因。

  1. Running (运行中)

Running 状态表明容器正在执行状态并且没有问题发生。 如果配置了 postStart 回调,那么该回调已经执行完成。如果你使用 kubectl 来查询包含 Running 状态的容器的 Pod 时,你也会看到 关于容器进入Running状态的信息。

  1. Terminated(已终止)

处于 Terminated 状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。 如果你使用kubectl来查询包含 Terminated 状态的容器的 Pod 时,你会看到容器进入此状态的原因、退出代码以及容器执行期间的起止时间。

5-4 为容器的生命周期事件设置处理函数


定义 postStart 和 preStop 处理函数

在本练习中,你将创建一个包含一个容器的 Pod,该容器为 postStart 和 preStop 事件提供对应的处理函数。
lifecycle.yaml

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: lifecycle-demo
  5. spec:
  6. containers:
  7. - name: lifecycle-demo-container
  8. image: nginx
  9. lifecycle:
  10. postStart:
  11. exec:
  12. command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
  13. preStop:
  14. exec:
  15. command: ["/bin/sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done"]

在上述配置文件中,可以看到 postStart 命令在容器的/usr/share 目录下写入文件 message。
命令preStop 负责优雅地终止 nginx 服务。当因为失效而导致容器终止时,这一处理方式很有用。

  • 创建pod

    1. [root@node1 ~]# kubectl create -f lifecycle.yaml
    2. pod/lifecycle-demo created
    3. [root@node1 ~]# kubectl get po -w
    4. NAME READY STATUS RESTARTS AGE
    5. lifecycle-demo 0/1 ContainerCreating 0 16s
    6. lifecycle-demo 1/1 Running 0 18s
  • 使用shell进入pod容器内

    1. [root@node1 ~]# kubectl exec -it lifecycle-demo bash
    2. kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
    3. root@lifecycle-demo:/#
  • 在shell中,验证portStart处理函数创建了message文件,输出为处理函数所写入的内容

    1. root@lifecycle-demo:/# cat /usr/share/message
    2. Hello from the postStart handler

    5-5 创建包含Init 容器的 Pod


理解 Init 容器

每个 Pod 中可以包含多个容器,应用运行在这些容器里面,同时 Pod 也可以有一个或多个先于应用容器启动的 Init 容器。

Init 容器与普通的容器非常像,除了如下两点:

  1. 它们总是运行到完成。
  2. 每个都必须在下一个启动之前成功完成。

如果Pod的Init容器失败,Kubernetes会不断地重启该Pod,直到Init容器成功为止。然而,如果Pod 对应的 restartPolicy 值为 Never,Kubernetes 不会重新启动 Pod。

与普通容器的不同之处

Init 容器支持应用容器的全部属性和特性,包括资源限制、数据卷和安全设置。
同时Init 容器不支持 lifecycle、livenessProbe、readinessProbe 和 startupProbe,因为它们必须在Pod 就绪之前运行完成。

实战 Init Pod

下面的例子定义了一个具有2个Init容器的简单Pod。第一个等待 myservice 启动, 第二个等待mydb 启动。一旦这两个 Init容器 都启动完成,Pod 将启动 spec 节中的应用容器。

initPod.yaml

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: myapp
  5. labels:
  6. name: myapp
  7. spec:
  8. containers:
  9. - name: myapp
  10. image: busybox:1.28
  11. command: ['sh', '-c', 'date && sleep 3600']
  12. resources:
  13. limits:
  14. memory: "128Mi"
  15. cpu: "500m"
  16. ports:
  17. - containerPort: 80
  18. initContainers:
  19. - name: init-container
  20. image: busybox:1.28
  21. command: ['sh', '-c', 'date && sleep 10']

在init容器启动后10s才启动myapp容器

  • 创建容器
    1. [root@node1 ~]# kubectl create -f initPod.yaml
    2. pod/myapp created
    3. [root@node1 ~]#
    4. [root@node1 ~]# kubectl get po -w
    5. NAME READY STATUS RESTARTS AGE
    6. lifecycle-demo 1/1 Running 0 19m
    7. myapp 0/1 Init:0/1 0 6s
    8. myapp 0/1 PodInitializing 0 14s
    9. myapp 1/1 Running 0 15s
    10. ^C[root@node1 ~]#
    11. [root@node1 ~]# kubectl logs myapp
    12. Fri Jan 21 17:13:37 UTC 2022
    13. [root@node1 ~]#
    14. [root@node1 ~]# kubectl logs myapp -c init-container
    15. Fri Jan 21 17:13:27 UTC 2022

5-6 用探针检查 Pod 的健康性


探针的作用

探针是由kubelet 对容器执行的定期诊断。 要执行诊断,kubelet 调用由容器实现的 Handler(处理程序)。有三种类型的处理程序:

  • ExecAction:在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
  • TCPSocketAction:对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成

功的。

  • HTTPGetAction: 对容器的 IP 地址上指定端口和路径执行 HTTP Get 请求。如果响应的状态码大于等于200且小于400,则诊断被认为是成功的。

每次探测都将获得以下三种结果之一:

  • Success(成功):容器通过了诊断。
  • Failure(失败):容器未通过诊断。
  • Unknown(未知):诊断失败,因此不会采取任何行动。

    何时该使用启动探针?

    对于所包含的容器需要较长时间才能启动就绪的 Pod 而言,启动探针是有用的。 你不再需要配置一个较长的存活态探测时间间隔,只需要设置另一个独立的配置选定,对启动期间的容器执行探测,从而允许使用远远超出存活态时间间隔所允许的时长。

如果你的容器启动时间通常超出 initialDelaySeconds + failureThreshold x periodSeconds 总值,你应该设置一个启动探测,对存活态探针所使用的同一端点执行检查。periodSeconds 的默认值是 30秒。你应该将其 failureThreshold 设置得足够高,以便容器有充足的时间完成启动,并且避免更改存活态探针所使用的默认值。这一设置有助于减少死锁状况的发生。

实战 HTTP 请求探针

下面是一个 Pod 的配置文件:liveness.yaml

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. labels:
  5. test: liveness
  6. name: liveness-http
  7. spec:
  8. containers:
  9. - name: liveness
  10. image: mirrorgooglecontainers/liveness
  11. args:
  12. - /server
  13. livenessProbe:
  14. httpGet:
  15. path: /healthz
  16. port: 8080
  17. httpHeaders:
  18. - name: Custom-Header
  19. value: Awesome
  20. initialDelaySeconds: 3
  21. periodSeconds: 3

在这个配置文件中,可以看到 Pod 也只有一个容器。periodSeconds字段指定了 kubelet 每隔 3 秒执
行一次存活探测。initiaLDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 3 秒。kubelet 会向容器内运行的服务(服务会监听 8080 端口)发送一个 HTTP GET 请求来执行探测。 如果服
务器上 /healthz 路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。 如果处理程序返回失败代码,则 kubelet 会杀死这个容器并且重新启动它。

任何大于或等于 200 并且小于 400 的返回代码标示成功,其它返回代码都标示失败。

可以在这里看服务的源码server.go

  1. http.HandleFunc("/healthz",func(w http.Responsewriter, r *http.Requests)
  2. duration := time.Now().Sub(started)
  3. if duration.Seconds() > 10 {
  4. w.WriteHeader500
  5. w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
  6. } else {
  7. w.WriteHeader(200)
  8. w.Write([]byte("ok"))
  9. }
  10. )}

容器存活的最开始 10 秒中, /healthz 处理程序返回一个 200 的状态码。之后处理程序返回 500 的状态码。

kubelet 在容器启动之后 3 秒开始执行健康检测。所以前几次健康检查都是成功的。但是 10 秒之后,健康检查会失败,并且 kubelet 会杀死容器再重新启动容器。

创建一个 Pod 来测试 HTTP 的存活检测:

  1. kubectl apply -f liveness.yaml
  2. # 或者
  3. kubect1 apply -f https://k8s.io/examples/pods/probe/http-liveness.yaml

10秒之后,过看Pod事件来检测存活探经失败了并且容器被重新启动了。

  1. [root@node1 ~]# kubectl get po -w
  2. NAME READY STATUS RESTARTS AGE
  3. liveness-http 0/1 ContainerCreating 0 8s
  4. liveness-http 1/1 Running 0 20s
  5. liveness-http 1/1 Running 1 (27m ago) 54s
  6. liveness-http 1/1 Running 2 (27m ago) 86s
  7. liveness-http 1/1 Running 3 (27m ago) 118s
  8. liveness-http 1/1 Running 4 (27m ago) 2m17s
  9. [root@node1 ~]#
  10. [root@node1 ~]# kubectl describe pod liveness-http
  11. ... ...
  12. Events:
  13. Type Reason Age From Message
  14. ---- ------ ---- ---- -------
  15. Normal Scheduled <invalid> default-scheduler Successfully assigned default/liveness-http to node1
  16. Normal Pulled 2m27s kubelet Successfully pulled image "mirrorgooglecontainers/liveness" in 17.895838646s
  17. Normal Pulled 114s kubelet Successfully pulled image "mirrorgooglecontainers/liveness" in 15.605885143s
  18. Normal Started 81s (x3 over 2m26s) kubelet Started container liveness
  19. Normal Pulled 81s kubelet Successfully pulled image "mirrorgooglecontainers/liveness" in 15.652596078s
  20. Normal Created 81s (x3 over 2m27s) kubelet Created container liveness
  21. Warning Unhealthy 63s (x9 over 2m15s) kubelet Liveness probe failed: HTTP probe failed with statuscode: 500
  22. Normal Killing 63s (x3 over 2m9s) kubelet Container liveness failed liveness probe, will be restarted
  23. Normal Pulling 63s (x4 over 2m44s) kubelet Pulling image "mirrorgooglecontainers/liveness"
  24. [root@node1 ~]#
  25. [root@node1 ~]# kubectl get po
  26. NAME READY STATUS RESTARTS AGE
  27. liveness-http 0/1 CrashLoopBackOff 7 (27m ago) 9m55s

5-7 为容器设置启动时要执行的命令和参数


创建 Pod 时设置命令及参数

创建 Pod 时,可以为其下的容器设置启动时要执行的命令及其参数。如果要设置命令,就填写在配置文件的 command字段下,如果要设置命令的参数,就填写在配置文件的 args 字段下。一旦 Pod 创建完成,该命令及其参数就无法再进行更改了。

如果在配置文件中设置了容器启动时要执行的命令及其参数,那么容器镜像中自带的命令与参数将会被覆盖而不再执行。如果配置文件中只是设置了参数,却没有设置其对应的命令,那么容器镜像中自带的命令会使用该新参数作为其执行时的参数。

说明:在有些容器运行时中, command 字段对应 entrypoint

本示例commands.yaml中,将创建一个只包含单个容器的 Pod。在 Pod 配置文件中设置了一个命令与两个参数:

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: command-demo
  5. labels:
  6. purpose: demonstrate-command
  7. spec:
  8. containers:
  9. - name: command-demo-container
  10. image: debian
  11. command: ["printenv"]
  12. args: ["HOSTNAME", "KUBERNETES_PORT"]
  13. restartPolicy: OnFailure

另一示例args.yaml

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: myapp
  5. labels:
  6. name: myapp
  7. spec:
  8. containers:
  9. - name: myapp
  10. image: debian
  11. command: ["printenv"]
  12. args: ["HOSTNAME","KUBERNATES_PORT"]
  13. restartPolicy: OnFailure
  1. 基于YAML文件创建一个Pod:

    1. kubectl apply -f commands.yaml
  2. 获取正在运行的Pods:

    1. kubectl get pods

    查询结果显示在 command-demo 这个Pod运行的容器已经启动完成。

  3. 如果要获取容器启动时执行命令的输出结果,可以通过Pod的日志进行查看:

    1. kubectl logs command-demo

    日志中显示了 HOSTNAME 和 KUBERNETES_PORT 这两个环境变量的值:

    1. command-demo
    2. tcp://10.3.240.1:443

    使用环境变量来设置参数

    在上面的示例中,我们直接将一串字符作为命令的参数。除此之外,我们还可以将环境变量作为命令的参数。 ```yaml env:

  • name: MESSAGE value: “hello world” command: [“/bin/echo”] args:[“$(MESSAGE)”]

    1. comands.yaml文件就变成:
    2. ```yaml
    3. apiVersion: v1
    4. kind: Pod
    5. metadata:
    6. name: command-demo
    7. labels:
    8. purpose: demonstrate-command
    9. spec:
    10. containers:
    11. - name: command-demo-container
    12. image: debian
    13. env:
    14. - name: MESSAGE
    15. value: "hello world"
    16. command: ["/bin/echo"]
    17. args: ["$(MESSAGE)"]
    18. restartPolicy: OnFailure
  • 再次创建查看结果 ```bash

    通过YAML文件删除pod

    [root@node1 ~]# kubectl delete -f commands.yaml pod “command-demo” deleted

添加env参数后重新构建pod

[root@node1 ~]# kubectl apply -f commands.yaml pod/command-demo created [root@node1 ~]# [root@node1 ~]# kubectl get po -w NAME READY STATUS RESTARTS AGE command-demo 0/1 ContainerCreating 0 6s command-demo 0/1 Completed 0 29s ^C[root@node1 ~]# [root@node1 ~]# kubectl logs command-demo hello world

  1. 这意味着你可以将那些用来设置环境变量的方法应用于设置命令的参数,其中包括了ConfigMapsSecrets
  2. > 说明:环境变量需要加上括号,类似于`"$(VAR)"`。这是在 `command` `args` 字段使用变量的格式要求。
  3. <a name="2413e318"></a>
  4. # 5-8 为容器定义相互依赖的环境变量
  5. ---
  6. 当创建一个 Pod 时,你可以为运行在 Pod 中的容器设置相互依赖的环境变量。 设置相互依赖的环境变量,你就可以在配置清单文件的 `env` `value` 中使用 `$(VAR_NAME)`
  7. 在本练习中,你会创建一个单容器的 Pod Pod 的配置文件定义了一个已定义常用用法的相互依赖的环境变量。 下面是 Pod 的配置清单:
  8. ```yaml
  9. apiVersion: v1
  10. kind: Pod
  11. metadata:
  12. name: dependent-envars-demo
  13. spec:
  14. containers:
  15. - name: dependent-envars-demo
  16. args:
  17. - while true; do echo -en '\n'; printf UNCHANGED_REFERENCE=$UNCHANGED_REFERENCE'\n'; printf SERVICE_ADDRESS=$SERVICE_ADDRESS'\n';printf ESCAPED_REFERENCE=$ESCAPED_REFERENCE'\n'; sleep 30; done;
  18. command:
  19. - sh
  20. - -c
  21. image: busybox
  22. env:
  23. - name: SERVICE_PORT
  24. value: "80"
  25. - name: SERVICE_IP
  26. value: "172.17.0.1"
  27. - name: UNCHANGED_REFERENCE
  28. value: "$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
  29. - name: PROTOCOL
  30. value: "https"
  31. - name: SERVICE_ADDRESS
  32. value: "$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
  33. - name: ESCAPED_REFERENCE
  34. value: "$$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
  1. 依据清单创建 Pod:

    1. kubectl apply -f https://k8s.io/examples/pods/inject/dependent-envars.yaml
    2. pod/dependent-envars-demo created
  2. 列出运行的 Pod:

    1. kubectl get pods dependent-envars-demo
    2. NAME READY STATUS RESTARTS AGE
    3. dependent-envars-demo 1/1 Running 0 9s
  3. 检查 Pod 中运行容器的日志:

    1. kubectl logs pod/dependent-envars-demo
    2. UNCHANGED_REFERENCE=$(PROTOCOL)://172.17.0.1:80
    3. SERVICE_ADDRESS=https://172.17.0.1:80
    4. ESCAPED_REFERENCE=$(PROTOCOL)://172.17.0.1:80

如上所示,你已经定义了 SERVICE_ADDRESS 的正确依赖引用, UNCHANGED_REFERENCE 的错误依赖引用, 并跳过了 ESCAPED_REFERENCE 的依赖引用。

如果环境变量被引用时已事先定义,则引用可以正确解析, 比如 SERVICE_ADDRESS 的例子。

当环境变量未定义或仅包含部分变量时,未定义的变量会被当做普通字符串对待, 比如 UNCHANGED_REFERENCE 的例子。 注意,解析不正确的环境变量通常不会阻止容器启动。

$(VAR_NAME) 这样的语法可以用两个 $ 转义,既:$$(VAR_NAME)。 无论引用的变量是否定义,转义的引用永远不会展开。 这一点可以从上面 ESCAPED_REFERENCE 的例子得到印证。

5-9 为容器和 Pods 分配 CPU 资源


创建一个命名空间

创建一个命名空间,以便将 本练习中创建的资源与集群的其余部分资源隔离。

  1. kubectl create namespace cpu-example

指定 CPU 请求和 CPU 限制

要为容器指定 CPU 请求,请在容器资源清单中包含resources:requests 字段。要指定CPU限制,请包含resources:limits

在本练习中,你将创建一个具有一个容器的 Pod。这是 Pod 的配置文件 cpu-request-limit.yaml :

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: cpu-demo
  5. namespace: cpu-example
  6. spec:
  7. containers:
  8. - name: cpu-demo-crt
  9. image: nginx
  10. resources:
  11. limits:
  12. memory: "128Mi"
  13. cpu: "1"
  14. requests:
  15. cpu: "0.5"
  16. args:
  17. - -cpus
  18. - "2"

配置文件的args部分提供了容器启动的参数。-cpus "2"参数告诉容器尝试使用2个CPU。

  • 创建Pod:

    1. kubectl apply -f cpu-request-limit.yaml --namespace=cpu-example

    为命令取别名alias kc="kubectl"。 输出pod的yaml文件kc get po cpu-demo -o yaml

  • 验证所创建的pod处于running状态

    1. kubectl get pod cpu-demo --namespace=cpu-example
  • 查看显示关于pod的详细信息

    1. kubectl get pod cpu-demo --output=yaml --namespace=cpu-example
  • 输出显示Pod中的一个容器的CPU请求为 500 milli CPU,并且CPU 显示为1个

    1. resources:
    2. limits:
    3. cpu: "1"
    4. requests:
    5. cpu: 500m

5-10 用节点亲和性把 Pods 分配到节点

本页展示在Kubernetes集群中,如何使用节点亲和性把Kubernetes Pod 分配到特定节点。


给节点添加标签

  1. 列出集群中的节点及其标签:

    1. kubectl get node --show-labels

    image.png
    在前面的输出中,可以看到node2节点有个disktype=ssd的标签

  2. 选择一个节点,给它添加一个标签:

    1. kubectl label nodes <your-node-name> disktype=ssd

    image.png

依据强制的节点亲和性调度Pod

下面清单描述了一个 Pod,它有一个节点亲和性配置 requiredDuringSchedulingIgnoredDuringExecution ,disktype=ssd。这意味着pod只会被调度到具有 disktype=ssd 标签的节点上。

affinity.yaml

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: nginx
  5. spec:
  6. affinity:
  7. nodeAffinity:
  8. requiredDuringSchedulingIgnoreDuringExecution:
  9. nodeSelectorTerms:
  10. - matchExpressions:
  11. - key: disktype
  12. operator: In
  13. values:
  14. - ssd
  15. containers:
  16. - name: nginx
  17. image: nginx
  18. imagePullPolicy: IfNotPresent
  1. 执行(Apply)此清单来创建一个调度到所选节点上的pod:

    1. kubectl apply -f affinity.yaml
  2. 查看结果:

    1. kubectl describe pod nginx
    2. kubectl get pods --output=wide

    image.png

5-11 将ConfigMap 中的键值对配置为容器环境变量


  • 创建一个包含多个键值对的ConfigMap。以下是:configmap-multikeys.yaml

    1. apiVersion: v1
    2. kind: ConfigMap
    3. metadata:
    4. name: special-config
    5. namespace: default
    6. data:
    7. SPECIAL_LEVEL: very
    8. SPECIAL_TYPE: charm

    创建ConfigMap:

    1. kubectl create -f configmap-multikeys.yaml
  • 使用envFrom将所有ConfigMap的数据定义为容器环境变量,ConfigMap中的键成为Pod中的环

境变量名称。创建文件:pod-configmap-envFrom.yaml

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: dapi-test-pod
  5. spec:
  6. containers:
  7. - name: test-container
  8. image: k8s.gcr.io/busybox
  9. command: [ "/bin/sh", "-c", "env" ]
  10. envFrom:
  11. - conifigMapRef:
  12. name: special-config
  13. restartPolicy: Never
  • 将configmap与pod结合的文件:configmap.yaml ```yaml 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” ```
  • 查看configmap命令:kubectl get configmap

5-12 容器root用户vs privileged(特权用户)


以root用户运行

Docker允许隔离其主机OS上的进程,功能和文件系统,并且出于实际原因,大多数容器默认实际上以root用户身份运行。

  1. [root@master ~]# docker run -it busybox sh
  2. Unable to find image 'busybox:latest' locally
  3. latest: Pulling from library/busybox
  4. 5cc84ad355aa: Pull complete
  5. Digest: sha256:5acba83a746c7608ed544dc1533b87c737a0b0fb730301639a0179f9344b1678
  6. Status: Downloaded newer image for busybox:latest
  7. / # whoami
  8. root # Notice here, we are still root!
  9. / # id -u
  10. 0
  11. / # hostname
  12. e0c1bb5f966f
  13. / # sysctl kernel.hostname=Attacker
  14. sysctl: error setting key 'kernel.hostname': Read-only file system

启动privileged

  1. [root@master ~]# docker run -it --privileged busybox sh
  2. / # whoami
  3. root # Root again!
  4. / # id -u
  5. 0
  6. / # hostname
  7. 0974f256c8b7
  8. / # sysctl kernel.hostname=Attacker
  9. kernel.hostname = Attacker # Except now we are privileged.
  10. / # hostname
  11. Attacker

Kubernetes通过Security Context提供了相同的功能:

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: nginx
  5. spec:
  6. containers:
  7. - name: nginx
  8. image: nginx
  9. securityContext:
  10. privileged: true

5-13 为 Pod 创建非 root 用户运行

要为 Pod 设置安全性设置,可在 Pod 规约中包含securityContext 字段。securityContext 字段值是一个PodSecurityContext对象。你为 Pod 所设置的安全性配置会应用到 Pod 中所有 Container 上。
下面是一个 Pod的配置文件,该Pod定义了securityContext和一个emptyDir卷。创建security-context.yaml

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: myapp
  5. labels:
  6. name: myapp
  7. spec:
  8. securityContext:
  9. runAsUser: 1000
  10. runAsGroup: 3000
  11. fsGroup: 2000
  12. volumes:
  13. - name: security
  14. emptyDir: {}
  15. containers:
  16. - name: myapp
  17. image: busybox
  18. command: ["sh","-c","sleep 1h"]
  19. volumeMounts:
  20. - name: security
  21. mountPath: /data/demo
  22. securityContext:
  23. allowPrivilegeEscalation: false
  24. resources:
  25. limits:
  26. memory: "128Mi"
  27. cpu: "500m"

在配置文件中,runAsUser字段指定 Pod 中的所有容器内的进程都使用用户 ID 1000 来运行。runAsGroup 字段指定所有容器中的进程都以主组 ID 3000 来运行。 如果忽略此字段,则容器的主组 ID 将是root(0)。 当 runAsGroup 被设置时,所有创建的文件也会划归用户 1000 和组 3000。由于fsGroup 被设置,容器中所有进程也会是附组 ID 2000 的一部分。卷/data/demo 及在该卷中创建的任何文件的属主都会是组 ID 2000。