原文: How to Become a Certified Kubernetes Application Developer

本指南是我对最近通过的K8S认证开发者考试的学习笔记总结。

即使你对认证不感兴趣,你可以把K8S看作一站式商店:通过无数的例子解释所有主要的技术概念。

此外,它还有一些来自我准备和参加考试经验和附加内容。

在写本文时,CKAD的课程(研究领域和每个领域的比重)如下:

  • 13% – 核心概念
  • 18% – 配置
  • 10% – 多容器 Pods
  • 18% – 可观察性
  • 20% – Pod 设计
  • 13% – 服务 & 网络
  • 8% – 状态持久性

本指南涵盖了这些课程,只是顺序不同。

我假设你已经知道K8S的基础知识(基本的containers和pods),并希望将你的技能提高一个新的水平。通过这个考试将使你的简历脱颖而出,因为它是一个非常抢手的认证。

内容

  • Kubernetes简介
  • 如何管理Kubernetes的集群
  • 超越Pods和Deployments
  • 如何配置Pods和Containers
  • 如何在Kubernetes调度Pods
  • Kubernetes的存储
  • Kubernetes的网络和安全
  • Kubernetes中的观察性和调试
  • 技巧和窍门
  • 实践时间
  • 结论

介绍Kubernetes

Kubernetes是一项技术,允许在多个节点上轻松部署和管理容器化程序。它的一些最突出的特点是:

  • Container 配置和部署
  • 系统监控
  • 持久性存储
  • 自动调度
  • 自动扩展和自动修复

还有更多。

Kubernetes的工作方式是声明式的:你在集群中定义你想要的状态,Kubernetes是确保集群始终处于这种状态。

  • REST 调用
  • 命令行工具kubectl。你可以通过这个教程,在你的机器上安装它,点击这里

如果你不能访问一个Kubernetes集群,我建议在你的本地机器上安装minikube。一旦完成安装并启动,允许以下命令来创建你的第一个Pod。

kubectl run --image=busybox --restart=Never --rm -it -- echo "Welcome to Kubernetes!!"

这个pod一旦打印出欢迎消息,它将被自动删除。

如果管理Kubernetes集群

管理集群不是成为CKAD的课程的一部分。就考试而言,你不需要知道如何如何创建一个集群,管理nodes等。

Namespaces

Namespaces 允许你创建虚拟集群,也就是说将资源隔离在同一物理集群的不同部分。比如说:

  • 将以下不同的环境隔开 development, stage, QA, and production
  • 将一个复杂的系统分解成更小的子系统,你可以为前端组件创建一个Namespace,也可以为后端组件创建另一个Namespace,以此类推。
  • 避免了名称冲突: 同一资源可以在不同的Namespace中以相同的名称创建。这使得创建不同的环境 (think stage and prod),看起来相同。

你可以通过运行以下命令创建一个Namespace:

kubectl create ns my-namespace

资源配额

如果你想限制开发者在Namespace中可以创建的资源数量(包括物理资源和Kubernetes对象,如pods),你可以使用资源配额

例如,你可以通过ResourceQuota下的Kubernetes secrets 的数量来限制用户在集群上创建:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: my-quota
spec:
  hard:
    secrets: "2"

在一个文件中,我们称之为my-qouta.yaml,可以简单运行kubectl apply -f my-quota.yaml

kubectl create quota my-quota --hard="secrets=2"

更多的 秘密 在这个指南的后面。

Labels

Labels(标签)允许你在Kubernetes集群组织资源,label是一个键值对,你可以在创建资源时,添加到资源上,也可以添加到现有资源。你能使用labels过滤资源。

例如:

  • 你想只显示 backend pods。你将label tier=backed添加到pods(键和值都是任意的,你可以使用任何你想的),然后运行 kubectl get pods -l tier=backend去检索需要的pods。
  • 你想定义一个部署或服务的相关pods。告诉 deployment/service,它需要关注通过labels选择pods。

以下是一些常见的label-related的命令:

# 给Pods 添加一个label, 等等
kubectl label pods pod-name key=value
# 通过podname 删除pods上的label
kubectl label pods pod-name label_key-

# 删除一个pod,通过label选择器
kubectl label po -l app app-

# 显示 label
kubectl get pods --show-labels

# 通过lables选择pods
kubect get pods -l 'tier in (frontend,backend)'
kubect get pods -l tier=frontend,deployer=coolest-team

Annotations和labels相似,都是键值对,但Annotations不能用于选择资源,它们的目的是不同的。

Annotations通常为其他工具添加。比如,如果你运行Prometheus来收集指标,你可以把这个配置添加到你的描述符中。

metadata:
	labels:
		name: fluentd-elasticsearch
	annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port: '9102'

它让Prometheus将 scrapeport 的默认值分别改为 true9102

超越 Pods和部署

Multi-container pods是Kubernetes的基本单元。在大多数情况下,你可以认为它是一个容器,但是一个pod可以由多个容器组成。由于pod是短暂的,我们需要一种机制确保当我们现有的pod死亡时,新的pod被创建。

通过部署,你的可以定义一个理想的状态,例如,有三个应用程序的副本一直在运行,Kubernetes将努力实现并在集群中保持这种状态。

也可以轻松管理任何时候运行的副本数量,执行滚动更新,回滚到以前的版本等等。

还有很多工作负荷。

多容器 pod

一个pod可以运行一个以上的容器。容器间可以无缝通讯,因为它们在同一网络和而且它们可以使用 volumes 共享数据。

现在,让我们深入了解一些多容器pod设计模式,在本指南后面,我们将详细介绍volumes(卷),以及如何部署这些模式中的一些。

Sidecar(边车)

在这个模式中,我们有一个运行主程序的容器,同时还有另一个容器,运行次要任务 以增强主容器的功能

一个经典的例子是,在运行Web服务(主容器)的同时,还有一个处理日志、监控、刷新pod volume(卷)的数据,终端TLS等任务的side 容器

适配器

同样,你可以有你的主容器和一个次要容器, 转换主容器的输出

例如,想象一些你的主容器运行一个输出大量复杂日志的服务。你可以使用一个适配容器来简化这个输出,然后再发送给你的日志服务器,把主容器(以及服务的开发者)从这个服务中解放出来。

Ambassador

另外一个常见选择是使用以辅助容器, 作为一个代理,在你的主容器和外部之间充当代理。

例如,你可能有不同的数据库用于不同的容器,比如测试和生产。当你的主容器需要连接到一个数据库时,它可以根据环境确定适合的数据库给 ambassador容器。

服务

Pods 可以通过其他的Pods的IP地址,连接其他的Pods。然而,pods是短暂的,当它们死亡时,假设你有个复控制器,一个新的pod将被创建,有一个新的IP地址。这使人们很难直接使用IP连接到pods。

Kubernetes提供了一个名为服务的抽象,一个资源有固定的IP地址 ,并将请求发送到对应的pod。

不需要直接连接到pods,你可以通过它的IP地址到达它们的服务。或者通过DNS服务使用完整限定名称查找,这样会更好。此外,一些服务类型也将你的应用程序暴露在集群外面。

服务类型

你可以创建的主要服务类型:

  • ClusterIP – 在集群内暴露你的应用。如果你不指定类型,这就是Kubernetes创建的默认服务。
  • NodePort – 它会在集群中的每一个node打开一个端口,将你的应用程序暴露给世界。
  • LoadBalancer – 使用云服务提供商的负载均衡器向外部暴露服务。

如何在集群内部暴露你的应用程序

假设你想把你的应用程序my-app暴露给集群中的其他节点,端口为80。你可以创建一个命令部署你的应用程序。

kubectl create deploy my-app --image=my-app --port=80

这将创建集群内部其他的Pod只能通过IP访问的pod,如果pod重新启动,其IP将改变。

下一步是为这些pod创建一个ClusterIP服务。下面的命令创建一个可以通过80端口的服务,并将请求转发到的你的应用程序(80端口)。

kubectl expose deploy my-app --port=80

如何将你的应用程序暴露在集群之外

如果你想把你的应用服务暴露给世界,你可以使用这个配置创建一个NodePort服务。

kind: Service
apiVersion: v1
metadata:
	name: my-svc
spec:
    type: NodePort
    selector:
	    app: nginx
    ports:
    - protocol: TCP
    	port: 80 # 你可以到达该服务的端口
    	targetPort: 80 # 你在pod打开的端口

Ingress

服务并不是将你的应用程序给世界的唯一方法。你也可以用 Ingress对象作为你的集群的一个入口点。

除此之外,你还需要一个 Ingress控制器,比如Nginx,来实现对Ingress对象中定义的规则。

由于Ingresses不在CKAD的范围内,我们将转到其他的主题。

Jobs

一个Job将创建一个或多个pods 如果它们成功,将不会被重启启动。你可以使用它们来执行批处理, 一次性任务 ,如并行计算,备份一些文件,转换和导出一些数据等等。

除非另有说明, pod 将运行一次。你可以使用参数 completions来指定一项工作需要运行多少次才能被认为已经完成。默认情况下, pods将按照顺序运行,但你可以设置job,使它们并行运行。

如果它们没有成功运行, 你将它们配置为 Never 重启 (重试)。或者重启失败最多的backoffLimit 次数,
然后Kubernetes认为job已经失败。

下面是如何从命令行中创建一个简单的job:

 kubectl create job my-job --image=busybox -- echo "Hello World"

如果它们的job运行成功,其状态是COMPLETED,其pod(s)不会被删除,这样你就可以访问它们的日志。你可以通过以下方式看到它们。

kubectll get pods --show-all

在默认情况下,2分钟后它们在默认的pod列表中是不可见的。

Cronjobs

Kubernetes提供了Conjobs来创建需要定期或者在未来特定时间运行的jobs:定时清理和备份任务,更新TLS证书等等。

Kubernetes会尽力只运行另一个job来执行你在Cronjob配置中指定的任务。然而,有3个常见的问题你应该注意。

  • 不能保证job会 准确地在所需的时间运行。想象一下,你需要你的工作在 上午9点运行,你可以将startingDeadlineSeconds属性设置为x秒。这样,如果job没在上午9点x秒之后开始, 它将标记为失败,不会再运行。
  • 2个job可能被安排在同一时间 执行任务。在这种情况下,你需要确保任务是 idempotent ,如果任务被多次执行,执行的任务的结果也不会改变。
  • 没有job可以安排。 为了克服这个问题,请确保Cronjob能运行前一次未完成的job。

这是创建一个简单的Cronjob,每分钟打印出"Hello World"

kubectl create cronjob my-job --image=busybox --schedule="*/1 * * * *" -- echo "Hello World"

我推荐这个 网站 帮助你写出正确的cron定时任务。

接下的3个资源不是CKAD考试的一部分,但我认为你至少应该对它们有一个基本的了解。

Daemon sets

Daemon sets(守护设置)确保在你的集群的每一个 node 都有一个pod 调度。除此以外,pod总是在运行:如果pod死了或者有人删除了它,pod将被重新创建。

一个常见的使用场景是,Daemon sets(守护设置)用来收集每个node的日志和指标。但是,即使你不创建任何东西,它们已经在你的集群中运行了一些Daemo sets(守护设置): Kubernetese创建一个 Daemon set(守护设置),在每个node上运行kube-proxy 组件。

如果你从集群中移除一个node,Kubernetes不会在别的node上创建它的守护进程,因为这个这个node已经在运行Daemon set(守护设置)。如果你在集群中添加一个node,它会自动运行 Daemon set(守护设置)。

Stateful set

到目前为止,我们应该看到部署无状态的应用或者pods的工具,它们有自己的持久性储存,不随工具停止运行而消失。为了部署有状态的应用,你可以是使用 Stateful set(状态设置)

由于这不是CKAD考试的一部分,我们就不多讲了。

静态 pods

静态pods是由kubelet管理的颇多,而不是由Kubernetes API管理的。

要创建它们,你只需要创建一个普通的pod的配置文件和配置kubelet 扫描该目录。如何你重启kubelet后,它将创建pod,并在失败时重启pod。

Kubernetes将创建pod的镜像,这是Kubernetes API服务器中的一个副本。当你运行kubectl get pods,这个pod会出现,但是如果你试图用kubectl delete podName删除它,通过kubelet创建的静态pod 运行在这个node上,它将直接出现在pod列表中。

如何配置 Pods和Containers(容器)

我们刚刚看到在可以在Kubernetes集群上部署不同的工作负载。现在让我们花些时间学习如何配置工作负载中的pods和containers(容器)。

初始化容器

你可以使用 init containers 来设置你的pod初始状态: 向pod的卷写入一些数据,下载些文件等等。

你可以定义一个或者多个init容器,它们将按顺序执行,只在所有的容器完成后,pod才会运行,因此,init容器也可以用来让pod在执行前等待某个条件。

例如,你可以让一个pod在启动前等待另一个服务的容器完成启动和运行。

你可以通过在pod的定义的 spec部分添加类似的这样的内容定义init容器。

spec:
  initContainers:
  - name: init-myservice
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]
 ...

ConfigMaps

将你的应用程序与代码分开是你应该遵循的实践。ConfigMaps 允许你在Kubernetes这样做。

ConfigMaps 是使用 存储非机密数据的键值对, 我们将在下一节看到 Secrects 存储机密数据(如密码)。

# 通过参数传递数值
kubectl create configmap my-map --from-literal=db_url=my-url --from-literal=username=username

# 通过文件获取数值
kubectl create configmap another-map --from-file=my-file

一旦创建,你的应用程序可以在同一个namespace的pod以多种方式使用ConfigMaps。

  • 作为命令行参数
  • 作为环境变量
  • 从只读卷中的一个文件

让我们看看一个pod的定义使用这些办法从ConfigMap中读取。

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  restartPolicy: Never
  containers:
    - name: busybox
      image: busybox
      args:
        - /bin/sh
        - -c
        - "echo $MY_VARIABLE" # This value comes from the configmap
      env:
      - name: MY_VARIABLE
        valueFrom:
          configMapKeyRef:
            name: another-map           
            key: my-key # To load individual keys from a map
      envFrom:
      - configMapRef:
          name: another-map # To import all values from a map as env variables
      volumeMounts:
      - name: config
        mountPath: "/config" # This will contain files with the data stored in my-map
        readOnly: true
  volumes:
    # Name to refer to this volume in the pod
    - name: config
      configMap:
        # Name of the ConfigMap
        name: my-map

Secrets

Secrets跟ConfigMaps很相似,但你用它们来存储 机密数据。创建和管理Secrets是一个敏感的话题。请务必阅读文档。在这里,我们将学习基础知识。

创建Secrets的最简单方法是:

#从一个字面量创建 secret
kubectl create secret generic secret-name --from-literal=password=password
#从文件内容来创建 secret
kubectl create secret generic secret-name --from-file=path-to-file

然后,你可以将你的secrets导入pod,以环境变量或者文件的形式:

apiVersion: v1
kind: Pod
metadata:
  name: another-pod
spec:
  restartPolicy: Never
  containers:
    - name: busybox
      image: busybox
      args:
        - /bin/sh
        - -c
        - "echo $MY_VARIABLE"
      env:
      - name: MY_VARIABLE
        valueFrom:
          secretKeyRef:
            name: my-secret2
            key: username # To load individual keys from a map
      envFrom:
      - secretRef:
          name: yet-another-secret # To import all values from a map as env variables
      volumeMounts:
      - name: secret-volume
        mountPath: "/secrets" # This will contain files with the data stored in my-map
        readOnly: true
  volumes:
    # Name to refer to this volume in the pod
    - name: secret-volume
      secret:
        # Name of the Secret
        secretName: my-secret

Probes

Probes(探针)允许你设置判断pod是否健康的规则,是否为服务流量做好准备,是否准备好启动。在本节的最后,我将介绍一个带有这些Probes(探针)的样本。

Liveness probes(活性探针)

Kubernetes 会自动重启 crashed containers (崩溃的容器)。然而,这不会考虑到下面的bug (像你的应用程序的无限循环), 死锁等等。 你可以定义 Liveness probes(活性探针) 检测你的应用程序是否健康。 Kubernetes 使用这探针来决定是否要重启容器。

你可以定义三种Liveness probes(活性探针):

  • HTTP Get, 在你的应用程序中定义一个HTTP路由(如/health),Kubernetes可以通过HTTP请求获取应用程序是否健康
  • TCP socket 探针,尝试建立一个TCP连接到一个特定的端口。如果连接不能建立,应用程序将重启。
  • Exec 探针, 在容器内运行一个命令,如果退出状态码不是0,将重启容器。

Readiness probes(准备就绪探针)

想象一下,一个服务在启动时需要加载一个大的配置文件。容器本身是健康的(这可以用上面的Liveness probes检查),但是没有启动完,接收请求。

Kubernetes使用Readiness probes(准备就绪探针)来确定你的应用程序是否准备好,处理请求。

关于Liveness probes(活性探针)的东西也适用于Readiness probes(准备就绪探针):

  • 它们可以被配置为初始延迟,超时,阈值,周期等等。
  • 它们可以基于HTTP get调用,TCP socket连接,或者在容器内执行命令。

然而,当Liveness probes(活性探针)告诉Kubernetes信息,Kubernetes让不健康的容器重启。Readiness probes(准备就绪探针)用来从可以接收请求的容器池移除没准备好的容器。一旦容器准备好了,它就可以为请求提供服务。

Startup probes(启动探针)

Startup probes(启动探针)只能在启动期间使用,例如应用程序启动缓慢。如果Startup probes(启动探针)返回成功,liveness and readiness 探针(如果已经配置),将定期运行。

例如

这个pod 将执行以下命令:

  • sleep 20
  • touch /tmp/healthy
  • sleep 30
  • rm -rf /tmp/healthy
  • sleep 600

在配置探针时,你应该注意的最基本参数是:

  • initialDelaySeconds 开始探针延迟秒数
  • periodSeconds 探针频率
  • timeoutSeconds 探针的超时时间
  • failureThreshold Kubernetes尝试次数,超过这个,将放弃尝试
    根据我们的配置, 这些探针将在pod创建后10秒后开始。在前20秒内, 文件 /tmp/healthy是不存在。因此, readiness probe(准备就绪探针)返回失败。 在接下30秒内readiness probe(准备就绪探针)返回成功,直到我们删除/tmp/healthy文件。

liveness probe(活性探针) 是简单的echo "I'm healthy"命令。如果它能运行,说明这个pod是健康的,否则,这个pod 要重启。

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - args:
    - /bin/sh
    - -c
    - "sleep 20; touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600"
    image: busybox
    name: tmp
    livenessProbe:
     exec: # If this command can be run, the pod is healthy
       command:
       - echo
       - "I'm healthy"
     initialDelaySeconds: 5 # Only start probing after 5 seconds
     periodSeconds: 5 # Probe every 5 seconds
    readinessProbe:
     exec: # If this command can be run, the pod is ready to serve traffic
       command:
       - cat
       - /tmp/healthy
     initialDelaySeconds: 5 # Only start probing after 5 seconds
     periodSeconds: 10 # Probe every 10 seconds
  dnsPolicy: ClusterFirst
  restartPolicy: Never
status: {}

执行这个命令可以看到pods的状态:

kubectl get pods --watch

看到Ready 列的值 从 0/1, 到 1/1 和回到 0/.1?

运行这个命令,以获得一个事件列表和探针如何失败的,正如我们期待的。

kubectl describe pods my-pod

容器资源

你可以通过定义创建一个pod:

  • 它运行所需的最小资源量,被称为 requests.
  • pod可以使用的最大资源量,被称为 limits.

Kubernetes在调度pod时,会考虑到你请求的资源。它不会在一个没有足够容量的节点上调度pod,不管当前资源的消耗情况如何。

例如, pod A和podB 在node N上运行,Kubernetes将通过下面的方法计算新的pod C 是否可以在node N上运行:

Total capacity of N - (resources requested by A + resources requested by B) <= resources requested by C( node N的总资源减去 A B需要的最小资源后,是否少于pod C需要的最小资源量)

即使pod A和B没有使用它们请求的最小资源, Kubernetes 承诺 A和B会有它们所请求的最小资源可使用。这是为什么当前的资源使用情况不在前面的公式表达。

如果一个pod超过它的资源限制(它可以使用的最大资源量时),会发生两种情况:

  • 如果它超过CPU限制,它将被杀掉
  • 如果它超过内存限制,它将会被重新启动

现在让我们创建一个pod,设置好它的最小内存和cpu和最大内存和cpu使用:

apiVersion: v1
kind: Pod
metadata:
  name: resource-limited-pod
spec:
  restartPolicy: Never
  containers:
    - name: busybox
      image: busybox
      args:
        - /bin/sh
        - -c
        - "echo Hello Kubernetes; sleep 3600"
      resources:
        requests:
          memory: "300Mi"
          cpu: 0.2
        limits:
          memory: "1Gi"
          cpu: 0.5

作为一个练习,设置分配的内存和cpu高得离谱一些的值,去创建pod。你会看到pod从未准备好,将有输出

kubectl describe pods resource-limited-pod

会告诉你为什么.

怎样在Kubernetes中调度Pods

这一部分不是CKAD考试教程的一部分。但我相信,你应该对我在这样阐述的概念有一些基本的了解,因为你在使用Kubernetes的过程中,它们很可能会出现。

Kubernetes允许你通过多种机制来指定你希望的pods,被安排到哪个nodes。

最简单的是使用 nodeSelector 字段,根据一个label选择一个node。其他功能的使用,允许更复杂的设置。

Taints and tolerations

你可以 taint(污损) 一个node 去防止在其中的pods被调度, 不用去修改pods本身。 当你 taint(污损)一个node, Kubernetes 会调度 这个node 中的 pods,容忍 taint(污损)。

你可以看到这个动作,如果你运行:

kubectl describe node master.Kubernetes

在前一个命令的输出中,你会看到这样一行字:

Taints: node-role.kubernetes.io/master:NoSchedule

这种污损会阻止pods在master node中被调度 (除非pods能容忍这种taint).

有三种类型的taint:

  • NoSchedule: 如果node不能容忍taint,Kubernetes不会在node中调度pod。
  • PreferNoSchedule: pods不能容忍taint,不会调度在一个node中,除非pods不能调度到其他地方。
  • NoExecute: 如果已经在node中运行的pods不容忍taint,就把pods从node中移除出去。

Taints 并不保证 pods 会被调度到特定的nodes。 为了实现这一点,你需要探索 Node affinity (node亲和性) 概念。

Node亲和性

Node 亲和性 告诉 Kubernetes 去 调度pods到特定的nodes 上.

想象一下,你有一个服务S,需要特定硬件去运行。你像把专用硬件用于这个服务,并希望S的pod在上面运行。你怎样才能实现?

你可以taint这种设备上的nodes,以便只有来自服务S的pod可以在这些node上被调度。但是,Kubernetes可以在其他不不具备的所需硬件上的nodes部署这些pods。

我们可以看到: 组合taints和node 亲和性来确保只有来自服务S的pod 在我们的专门的nodes运行。

  • Node 亲和性调度将S的pods安排到专门的nodes(而不是其他节点)。
  • Taints 确保没有其他的pod会被安排在专门的nodes,只有来自服务S的pod。

Kubernetes的存储

默认情况下,当一个pod内的容器重启,容器内的数据会丢失,设计上是这样的(无状态)。

如果你想让数据比容器,pod,甚至是node都持久,你需要用。另外,如果一个pod是由多个容器组成的,那么这个pod 的卷可以被所有的容器使用。

在你定义了卷后,在pod层,你需要在每个访问的容器装载它。

有很多类型的卷,以满足不同的使用情况(通常,取决于你想在 pod 被销毁时发生什么)。一些常见的卷类型:

  • emptyDir: 创建一个初始是空的目录。这是在pod不同容器之间共享文件的简单方法。
  • hostPath: 将一个文件或者目录从node的文件系统挂载到pod,在pod被删除后,这些文件将保留在主机种。
  • 在AWS、GCP或者 Azure云上的卷.

关于这些和其他类型的卷的更多信息,请参考文档。

在容器种挂载一个卷,你的pod的描述符(我们把这个文件称为 with-mounted-volume.yaml),应该是这样的。

apiVersion: v1
kind: Pod
metadata:
  name: with-mounted-volume
spec:
  volumes: # Volumes are defined at the pod level
  - name: my-volume # We'll use this name to mount the volume inside the pod
    hostPath: # Here we define the type of volume we want
      path: /var/my-k8s-volume
  containers:
  - args:
    - /bin/sh
    - -c
    - "sleep 3600"
    image: busybox
    name: bb
    volumeMounts:
    - name: my-volume
      mountPath: /tmp/my-volume-path
  restartPolicy: Never

由于我们已经创建了一个hostPath的卷,它的内容寿命将超过pod。让我们试一下。

# 创建一个 pod
kubectl apply -f with-mounted-volume.yaml
# 创建一个文件 并挂载到本地
kubectl exec -it with-mounted-volume -- sh -c "echo 'Inside the pod' > /tmp/my-volume-path/newfile"
# 尝试去读取一个文件
kubectl exec -it with-mounted-volume -- cat /tmp/my-volume-path/newfile
# 删除 pod
kubectl delete pods with-mounted-volume
# 创建一个pod
kubectl apply -f with-mounted-volume.yaml
# 查看 `/tmp/my-volume-path` 下的内容,看它是否存在
kubectl exec -it with-mounted-volume -- cat /tmp/my-volume-path/newfile

Revisiting 多容器的pods

现在我们熟悉了卷,让我们创建一个多容器的pod,它将用在一个挂载了卷的容器间共享日期。

  • 我们的 pod 描述定义 在文件communicating-containers.yaml
  • 我们将会一个带有2个 busybox 容器的pod。
  • 其中一个容器将"Hello World" 追加到一个文件中。
  • 另一个容器可以访问这个文件的内容(以及放在这个共享卷的任何东西)。我们将查看这个共享文件夹,看另一个容器是如何将Hello World追加到文件里的。
    这就是我们定义的pod:
apiVersion: v1
kind: Pod
metadata:
  name: communicating-containers
spec:
  volumes:
  - name: vol
    emptyDir: {}
  containers:
  - args:
    - sh
    - -c
    - "while true;do echo 'Hello World' >> /etc/shared/log; sleep 1; done"
    image: busybox
    name: container-1
    volumeMounts:
    - name: vol
      mountPath: /etc/shared/ # The container can access the volume here
  - args:
    - sh
    - -c
    - "sleep 3600"
    image: busybox
    name: container-2
    volumeMounts:
    - name: vol
      mountPath: /etc/a-different-location # The volume can be mounted at different locations on each containers
  restartPolicy: Never

让我们创建pod:

kubectl apply -f communicating-containers.yaml

一旦pod运行起来,我们就可以tailcontainer-2文件,查看文件尾部内容:

kubectl exec -it communicating-containers -c container-2 -- tail -f /etc/a-different-location/log

尽管container-1写东西到 log,但由于log在一个共享卷中,container-2可以看到这个文件。

Persistent Volumes(持久卷)

Persistent Volumes(PVs 持久卷)和 Persistent Volume Claims(PVCs 持久卷申领)通过存储技术跟pod解耦。 PVs是通过集群管理员创建 或者动态根据 存储类型.

相对我们之前创建的卷,在pod层定义的,PV(持久卷)有自己的生命周期,独立于任何pod。

在创建PVs 后,用户 能创建 Persistent Volume Claims(持久卷申领)来获得它们需要的存储,而不需要关心其存储的实际基础设施

例如

首先,我们定义一个 Persistent Volume(持久卷)在pv.yaml 文件:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: myvolume
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
    - ReadWriteMany
  storageClassName: normal
  hostPath:
    path: /etc/foo

对于 Persistent Volume Claim(持久卷申领),在文件pvc.yaml描述:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc
spec:
  storageClassName: normal
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi

现在, 让我们在集群中创建资源。

# 首先, 创建Persistent Volume(持久卷)
kubectl apply -f pv.yaml
# 然后, 创建the Persistent Volume Claim(持久卷声明)
kubectl apply -f pvc.yaml

我们可以从前面的例子获取pod的定义,稍作调整,开始使用这个持久卷,代替我们定义的hostPath,这是我们唯一要改的地方。

volumes:
  - name: mypd # 当我们配置pod时,要参考这个卷
    persistentVolumeClaim: # 替换掉hostPath
      claimName: mypvc # 名称是我们刚才创建的pvc 

Kubernetes中的网络和安全

关于安全问题,这些概念是你应该熟悉的最低限度,以便通过CKAD考试。

Network Policies(网络策略)

默认情况下,所有的ingress流量(即流入你的应用程序的流量)和egress流量(即你的应用程序流出的流量)都是允许的。如何pod都可以连接到其他的pod,即使它们在不同的namespaces。

你可以定义网络策略,根据不同的标准控制ingress流量和egress流量

为了说明这一点,我们创建一个网络策略,只允许带有label access: allowed的pod访问的我们的数据库:

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: access-db-policy # 起个名称
spec:
  podSelector:
    matchLabels:
      app: db # 数据库pods选择
  ingress: # 允许的 ingress 流量
  - from:
    - podSelector: 
        matchLabels: 
          access: allowed # 只有具有该label的pod才能向数据库发送流量

Security Context(安全背景)

当配置一个Security Context(安全背景),你可以启用一些安全功能,比如防止容器以root身份运行,选择什么用户运行pod等等,下面是一个例子:

spec:
  securityContext:
    runAsUser: 1000 #这将为这个pod层的每个容器设置用户,但可以被覆盖。
  containers:
  - name: my-container
     image: alpine
     securityContext:
     	runAsUser: 1001 #    这覆盖在pod层上的用户设置 	
  - name: another-container
	....

Services Accounts(服务账号)

你的应用程序可以连接到API 服务器进行数据交换。例如,检索有关pod信息在一个namespace。Service accounts(服务账号) 允许应用服务对API服务器进行认证,以便它们进行数据交互。

这是一个 default service account (默认的服务账号),但是这不一个好的实践,因为不是每个应用程序都需要在API服务器上执行相同的操作。

创建service account(服务账号)的最简单的方法是通过命令行:

kubectl create serviceaccount my-sa

一旦创建, 你一个分配 serviceAccountName 字段 (pod 描述符)分配给一个pod:

spec:
  serviceAccountName: my-sa
  containers:
  - name: web-server
  ...

一旦应用程序被认证,下一步看它是否有适当的权限来完成它所要的操作,也就是说, 查看应用程序是否被授权。

Role Based Access Control(基于角色的访问控制)

管理授权的一个常见办法是 Role Based Access Control (RBAC) (基于角色的访问控制)。 Service accounts(服务账号) 被链接到一个或者多个role(角色)。 每个role(角色)都有在某个资源上执行特定操作的权限。

RBAC 授权是通过两个独立的步骤来定义的:

  • Role 创建, 使用 Role和ClusterRole 资源, 以指定在某些资源上可以执行的操作。
  • 绑定 roles到账号上, 通过使用 RoleBinding 和 ClusterRoleBinding。

正如你所想,包含前缀Cluster的资源在整个集群范围内可用,而其他的资源只在特定的namespace中定义。

由于这不属于CKAD考试的范围,我们将不讨论如何在你的集群中创建和配置RBAC的细节。

Kubernetes中的可观察性和调试

一旦你部署你的应用程序, 你如何知道你的集群发生了什么?

我们已经介绍了Probes作为一种机制决定是否重启pod,以及pod是否为网络流量做好了准备。在这里,我们将看到更多的机制来更好地了解我们的集群状态,并对任何问题故障排除。

获取Pod的基本信息

访问pods的当前状态,有两个基本选项。这是第一选项:

kubect get pods

这将显示你的pods STATUS, AGE, RESTARTS的数量和多少pods内的容器处于 READY状态. 你可以用这个命令去显示pod的ip地址、labels等等。

第二个选项提供了更详细的信息:

kubectl describe pods my-pod

在这个命令的输出的最底部,你会发现一个事件列表,在出差的情况下会给你提示:

  • Liveness/readiness probes 失败
  • 拉取镜像出现错误
  • 没有足够的内存分配给pod

等等。

如何获取容器的日志

如果一个pod正在运行,你可以使用以下命令访问它的日志。

kubectl logs pod-name [-c container-name] [-n namespace]

如果你的pod有不止一个容器,你只需传递容器名称。同样,从不同的namespace中检索日志要用namespace 标志符(-n)

你甚至tail日志 -f 标志符,看到日志尾部,获取最新的变化:

kubectl logs -f pod-name [-c container-name] [-n namespace]

这对认证来讲是足够了。然而, 在真实的生产环境中,手动检查日志是很麻烦和低效的。 如果你在用GCP(谷歌云),你会希望用其他技术来管理你的日志和服务,像StackDriver技术。如果你想了解更多的有关StackDriver和GCP的信息,请务必查看我的GCP指南.

处理故障的小技巧

虽然在排除故障时没有灵丹妙药, 但从pod层开始通常是一个好注意,可以从可以找到问题的根本原因。

你的基本工具是我们上面提供的命令,除此之外,你可以随时在pod里打开一个终端。

# 假设你的容器镜像使用 sh
kubectl exec -it my-pod -c container -n namespace -- sh

不建议 用这种方式去解决pod的内部问题, 因为根据设计,pods是短暂的,在pods重启后,你的修改会丢失, 问题会复现。 当然, 这是一个好的方法去很好地了解问题。

获取你的pod或者node的指标,你可以运行以下命令。

kubectl top pod pod-name

这些工具帮助你解决日常操作中最常见的问题。有太多的事件会出差,在这里不做解释了。如果你面对一些你无法解决的事情,我建议你访问这个,调试你的应用程序

技巧和窍门

这是我发现的一些技巧,在我日常的Kubernetes工作中,特别是在通过CKAD考试时很有用。提高考试速度和效率部分。考虑到这点,让我们来看看可以做些什么来表现得更好。

设置别名

你将反复输入相同的命令。一旦开始考试,请创建以下别名。

# 重要的别名,我强烈建议设置它们
alias k='kubectl'
alias kg='k get'
alias kgpo='k get pods'
alias kdpo='k describe pod'
alias kd='k describe'

# 我还发现这个非常有用
alias kap='k apply -f'

# 我设置这个,只为工作需要
alias kgpoa='kgpo --all-namespaces'
alias kgpol='kgpo --show-labels'
alias kgpow='kgpo -o wide'
alias kgd='kg deploy'
alias kgs='kg svc'
alias kdd='kd deploy'
alias kds='kd svc'

更快搜索你的命令历史

我见过很多工程师,其中很多是高级工程师(seniors),笨拙按了20次以上的箭头键,在他们的命令历史中找东西。

不同的是,确保你自如地使用Ctrl + r,只有按下它并开始输入你要输入的命令的某些部分。然后,继续按Ctrl + r,直到你找到它。现在你可以按Enter运行它,或者简单开始修改后再运行。

这是一个节省大量时间的方法,并不是每个人都知道的。

熟悉文档

你可以在考试期间查阅文档,然而,这不是学习新东西的时候。访问文档获得yaml的模板,检查特定的参数等等。

确保你对 文档的结构 很熟悉和知道去哪里找东西。 使用 搜索功能 ,更快找到.

这份 Kubectl cheat sheet(简明手册) 是金子和 在考试期间可以为你你所用.

你不需要记住所有的东西

除了文档, kubectl--help 关于大多数命令的查看 (-h 是简写)。大多数时候,你会在那里找到问题的答案。

事实上,我建议你在查阅文档之前这样做,因为这要快得多。

想象一下,你想创建一个资源配额对象。你不记得你需要写的yaml或者创建它的命令。你知道 kubectl command--help 参数。那么在查阅文档之前,你应该先试一试这个:

# 我们可以创建得到创建资源配额对象所需要的东西
kubeclt create quota -h
# 例子
kubeclt create quota my-quota --hard="secrets=2"

你有大量的信息来源,因此你不需要记得住这么多的命令或者参数。

然而,考试期间时间非常有限,我认为值得熟记一些命令和任何使用它们。

快速生成一个基本的pod

这一点尤为重要。与其每一次需要一个pod时从文档中复制和粘贴,不如使用下面的命令获得一个描述符,你可以在以后修改它以满足你的需求。

# 运行一个pod nginx,并将其参数写入一个pod.yaml文件中
kubectl run nginx --image=nginx --restart=Never --dry-run=client -o yaml > pod.yaml
# 可以指定一些参数写入pod.yaml文件中
kubectl run nginx --image=nginx --restart=Never  --port=80 --labels=key=val --dry-run=client -o yaml > pod.yaml

别的有用的参数:

--rm # 在运行完成后立即删除pod
-it # 进入交换式终端,直接在pod中有运行命令

例如,你可以用它创建一个临时的pod,用它验证你的工作:

kubectl run tmp --image=busybox --restart=Never --rm -it -- /bin/sh

现在你可以运行命令 wget -O- svc:port ,看看你的服务是否在运行,网络策略是否在工作等等。

注意:

--dry-run=client -o yaml不仅适用于pod,也适用于很多资源。如果我们回到上一节,我们创建一个资源配额,我们可以得到这样描述符:

kubeclt create quota my-quota --hard="secrets=2" --dry-run=client -o yaml

使用 grep

你不需要深入了解正则表达式。只要使用关键词。例如,从冗长的kubectl describe pods my-pod 快速检索信息:

# -i 参数使搜索不区分大小写。有点慢,但对于非常短的文本来讲,这不会有什么影响。 
# -C 2 参数将显示匹配的最前2行和最后2行 (C 是 context的缩写)
kubectl describe pods my-pod | grep -i -C 2 labels
kubectl describe pods my-pod | grep -i -C 2 ip
...

观察pods 的状态变化

与其每隔几秒手动运行kubeclt get pods来查看变化,不如通过watch 参数来查看你的pods是如何变化:

kubectl get pods --watch

快速删除Pods

犯错是学习过程的一部分。在考试中你也会犯错。由于时间紧迫,我们需要确保在删除资源时不要等待很长时间,通过这些参数可以快速删除pods:

k delete pods my-pod --force --grace-period=0

用一个特定命令运行Pod

我发现学习如何用命令行想pod、job、cronjobs等传递命令很有用,比如这个例子:

kubectl run loop --image=busybox -o yaml --dry-run=client --restart=Never \
-- /bin/sh -c 'for i in {1..10}; do echo "Counting: $i"; done' \
> pod.yaml

这会产生一个文件pod.yaml:

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: loop
  name: loop
spec:
  containers:
  - args:
    - /bin/sh
    - -c
    - 'for i in {1..10}; do echo "Counting: $i"; done'
    image: busybox
    name: loop
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Never
status: {}

你可以运行一个命令,也可以运行多个命令链,就像一个小型的bash脚本一样:

# 运行一个特定的可执行文件
kubectl run busybox --image=busybox -it --restart=Never -- echo 'hello world'
# 在shell内运行(对运行多个命令很有用)
kubectl run busybox --image=busybox -it --restart=Never -- /bin/sh -c 'echo hello world'

这不是一个巨大的区别,但你不需要记住如何在pod的描述符中写,也不需要浪费时间去打开文档。 你只需要使用你已经知道的东西

roll out(展开)

熟悉rollout命令,以获得关于你的部署状态的信息。

我总是使用 --help 参数,来记住如何做我想做的事。

kubectl rollout -h

Bonus(奖励)

最后这些提示不是针对认证的,而是针对日常工作的:

  • 这个kube-ps1 模块 让你很容易知道你是在那个集群和namespace中操作的, 以防止犯错误,比如你不应该乱用 prod(生产环境)资源。
  • 另外,我推荐你看一下Helm。Helm 是一个软件包管理器,可以用来轻松部署应用程序 (像npm)。 Helm还允许你编写模板,你可以根据不同的值 (name, resource requests and limits, and so on)重复使用,创建对象。

练习时间

做事永远比读书学得多。所以我在这里留下一些我在准备考试已经解决的问题,供你练习。 查看项目地址这篇文章.

我建议在你先解决自己的所有问题,再看我上面提到资料。同时验证你做的东西: 检查日志,创建一个pod来连接你的服务等等。这也将有助建立你在考试中肌肉记忆。

尝试根据我的提示来练习这些练习,就像你真的在考试中一样,不分心,在浏览器里只打开一个标签--Kubernetes 的官方文档。

结论

本指南包含了将提高你的技能到新的水平,通过CKAD考试以及成为一名真正的Kubernetes开发者所需要的一切。这一切在你手中,你只需要付出一些努力,祝你好运!

你可以访问到我的博客www.yourdevopsguy.com在推特上关注我 获得更多高质量的技术内容。

如果你喜欢这篇文章,请分享它,因为你可以帮助别人通过考试或者找到工作。