如果您正在阅读本文,那么您可能已经听说过有关Kubernetes的内容(如果没有,您是如何结束的?)但是Kubernetes到底是什么呢?这是“工业级容器编排”吗?还是“云原生操作系统”?这到底是什么意思?
老实说,我不确定100%。但是我认为深入研究内部结构并查看其多层抽象下的Kubernetes实际发生的事情很有趣。因此,仅出于娱乐目的,让我们看看最小的“ Kubernetes集群”的外观。 (这将比Kubernetes The Hard Way容易得多。)
我假设您具有Kubernetes,Linux和容器的基本知识。我们在这里谈论的所有内容仅用于研究/研究,请勿在生产中运行任何内容!
总览
Kubernetes包含许多组件。根据Wikipedia的说法,该体系结构如下所示:
这里至少显示了八个组件,但是我们将忽略其中的大部分。我想指出,可以合理地称为Kubernetes的最小事物具有三个主要组成部分:
- kubelet
- kube-apiserver(取决于etcd-它的数据库)
- 容器运行时(在本例中为Docker)
让我们看看文档中关于它们(俄语,英语)的说法。首先是kubelet:
在集群中每个节点上运行的代理。他确保容器在吊舱中运行。
听起来很简单。怎么样运行的容器(容器运行时)?
容器运行时是旨在运行容器的程序。
非常丰富。但是,如果您熟悉Docker,那么您应该对它的功能有基本的了解。 (的容器运行时和kubelet之间分离关注的细节其实很微妙,我不会去到这里。)
和服务器API?
API Server-Kubernetes仪表板组件,代表Kubernetes API。API服务器是Kubernetes仪表板的前端,
任何使用Kubernetes做过任何事情的人都必须直接或通过kubectl与API进行交互。这就是创建Kubernetes的核心。Kubernetes是将我们都知道并喜欢的YAML山脉变成一个正常工作的基础设施的大脑。显然,API应该以我们的最小配置出现。
前提条件
- 扎根的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
包含静态容器文件的目录路径,或描述静态容器文件的路径。以句点开头的文件将被忽略。 (不推荐使用:此参数应在通过--config选项传递给Kubelet的配置文件中设置。有关更多信息,请参见kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file。)
此参数使我们可以静态运行豆荚-未通过Kubernetes API管理吊舱。静态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的骇客-有关详细信息,请参见此博客。)Kubelet将busybox使用指定的命令启动我们的容器,并无限期地重新启动容器,直到移除静态容器为止。
恭喜你 我们只是想出了最复杂的方法之一来向终端输出文本!
运行etcd
我们的最终目标是运行Kubernetes API,但为此,我们首先需要运行etcd。通过将其设置放在pods目录(例如
pods/etcd.yaml)中,启动一个最小的etcd集群:
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文件。这里只有两件事值得注意:
我们将主机文件夹安装
/var/lib/etcd在Pod中,以便在重启后保存etcd数据(如果不这样做,则每次重启Pod都会删除集群状态,即使最小的Kubernetes安装也很糟糕)。
我们已经安装好了
hostNetwork: true。毫不奇怪,此选项将etcd配置为使用主机网络而不是Pod的内部网络(这将使API服务器更容易找到etcd集群)。
一个简单的检查表明etcd确实在localhost上运行并将数据保存到磁盘:
$ 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 Server更加容易。需要传递的唯一参数
--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服务器并通知其状态。在检查了文档之后,我们找到了相应的标志:
--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不是其pod的“父级”,就像Docker,它更像是一个“控制守护程序”。由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
这次让我们真正地祝贺自己(我知道我已经祝贺了)-我们使用功能齐全的API的最小Kubernetes“集群”!
跑下
现在,让我们看看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 and Deployment却无法正常工作。
成功!
这篇文章越来越大,所以我要宣布胜利,并宣布这是一个称为“ Kubernetes”的可行配置。总结:四个二进制文件,五个命令行参数和“仅” 45行YAML(按标准,不是很多) Kubernetes),我们还有很多工作要做:
- 使用常规的Kubernetes API管理Pod(有一些技巧)
- 您可以上传和管理公共容器图像
- Pod保持活动并自动重启
- 单个节点内的Pod之间的联网效果很好
- ConfigMap,秘密和最简单的存储库安装按预期工作
但是,使Kubernetes真正有用的大多数东西仍然缺失,例如:
- 吊舱规划师
- 认证/授权
- 多个节点
- 服务网络
- 群集内部DNS
- 服务帐户,部署,云提供商集成以及Kubernetes带来的其他大多数优势的控制器
那么,我们实际上得到了什么?单独运行的Kubernetes API实际上只是一个容器自动化平台。它并没有做什么用-它适用于使用API的各种控制器和操作员-但它提供了一致的自动化框架。
在免费的在线讲座中了解有关该课程的更多信息。