一个“正常”安装的 Kubernetes(如果有这么一说的话)对于物联网来说有点沉重。K8s 的推荐内存配置,是每台机器 2GB!不过,我们也有一些替代品,其中一个新人是 k3s —— 一个轻量级的 Kubernetes 发行版。
Kubernetes 是一个颇受欢迎的容器编排系统。它可能最常用在那些能够处理巨大负载的强劲硬件上。不过,它也能在像树莓派 3 这样轻量级的设备上运行。让我们继续阅读,来了解如何运行它。
为什么用 Kubernetes?
虽然 Kubernetes 在云计算领域风靡一时,但让它在小型单板机上运行可能并不是常见的。不过,我们有非常明确的理由来做这件事。首先,这是一个不需要昂贵硬件就可以学习并熟悉 Kubernetes 的好方法;其次,由于它的流行性,市面上有大量应用进行了预先打包,以用于在 Kubernetes 集群中运行。更不用说,当你遇到问题时,会有大规模的社区用户为你提供帮助。
最后但同样重要的是,即使是在家庭实验室这样的小规模环境中,容器编排也确实能够使事情变得更加简单。虽然在学习曲线方面,这一点并不明显,但这些技能在你将来与任何集群打交道的时候都会有帮助。不管你面对的是一个单节点树莓派集群,还是一个大规模的机器学习场,它们的操作方式都是类似的。
K3s - 轻量级的 Kubernetes
一个“正常”安装的 Kubernetes(如果有这么一说的话)对于物联网来说有点沉重。K8s 的推荐内存配置,是每台机器 2GB!不过,我们也有一些替代品,其中一个新人是 k3s —— 一个轻量级的 Kubernetes 发行版。
K3s 非常特殊,因为它将 etcd 替换成了 SQLite 以满足键值存储需求。还有一点,在于整个 k3s 将使用一个二进制文件分发,而不是每个组件一个。这减少了内存占用并简化了安装过程。基于上述原因,我们只需要 512MB 内存即可运行 k3s,极度适合小型单板电脑!
环境准备:
k3s-node1
k3s-node2
在两台主机上配置hosts
cat /etc/hosts
192.168.20.102 k3s-node1
192.168.20.105 k3s-node2
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
配置防火墙,允许 6443 和 8372 端口的通信。或者,你也可以简单地运行 systemctl stop firewalld
来为这次实验关闭防火墙。
[root@k3s-node1 ~]# service firewalld status
Redirecting to /bin/systemctl status firewalld.service
● firewalld.service - firewalld - dynamic firewall daemon
Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled)
Active: inactive (dead)
Docs: man:firewalld(1)
[root@k3s-node2 ~]# service firewalld status
Redirecting to /bin/systemctl status firewalld.service
● firewalld.service - firewalld - dynamic firewall daemon
Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled)
Active: inactive (dead)
Docs: man:firewalld(1)
安装k3s,安装 k3s 非常简单。直接运行安装脚本:
[root@k3s-node1 ~]# curl -sfL https://get.k3s.io | sh -
它会下载、安装并启动 k3s。安装完成后,运行以下命令来从服务器获取节点列表:
[root@k3s-node1 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k3s-node1 Ready 67s v1.14.1-k3s.4
需要注意的是,有几个选项可以通过环境变量传递给安装脚本。这些选项可以在文档中找到。当然,你也完全可以直接下载二进制文件来手动安装 k3s。
对于实验和学习来说,这样已经很棒了,不过单节点的集群也不能算一个集群。幸运的是,添加另一个节点并不比设置第一个节点要难。只需要向安装脚本传递两个环境变量,它就可以找到第一个节点,而不用运行 k3s 的服务器部分。
curl -sfL https://get.k3s.io | K3S_URL=https://example-url:6443 \
K3S_TOKEN=XXX sh -
上面的 example-url
应被替换为第一个节点的 IP 地址,或一个完全限定域名。在该节点中,(用 XXX 表示的)令牌可以在 /var/lib/rancher/k3s/server/node-token
文件中找到。
[root@k3s-node1 ~]# cat /var/lib/rancher/k3s/server/node-token
K10e75d2dfbb13cad45b1895f0e02ab3271cb9238abb54bc902b38c4da69037ffde::node:f05c806e90e81eef905959061a7a1639
在k3s-node2上执行
curl -sfL https://get.k3s.io | K3S_URL=https://k3s-node1:6443 \
K3S_TOKEN=K10e75d2dfbb13cad45b1895f0e02ab3271cb9238abb54bc902b38c4da69037ffde::node:f05c806e90e81eef905959061a7a1639 sh -
[root@k3s-node2 ~]# curl -sfL https://get.k3s.io | K3S_URL=https://k3s-node1:6443 \
> K3S_TOKEN=K10e75d2dfbb13cad45b1895f0e02ab3271cb9238abb54bc902b38c4da69037ffde::node:f05c806e90e81eef905959061a7a1639 sh -
[INFO] Finding latest release
[INFO] Using v0.5.0 as release
[INFO] Downloading hash https://github.com/rancher/k3s/releases/download/v0.5.0/sha256sum-amd64.txt
[INFO] Downloading binary https://github.com/rancher/k3s/releases/download/v0.5.0/k3s
[INFO] Verifying binary download
[INFO] Installing k3s to /usr/local/bin/k3s
[INFO] Creating /usr/local/bin/kubectl symlink to k3s
[INFO] Creating /usr/local/bin/crictl symlink to k3s
[INFO] Creating killall script /usr/local/bin/k3s-killall.sh
[INFO] Creating uninstall script /usr/local/bin/k3s-agent-uninstall.sh
[INFO] env: Creating environment file /etc/systemd/system/k3s-agent.service.env
[INFO] systemd: Creating service file /etc/systemd/system/k3s-agent.service
[INFO] systemd: Enabling k3s-agent unit
Created symlink from /etc/systemd/system/multi-user.target.wants/k3s-agent.service to /etc/systemd/system/k3s-agent.service.
[INFO] systemd: Starting k3s-agent
在k3s-node1检查集群
[root@k3s-node1 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k3s-node1 Ready 12m v1.14.1-k3s.4
k3s-node2 Ready 8s v1.14.1-k3s.4
部署一些容器
现在我们有了一个 Kubernetes 集群,我们可以真正做些什么呢?让我们从部署一个简单的 Web 服务器开始吧。
[root@k3s-node1 ~]# kubectl create deployment my-server --image nginx
deployment.apps/my-server created
这会从名为 nginx
的容器镜像中创建出一个名叫 my-server
的 部署(默认使用 docker hub 注册中心,以及 latest
标签)。
[root@k3s-node1 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
my-server-6889ff8bd8-rx2ll 0/1 ContainerCreating 0 109s
发现pod的状态一直为正在创建状态,利用kubectl describe pod
查看pod创建的详细记录
[root@k3s-node1 k3s]# kubectl describe pods
Name: my-server-6889ff8bd8-rx2ll
Namespace: default
Priority: 0
PriorityClassName:
Node: k3s-node2/192.168.20.105
Start Time: Sat, 01 Jun 2019 15:44:33 +0800
Labels: app=my-server
pod-template-hash=6889ff8bd8
Annotations:
Status: Pending
IP:
Controlled By: ReplicaSet/my-server-6889ff8bd8
Containers:
nginx:
Container ID:
Image: nginx
Image ID:
Port:
Host Port:
State: Waiting
Reason: ContainerCreating
Ready: False
Restart Count: 0
Environment:
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-gprt4 (ro)
Conditions:
Type Status
Initialized True
Ready False
ContainersReady False
PodScheduled True
Volumes:
default-token-gprt4:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-gprt4
Optional: false
QoS Class: BestEffort
Node-Selectors:
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 17m default-scheduler Successfully assigned default/my-server-6889ff8bd8-rx2ll to k3s-node2
Warning FailedCreatePodSandBox 12m (x8 over 17m) kubelet, k3s-node2 Failed create pod sandbox: rpc error: code = Unknown desc = failed to get sandbox image "k8s.gcr.io/pause:3.1": failed to pull image "k8s.gcr.io/pause:3.1": failed to resolve image "k8s.gcr.io/pause:3.1": no available registry endpoint: failed to do request: Head https://k8s.gcr.io/v2/pause/manifests/3.1: dial tcp 108.177.125.82:443: i/o timeout
Warning FailedCreatePodSandBox 2m31s (x11 over 9m38s) kubelet, k3s-node2 Failed create pod sandbox: rpc error: code = Unknown desc = failed to get sandbox image "k8s.gcr.io/pause:3.1": failed to pull image "k8s.gcr.io/pause:3.1": failed to resolve image "k8s.gcr.io/pause:3.1": no available registry endpoint: failed to do request: Head https://k8s.gcr.io/v2/pause/manifests/3.1: dial tcp 108.177.125.82:443: i/o timeout
Warning FailedCreatePodSandBox 25s (x3 over 109s) kubelet, k3s-node2 Failed create pod sandbox: rpc error: code = Unknown desc = failed to get sandbox image "k8s.gcr.io/pause:3.1": failed to pull image "k8s.gcr.io/pause:3.1": failed to resolve image "k8s.gcr.io/pause:3.1": no available registry endpoint: failed to do request: Head https://k8s.gcr.io/v2/pause/manifests/3.1: dial tcp 74.125.203.82:443: i/o timeout
发现无法访问gcr.io.
注意:通过journalctl工具,我发现无法获取node资源的一个最最重要的问题就是通过最后对docker服务的检查发现其中的每个pod的pause container为从gcr.io上下载的,而由于屏蔽了中国的访问,故需要我们手动下载缺少的镜像,缺少的pause镜像为3.1版本的在docker hub上进行搜索,我最终找到了一个3.1版本的pause(docker.io/kubernetes/pause 为3.0版本的,故我没有选择该image
[root@k3s-node1 ~]# mkdir /certs
[root@k3s-node1 ~]# cd /certs
[root@k3s-node1 certs]# openssl req -subj "/C=CN/ST=BeiJing/L=Dongcheng/CN=k8s.gcr.io" -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout registry.key -out registry.crt
Generating a 2048 bit RSA private key
...+++
.................................+++
writing new private key to 'registry.key'
-----
[root@k3s-node1 certs]# cat /certs/registry.crt >> /etc/pki/tls/certs/ca-bundle.crt
安装docker
[root@k3s-node1 ~]# yum install -y docker
[root@k3s-node1 ~]# systemctl enable docker
[root@k3s-node1 ~]# systemctl start docker
[root@k3s-node1 ~]#
接取镜像:
[root@k3s-node1 ~]# docker pull docker.io/rancher/pause-amd64:3.1
[root@k3s-node1 ~]# docker tag docker.io/rancher/pause-amd64:3.1 k8s.gcr.io/pause:3.1
修改hosts
[root@k3s-node1 ~]# cat /etc/hosts
192.168.20.102 k8s.gcr.io
安装ca.cert,这一步不做会出现
x509: certificate signed by unknown authority
mkdir /etc/docker/certs.d/k8s.gcr.io/
cp /certs/registry.crt /etc/docker/certs.d/k8s.gcr.io/ca.crt
重新启动docker
[root@k3s-node1 ~]# systemctl restart docker
建立registry
[root@k3s-node1 ~]# docker run -d -p 443:443 --restart=always --name registry -v /certs:/certs -e REGISTRY_HTTP_ADDR=0.0.0.0:443 -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry.crt -e REGISTRY_HTTP_TLS_KEY=/certs/registry.key registry:2
84e9e6c39eb55a47c66b6964c65e7505bbeb13fa0b1d6d0a6115c74130526c0a
查看dockers日志,tls handsharke error不用理。
[root@k3s-node1 certs]# docker logs registry
time="2019-06-01T13:23:18.951071701Z" level=warning msg="No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable." go.version=go1.11.2 instance.id=c85c09e2-c1fd-409e-b971-1e08ff4c503b service=registry version=v2.7.1
time="2019-06-01T13:23:18.951230536Z" level=info msg="redis not configured" go.version=go1.11.2 instance.id=c85c09e2-c1fd-409e-b971-1e08ff4c503b service=registry version=v2.7.1
time="2019-06-01T13:23:18.951273123Z" level=info msg="Starting upload purge in 36m0s" go.version=go1.11.2 instance.id=c85c09e2-c1fd-409e-b971-1e08ff4c503b service=registry version=v2.7.1
time="2019-06-01T13:23:18.972833753Z" level=info msg="using inmemory blob descriptor cache" go.version=go1.11.2 instance.id=c85c09e2-c1fd-409e-b971-1e08ff4c503b service=registry version=v2.7.1
time="2019-06-01T13:23:18.973733421Z" level=info msg="listening on [::]:443, tls" go.version=go1.11.2 instance.id=c85c09e2-c1fd-409e-b971-1e08ff4c503b service=registry version=v2.7.1
2019/06/01 13:23:24 http: TLS handshake error from 192.168.20.102:48180: remote error: tls: bad certificate
2019/06/01 13:23:28 http: TLS handshake error from 192.168.20.102:48182: remote error: tls: bad certificate
打标签,上传
[root@k3s-node1 certs]# docker push k8s.gcr.io/pause:3.1
The push refers to a repository [k8s.gcr.io/pause]
e17133b79956: Pushed
3.1: digest: sha256:fcaff905397ba63fd376d0c3019f1f1cb6e7506131389edbcb3d22719f1ae54d size: 527
查看kubectl
[root@k3s-node1 certs]# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/my-server-6889ff8bd8-mzsxq 0/1 ContainerCreating 0 4h11m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.43.0.1 443/TCP 6h1m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/my-server 0/1 1 0 4h11m
NAME DESIRED CURRENT READY AGE
replicaset.apps/my-server-6889ff8bd8 1 1 0 4h11m
[root@k3s-node1 certs]# kubectl delete deployment.apps/my-server
deployment.apps "my-server" deleted
[root@k3s-node1 certs]# kubectl get all
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.43.0.1 443/TCP 6h1m
还有些问题,迟些处理。