最小可行的 Kubernetes

文章的翻译是在课程开始前夕准备的 “DevOps 实践和工具”.

最小可行的 Kubernetes

如果您正在阅读本文,您可能听说过一些有关 Kubernetes 的事情(如果没有,您是怎么来到这里的?)但是 Kubernetes 到底是什么? 这 “工业级容器的编排”? 或 《云原生操作系统》? 这究竟意味着什么?

老实说,我不是100%确定。 但我认为深入研究内部结构并了解 Kubernetes 在其多层抽象下到底发生了什么是很有趣的。 因此,为了好玩,让我们看一下最小的“Kubernetes 集群”实际上是什么样子。 (这会比 Kubernetes 的艰难之路.)

我假设您具备 Kubernetes、Linux 和容器的基本知识。 我们在这里讨论的所有内容仅用于研究/学习目的,请勿将其投入生产!

查看

Kubernetes 包含许多组件。 根据 维基百科,架构如下所示:

最小可行的 Kubernetes

这里至少显示了八个组件,但我们将忽略其中的大多数。 我想说的是,可以合理地称为 Kubernetes 的最低限度由三个主要组件组成:

  • 库贝莱
  • kube-apiserver(依赖于 etcd - 它的数据库)
  • 容器运行时(在本例中为 Docker)

让我们看看文档中关于它们的内容(., 英语.)。 首先 库贝莱:

集群中每个节点上运行的代理。 它确保容器在 Pod 中运行。

听起来很简单。 关于什么 容器运行时 (容器运行时)?

容器运行时是设计用于运行容器的程序。

信息非常丰富。 但如果你熟悉 Docker,那么你应该对它的作用有一个大概的了解。 (容器运行时和 kubelet 之间职责分离的细节实际上非常微妙,我在这里不再赘述。)

И API服务器?

API Server 是公开 Kubernetes API 的 Kubernetes 控制面板组件。 API服务器是Kubernetes控制面板的客户端

任何使用过 Kubernetes 的人都必须直接或通过 kubectl 与 API 进行交互。 这是 Kubernetes 的核心——它将我们所熟知和喜爱的 YAML 大山(?)转变为工作基础设施的大脑。 很明显,API 应该出现在我们的最小配置中。

先决条件

  • 具有 root 访问权限的 Linux 虚拟机或物理机(我在虚拟机上使用 Ubuntu 18.04)。
  • 这就是全部!

钻孔安装

我们需要在我们将使用的机器上安装 Docker。 (我不会详细介绍 Docker 和容器的工作原理;如果您有兴趣,可以查看 精彩文章)。 让我们安装它 apt:

$ sudo apt install docker.io
$ sudo systemctl start docker

之后,我们需要获取 Kubernetes 二进制文件。 事实上,对于我们的“集群”的初始启动,我们只需要 kubelet,因为要运行其他服务器组件,我们可以使用 kubelet。 为了在集群运行后与集群进行交互,我们还将使用 kubectl.

$ curl -L https://dl.k8s.io/v1.18.5/kubernetes-server-linux-amd64.tar.gz > server.tar.gz
$ tar xzvf server.tar.gz
$ cp kubernetes/server/bin/kubelet .
$ cp kubernetes/server/bin/kubectl .
$ ./kubelet --version
Kubernetes v1.18.5

如果我们只是跑会发生什么 kubelet?

$ ./kubelet
F0609 04:03:29.105194    4583 server.go:254] mkdir /var/lib/kubelet: permission denied

kubelet 必须以 root 身份运行。 非常合乎逻辑,因为他需要管理整个节点。 我们看一下它的参数:

$ ./kubelet -h
<слишком много строк, чтобы разместить здесь>
$ ./kubelet -h | wc -l
284

哇,这么多选择! 幸运的是,我们只需要其中几个。 这是我们感兴趣的参数之一:

--pod-manifest-path string

包含静态 pod 文件的目录路径,或描述静态 pod 的文件的路径。 以点开头的文件将被忽略。 (已弃用:必须在通过 --config 选项传递到 Kubelet 的配置文件中设置此选项。有关更多信息,请参阅 kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

该选项允许我们运行 静态吊舱 — 不通过 Kubernetes API 管理的 Pod。 静态 Pod 很少使用,但是它们非常方便快速建立集群,而这正是我们所需要的。 我们将忽略这个大警告(再次强调,不要在生产中运行它!)并看看我们是否可以让 Pod 运行。

首先我们将为静态 Pod 创建一个目录并运行 kubelet:

$ mkdir pods
$ sudo ./kubelet --pod-manifest-path=pods

然后,在另一个终端/tmux 窗口/其他窗口中,我们将创建一个 pod 清单:

$ cat <<EOF > pods/hello.yaml
apiVersion: v1
kind: Pod
metadata:
  name: hello
spec:
  containers:
  - image: busybox
    name: hello
    command: ["echo", "hello world!"]
EOF

kubelet 开始写一些警告,但似乎什么也没发生。 但事实并非如此! 我们看一下 Docker:

$ sudo docker ps -a
CONTAINER ID        IMAGE                  COMMAND                 CREATED             STATUS                      PORTS               NAMES
8c8a35e26663        busybox                "echo 'hello world!'"   36 seconds ago      Exited (0) 36 seconds ago                       k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
68f670c3c85f        k8s.gcr.io/pause:3.2   "/pause"                2 minutes ago       Up 2 minutes                                    k8s_POD_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_0
$ sudo docker logs k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
hello world!

kubelet 我阅读了 pod 清单,并向 Docker 发出了根据我们的规范启动几个容器的命令。 (如果您想知道“暂停”容器,这是 Kubernetes hack - 请参阅 这个博客.) Kubelet 将启动我们的容器 busybox 使用指定的命令,并将无限期地重新启动它,直到静态 Pod 被删除。

祝贺你自己。 我们刚刚想出了一种将文本输出到终端的最令人困惑的方法!

启动etcd

我们的最终目标是运行 Kubernetes API,但要做到这一点,我们首先需要运行 。 让我们通过将其设置放在 pods 目录中来启动一个最小的 etcd 集群(例如, pods/etcd.yaml):

apiVersion: v1
kind: Pod
metadata:
  name: etcd
  namespace: kube-system
spec:
  containers:
  - name: etcd
    command:
    - etcd
    - --data-dir=/var/lib/etcd
    image: k8s.gcr.io/etcd:3.4.3-0
    volumeMounts:
    - mountPath: /var/lib/etcd
      name: etcd-data
  hostNetwork: true
  volumes:
  - hostPath:
      path: /var/lib/etcd
      type: DirectoryOrCreate
    name: etcd-data

如果您曾经使用过 Kubernetes,那么您应该熟悉这些 YAML 文件。 这里只有两点值得注意:

我们已经挂载了host文件夹 /var/lib/etcd 以便在重启后保留 etcd 数据(如果不这样做,每次重启 pod 时集群状态都会被删除,这对于最小的 Kubernetes 安装来说也不是好事)。

我们已经安装了 hostNetwork: true。 不出所料,此设置将 etcd 配置为使用主机网络而不是 pod 的内部网络(这将使 API 服务器更容易找到 etcd 集群)。

一个简单的检查显示 etcd 确实在本地主机上运行并将数据保存到磁盘:

$ curl localhost:2379/version
{"etcdserver":"3.4.3","etcdcluster":"3.4.0"}
$ sudo tree /var/lib/etcd/
/var/lib/etcd/
└── member
    ├── snap
    │   └── db
    └── wal
        ├── 0.tmp
        └── 0000000000000000-0000000000000000.wal

启动API服务器

运行 Kubernetes API 服务器更加容易。 唯一需要传递的参数是 --etcd-servers,执行您期望的操作:

apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - name: kube-apiserver
    command:
    - kube-apiserver
    - --etcd-servers=http://127.0.0.1:2379
    image: k8s.gcr.io/kube-apiserver:v1.18.5
  hostNetwork: true

将此 YAML 文件放入目录中 pods,API 服务器将启动。 检查与 curl 显示 Kubernetes API 正在侦听端口 8080,并且完全开放访问 - 无需身份验证!

$ curl localhost:8080/healthz
ok
$ curl localhost:8080/api/v1/pods
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink": "/api/v1/pods",
    "resourceVersion": "59"
  },
  "items": []
}

(再次强调,不要在生产中运行它!我对默认设置如此不安全感到有点惊讶。但我猜这是为了使开发和测试更容易。)

而且,令人惊喜的是,kubectl 开箱即用,无需任何额外设置!

$ ./kubectl version
Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:47:41Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:39:24Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
$ ./kubectl get pod
No resources found in default namespace.

问题

但如果你再深入一点,就会发现有些地方出了问题:

$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.

我们创建的静态 Pod 消失了! 事实上,我们的 kubelet 节点根本没有被发现:

$ ./kubectl get nodes
No resources found in default namespace.

怎么了? 如果您还记得前几段,我们使用一组极其简单的命令行参数启动了 kubelet,因此 kubelet 不知道如何联系 API 服务器并通知其状态。 经过研究文档,我们找到了对应的flag:

--kubeconfig string

文件的路径 kubeconfig,它指定如何连接到 API 服务器。 可用性 --kubeconfig 启用 API 服务器模式,否 --kubeconfig 启用离线模式。

一直以来,我们都在不知不觉中以“离线模式”运行 kubelet。 (如果我们是迂腐的,我们可以将独立的 kubelet 视为“最小可行的 Kubernetes”,但这会很无聊)。 为了使“真正的”配置起作用,我们需要将 kubeconfig 文件传递​​给 kubelet,以便它知道如何与 API 服务器通信。 幸运的是,这非常简单(因为我们没有任何身份验证或证书问题):

apiVersion: v1
kind: Config
clusters:
- cluster:
    server: http://127.0.0.1:8080
  name: mink8s
contexts:
- context:
    cluster: mink8s
  name: mink8s
current-context: mink8s

将此另存为 kubeconfig.yaml,杀死进程 kubelet 并使用必要的参数重新启动:

$ sudo ./kubelet --pod-manifest-path=pods --kubeconfig=kubeconfig.yaml

(顺便说一句,如果你在 kubelet 没有运行时尝试通过curl 访问 API,你会发现它仍在运行!Kubelet 并不像 Docker 那样是其 pod 的“父级”,它更像是一个“控件”守护进程。”由 kubelet 管理的容器将继续运行,直到 kubelet 停止它们。)

几分钟后 kubectl 应该向我们展示我们期望的 pod 和节点:

$ ./kubectl get pods -A
NAMESPACE     NAME                    READY   STATUS             RESTARTS   AGE
default       hello-mink8s            0/1     CrashLoopBackOff   261        21h
kube-system   etcd-mink8s             1/1     Running            0          21h
kube-system   kube-apiserver-mink8s   1/1     Running            0          21h
$ ./kubectl get nodes -owide
NAME     STATUS   ROLES    AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION       CONTAINER-RUNTIME
mink8s   Ready    <none>   21h   v1.18.5   10.70.10.228   <none>        Ubuntu 18.04.4 LTS   4.15.0-109-generic   docker://19.3.6

这次让我们真正祝贺自己(我知道我已经祝贺自己了)——我们有一个最小的 Kubernetes“集群”,运行着一个功能齐全的 API!

我们在下面启动

现在让我们看看 API 的功能。 让我们从 nginx pod 开始:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx

这里我们得到一个相当有趣的错误:

$ ./kubectl apply -f nginx.yaml
Error from server (Forbidden): error when creating "nginx.yaml": pods "nginx" is
forbidden: error looking up service account default/default: serviceaccount
"default" not found
$ ./kubectl get serviceaccounts
No resources found in default namespace.

在这里我们可以看到我们的 Kubernetes 环境是多么不完整——我们没有服务帐户。 让我们再次尝试手动创建服务帐户,看看会发生什么:

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
EOS
serviceaccount/default created
$ ./kubectl apply -f nginx.yaml
Error from server (ServerTimeout): error when creating "nginx.yaml": No API
token found for service account "default", retry after the token is
automatically created and added to the service account

即使我们手动创建服务帐户,也不会生成身份验证令牌。 当我们继续尝试我们的简约“集群”时,我们会发现通常自动发生的大多数有用的事情都会丢失。 Kubernetes API 服务器非常简约,大部分繁重的工作和自动配置都发生在尚未运行的各种控制器和后台作业中。

我们可以通过设置选项来解决这个问题 automountServiceAccountToken 对于服务帐户(因为我们无论如何都不必使用它):

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
automountServiceAccountToken: false
EOS
serviceaccount/default configured
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Pending   0          13m

终于,豆荚出现了! 但实际上它不会启动,因为我们没有 规划师 (调度器)是 Kubernetes 的另一个重要组件。 我们再次看到 Kubernetes API 出人意料地“愚蠢”——当您在 API 中创建 Pod 时,它会注册它,但不会尝试找出在哪个节点上运行它。

事实上,您不需要调度程序来运行 Pod。 您可以在参数中手动将节点添加到清单中 nodeName:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
  nodeName: mink8s

(代替 mink8s 到节点的名称。)删除并应用后,我们看到nginx已经启动并且正在监听内部IP地址:

$ ./kubectl delete pod nginx
pod "nginx" deleted
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods -owide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          30s   172.17.0.2   mink8s   <none>           <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

为了确保 Pod 之间的网络正常工作,我们可以从另一个 Pod 运行curl:

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl
spec:
  containers:
  - image: curlimages/curl
    name: curl
    command: ["curl", "172.17.0.2"]
  nodeName: mink8s
EOS
pod/curl created
$ ./kubectl logs curl | head -6
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

深入研究这个环境并看看什么有效、什么无效是非常有趣的。 我发现 ConfigMap 和 Secret 按预期工作,但 Service 和 Deployment 没有。

Успех!

这篇文章越来越长,所以我要宣布胜利,并说这是一个可以称为“Kubernetes”的可行配置。总结一下:四个二进制文件、五个命令行参数和“仅”45 行 YAML(不是按照 Kubernetes 的标准来说就这么多了),我们有很多东西在工作:

  • Pod 使用常规 Kubernetes API 进行管理(需要一些技巧)
  • 您可以上传和管理公共容器镜像
  • Pod 保持活动状态并自动重启
  • 同一节点内的 Pod 之间的网络运行良好
  • ConfigMap、Secret 和简单存储安装按预期工作

但 Kubernetes 真正有用的许多东西仍然缺失,例如:

  • Pod 调度程序
  • 认证/授权
  • 多个节点
  • 服务网络
  • 集群内部 DNS
  • 用于服务帐户、部署、与云提供商集成以及 Kubernetes 带来的大多数其他好处的控制器

那么我们实际上得到了什么? 独立运行的 Kubernetes API 实际上只是一个平台 集装箱自动化。 它的作用并不大——这是使用 API 的各种控制器和操作员的工作——但它确实为自动化提供了一致的环境。

在免费网络研讨会中了解有关该课程的更多信息。

阅读更多:

来源: habr.com

添加评论