在 Raspberry Pi 上从头开始完成 Kubernetes

在 Raspberry Pi 上从头开始完成 Kubernetes

就在最近,一家知名公司宣布将其笔记本电脑系列转移到 ARM 架构。 当我听到这个消息时,我想起:当我再次查看AWS中EC2的价格时,我发现Gravitons的价格非常可观。 当然,问题在于它是 ARM。 当时我没想到ARM是这么认真的……

对我来说,这种架构一直是移动和其他物联网事物的领域。 ARM 上的“真正”服务器在某种程度上是不寻常的,在某些方面甚至是疯狂的……然而,一个新的想法浮现在我的脑海中,所以一个周末我决定检查一下今天可以在 ARM 上运行什么。 为此,我决定从一些亲近的东西开始——Kubernetes 集群。 不仅仅是某种传统的“集群”,而是一切“以成人的方式”,因此它尽可能地与我习惯在生产中看到的相同。

根据我的想法,集群应该可以从互联网访问,一些Web应用程序应该在其中运行,并且至少应该有监控。 要实现这个想法,您需要一对(或更多)至少型号为 3B+ 的 Raspberry Pi。 AWS 本来可以成为一个实验平台,但我对“树莓派”(仍然闲置)感兴趣。 因此,我们将部署一个带有 Ingress、Prometheus 和 Grafana 的 Kubernetes 集群。

准备“覆盆子”

安装操作系统和 SSH

我并没有太费心去选择要安装的操作系统:我只是选择了最新的 Raspberry Pi OS Lite 官方网站。 在那里可用 安装文档,其中的所有操作都必须在未来集群的所有节点上执行。 接下来,您将需要执行以下操作(也在所有节点上)。

连接显示器和键盘后,必须首先配置网络和 SSH:

  1. 为了使集群运行,master 必须有一个静态 IP 地址,worker 节点也必须有一个静态 IP 地址。 为了便于设置,我自始至终更喜欢静态地址。
  2. 静态地址可以在操作系统中配置(在文件中 /etc/dhcpcd.conf 有一个合适的例子)或通过修复所用(在我的例子中是家庭)路由器的 DHCP 服务器中的租约。
  3. ssh-server 只是包含在 raspi-config 中(接口选项 → ssh).

之后就可以通过SSH登录了(默认登录是 pi密码是 raspberry 或您更改为的那个)并继续设置。

其他设置

  1. 让我们设置主机名。 在我的例子中他们将使用 pi-control и pi-worker.
  2. 让我们检查一下文件系统是否已扩展以覆盖整个磁盘(df -h /)。 如果需要,可以使用 raspi-config 进行扩展。
  3. 让我们更改 raspi-config 中的默认用户密码。
  4. 让我们关闭交换文件(这是 Kubernetes 的要求;如果您对此主题的详细信息感兴趣,请参阅 问题#53533):
    dphys-swapfile swapoff
    systemctl disable dphys-swapfile
  5. 让我们将软件包更新到最新版本:
    apt-get update && apt-get dist-upgrade -y
  6. 让我们安装 Docker 和其他软件包:
    apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistent

    当您安装 iptables-persistent 您需要保存 ipv4 的 iptables 设置,并在文件中 /etc/iptables/rules.v4 - 添加规则到链中 FORWARD,像这样:

    # Generated by xtables-save v1.8.2 on Sun Jul 19 00:27:43 2020
    *filter
    :INPUT ACCEPT [0:0]
    :FORWARD ACCEPT [0:0]
    :OUTPUT ACCEPT [0:0]
    -A FORWARD -s 10.1.0.0/16  -j ACCEPT
    -A FORWARD -d 10.1.0.0/16  -j ACCEPT
    COMMIT
  7. 剩下的就是重新启动。

现在您已准备好安装 Kubernetes 集群。

安装 Kubernetes

在这个阶段,我特意搁置了我和我们公司的所有开发,以自动化K8s集群的安装和配置。 相反,让我们使用官方文档 Kubernetes.io (稍微补充了评论和缩写)。

让我们添加 Kubernetes 存储库:

curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
sudo apt-get update

在文档中进一步建议安装 CRI(容器运行时接口)。 由于 Docker 已经安装,让我们继续安装主要组件:

sudo apt-get install -y kubelet kubeadm kubectl kubernetes-cni

在安装主要组件的步骤中,我立即添加了 kubernetes-cni,这是集群运行所必需的。 这里有一个重要的点:包 kubernetes-cni 由于某种原因,它没有为 CNI 接口设置创建默认目录,因此我必须手动创建它:

mkdir -p /etc/cni/net.d

为了使网络后端正常工作(将在下面讨论),您需要安装 CNI 插件。 我选择了我熟悉且易于理解的portmap插件 (有关完整列表,请参阅 文件资料):

curl -sL https://github.com/containernetworking/plugins/releases/download/v0.7.5/cni-plugins-arm-v0.7.5.tgz | tar zxvf - -C /opt/cni/bin/ ./portmap

配置 Kubernetes

具有控制平面的节点

安装集群本身非常简单。 为了加快此过程并验证 Kubernetes 映像是否可用,您可以首先运行:

kubeadm config images pull

现在我们执行安装本身 - 初始化集群的控制平面:

kubeadm init --pod-network-cidr=10.1.0.0/16 --service-cidr=10.2.0.0/16 --upload-certs

请注意,服务和 Pod 的子网不应相互重叠或与现有网络重叠。

最后,我们将看到一条消息,表示一切正常,同时他们将告诉我们如何将工作节点附加到控制平面:

Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
 mkdir -p $HOME/.kube
 sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
 sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
 https://kubernetes.io/docs/concepts/cluster-administration/addons/
You can now join any number of the control-plane node running the following command on each as root:
 kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4 
   --discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050 
   --contrl-plane --certificate-key 72a3c0a14c627d6d7fdade1f4c8d7a41b0fac31b1faf0d8fdf9678d74d7d2403
Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4 
   --discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050

让我们按照建议为用户添加配置。 同时,我建议立即为 kubectl 添加自动补全功能:

 kubectl completion bash > ~/.kube/completion.bash.inc
 printf "
 # Kubectl shell completion
 source '$HOME/.kube/completion.bash.inc'
 " >> $HOME/.bash_profile
 source $HOME/.bash_profile

到了这个阶段,你已经可以看到集群中的第一个节点了(虽然还没有准备好):

root@pi-control:~# kubectl get no
NAME         STATUS     ROLES    AGE   VERSION
pi-control   NotReady   master   29s   v1.18.6

网络配置

接下来,如安装后消息中所述,您需要将网络安装到集群中。 文档提供了 Calico、Cilium、contiv-vpp、Kube-router 和 Weave Net 的选择...这里我偏离了官方说明,选择了一个对我来说更熟悉和易于理解的选项: 绒布 在 host-gw 模式下(有关可用后端的更多信息,请参阅 项目文件).

在集群中安装它非常简单。 首先,下载清单:

wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

然后在设置中更改类型 vxlanhost-gw:

sed -i 's/vxlan/host-gw/' kube-flannel.yml

...以及 Pod 子网 - 从默认值到集群初始化期间指定的值:

sed -i 's#10.244.0.0/16#10.1.0.0/16#' kube-flannel.yml

之后,我们创建资源:

kubectl create -f kube-flannel.yml

准备好! 一段时间后,第一个K8s节点将切换到状态 Ready:

NAME         STATUS   ROLES    AGE   VERSION
pi-control   Ready    master   2m    v1.18.6

添加工作节点

现在您可以添加一名工人。 要在其上执行此操作 - 在根据上述场景安装 Kubernetes 本身之后 - 您只需运行之前收到的命令:

kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4 
    --discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050

此时我们可以假设集群已准备就绪:

root@pi-control:~# kubectl get no
NAME         STATUS   ROLES    AGE    VERSION
pi-control   Ready    master   28m    v1.18.6
pi-worker    Ready    <none>   2m8s   v1.18.6

我手头只有两个 Raspberry Pi,所以赠送了其中一个 我不想让它处于控制平面之下。 因此,我通过运行以下命令从 pi-control 节点中删除了自动安装的污点:

root@pi-control:~# kubectl edit node pi-control

...并删除行:

 - effect: NoSchedule
   key: node-role.kubernetes.io/master

用所需的最小值填充集群

首先我们需要 头盔。 当然,您可以在没有它的情况下完成所有操作,但 Helm 允许您自行决定自定义某些组件,而无需编辑文件。 事实上,它只是一个“不求面包”的二进制文件。

那么让我们去 舵机sh 到 docs/installation 部分并从那里执行命令:

curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash

之后,添加图表存储库:

helm repo add stable https://kubernetes-charts.storage.googleapis.com/

现在让我们按计划安装基础设施组件:

  • 入口控制器;
  • 普罗米修斯;
  • 格拉法纳;
  • 证书经理。

入口控制器

第一个组件是 入口控制器 - 安装非常简单,开箱即可使用。 要执行此操作,只需转到 网站上的裸机部分 并从那里运行安装命令:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yaml

然而,此时“树莓派”开始紧张并遇到磁盘IOPS问题。 事实是,与 Ingress 控制器一起安装了大量资源,对 API 发出了许多请求,相应地,大量数据被写入 etcd。 一般来说,要么class 10存储卡生产力不高,要么SD卡基本上不足以满足这样的负载。 然而,大约 5 分钟后,一切都开始了。

创建了一个命名空间,一个控制器及其所需的一切都出现在其中:

root@pi-control:~# kubectl -n ingress-nginx get pod
NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-2hwdx        0/1     Completed   0          31s
ingress-nginx-admission-patch-cp55c         0/1     Completed   0          31s
ingress-nginx-controller-7fd7d8df56-68qp5   1/1     Running     0          48s

普罗米修斯

接下来的两个组件很容易通过 Helm 从图表存储库安装。

普罗米修斯,创建一个命名空间并将其设置为:

helm search repo stable | grep prometheus
kubectl create ns monitoring
helm install prometheus --namespace monitoring stable/prometheus --set server.ingress.enabled=True --set server.ingress.hosts={"prometheus.home.pi"}

默认情况下,Prometheus 订购 2 个磁盘:用于 Prometheus 数据本身和 AlertManager 数据。 由于集群中尚未创建存储类,因此不会对磁盘​​进行排序,并且 Pod 也不会启动。 对于裸机 Kubernetes 安装,我们通常使用 Ceph rbd,但对于 Raspberry Pi,这显然是多余的。

因此,让我们在主机路径上创建一个简单的本地存储。 prometheus-server 和 prometheus-alertmanager 的 PV(持久卷)清单合并在一个文件中 prometheus-pv.yaml в 包含本文示例的 Git 存储库。 需要PV目录 提前 在我们要绑定Prometheus的节点的磁盘上创建:示例中是这样写的 nodeAffinity 按主机名 pi-worker 并在其上创建目录 /data/localstorage/prometheus-server и /data/localstorage/prometheus-alertmanager.

下载(克隆)清单并将其添加到 Kubernetes:

kubectl create -f prometheus-pv.yaml

在这个阶段,我首先遇到了ARM架构的问题。 Prometheus 图表中默认安装的 Kube-state-metrics 拒绝启动。 它给出了一个错误:

root@pi-control:~# kubectl -n monitoring logs prometheus-kube-state-metrics-c65b87574-l66d8
standard_init_linux.go:207: exec user process caused "exec format error"

事实上,kube-state-metrics 使用的是 CoreOS 项目的映像,该映像不是为 ARM 编译的:

kubectl -n monitoring get deployments.apps prometheus-kube-state-metrics -o=jsonpath={.spec.template.spec.containers[].image}
quay.io/coreos/kube-state-metrics:v1.9.7

我必须进行一些谷歌搜索并找到,例如, 这个图片。 要使用它,我们需要更新版本以指示哪个镜像用于 kube-state-metrics:

helm upgrade prometheus --namespace monitoring stable/prometheus --set server.ingress.enabled=True --set server.ingress.hosts={"prometheus.home.pi"} --set kube-state-metrics.image.repository=carlosedp/kube-state-metrics --set kube-state-metrics.image.tag=v1.9.6

让我们检查一切是否已经开始:

root@pi-control:~# kubectl -n monitoring get po
NAME                                             READY   STATUS              RESTARTS   AGE
prometheus-alertmanager-df65d99d4-6d27g          2/2     Running             0          5m56s
prometheus-kube-state-metrics-5dc5fd89c6-ztmqr   1/1     Running             0          5m56s
prometheus-node-exporter-49zll                   1/1     Running             0          5m51s
prometheus-node-exporter-vwl44                   1/1     Running             0          4m20s
prometheus-pushgateway-c547cfc87-k28qx           1/1     Running             0          5m56s
prometheus-server-85666fd794-z9qnc               2/2     Running             0          4m52s

Grafana 和证书管理器

对于图表和仪表板,我们设置 格拉法纳:

helm install grafana --namespace monitoring stable/grafana  --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}

在输出的最后,我们将看到如何获取访问密码:

kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo

要订购证书,我们将安装 证书经理。 安装方法请参考 文件资料,它为 Helm 提供了相应的命令:

helm repo add jetstack https://charts.jetstack.io

helm install 
  cert-manager jetstack/cert-manager 
  --namespace cert-manager 
  --version v0.16.0 
  --set installCRDs=true

对于家庭使用的自签名证书来说,这已经足够了。 如果您需要获得相同的 让我们加密,那么还需要配置集群发行者。 有关详细信息,请参阅我们的文章“来自 Let's Encrypt with cert-manager in Kubernetes 的 SSL 证书“。

我自己选择了以下选项 文档中的示例,决定 LE 的分期选项就足够了。 我们更改示例中的电子邮件,将其保存到文件中并将其添加到集群中(证书管理器集群颁发者.yaml):

kubectl create -f cert-manager-cluster-issuer.yaml

现在您可以订购证书,例如 Grafana 的证书。 这将需要一个域并从外部访问集群。 我有一个域,我根据创建的入口控制器服务在家庭路由器上转发端口 80 和 443 来配置流量:

kubectl -n ingress-nginx get svc
NAME                                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.2.206.61    <none>        80:31303/TCP,443:30498/TCP   23d

在本例中,第 80 个端口被转换为 31303,443 被转换为 30498。 (端口是随机生成的,因此您的端口会有所不同。)

这是证书的示例(证书管理器-grafana-certificate.yaml):

apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: grafana
  namespace: monitoring
spec:
  dnsNames:
    - grafana.home.pi
  secretName: grafana-tls
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt-staging

将其添加到集群中:

kubectl create -f cert-manager-grafana-certificate.yaml

之后,将出现 Ingress 资源,通过该资源进行 Let's Encrypt 验证:

root@pi-control:~# kubectl -n monitoring get ing
NAME                        CLASS    HOSTS                        ADDRESS         PORTS   AGE
cm-acme-http-solver-rkf8l   <none>   grafana.home.pi      192.168.88.31   80      72s
grafana                     <none>   grafana.home.pi      192.168.88.31   80      6d17h
prometheus-server           <none>   prometheus.home.pi   192.168.88.31   80      8d

验证通过后,我们会看到该资源 certificate 准备好,并在上面的秘密 grafana-tls - 证书和密钥。 您可以立即检查谁颁发了证书:

root@pi-control:~# kubectl -n monitoring get certificate
NAME      READY   SECRET        AGE
grafana   True    grafana-tls   13m

root@pi-control:~# kubectl -n monitoring get secrets grafana-tls -ojsonpath="{.data['tls.crt']}" | base64 -d | openssl x509 -issuer -noout
issuer=CN = Fake LE Intermediate X1

让我们回到 Grafana。 我们需要通过更改 TLS 设置以匹配生成的证书来稍微修复其 Helm 版本。

为此,请下载图表,从本地目录进行编辑和更新:

helm pull --untar stable/grafana

在文件中编辑 grafana/values.yaml TLS 参数:

  tls:
    - secretName: grafana-tls
      hosts:
        - grafana.home.pi

在这里您可以立即将安装的 Prometheus 配置为 datasource:

datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Prometheus
      type: prometheus
      url: http://prometheus-server:80
      access: proxy
      isDefault: true

现在我们从本地目录更新 Grafana 图表:

helm upgrade grafana --namespace monitoring ./grafana  --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}

检查 Ingress 中有什么 grafana 已添加443端口,可通过HTTPS访问:

root@pi-control:~# kubectl -n monitoring get ing grafana
NAME      CLASS    HOSTS                     ADDRESS         PORTS     AGE
grafana   <none>   grafana.home.pi           192.168.88.31   80, 443   63m

root@pi-control:~# curl -kI https://grafana.home.pi
HTTP/2 302
server: nginx/1.19.1
date: Tue, 28 Jul 2020 19:01:31 GMT
content-type: text/html; charset=utf-8
cache-control: no-cache
expires: -1
location: /login
pragma: no-cache
set-cookie: redirect_to=%2F; Path=/; HttpOnly; SameSite=Lax
x-frame-options: deny
strict-transport-security: max-age=15724800; includeSubDomains

要演示 Grafana 的实际应用,您可以下载并添加 kube-state-metrics 仪表板。 它看起来是这样的:

在 Raspberry Pi 上从头开始完成 Kubernetes

我还建议为节点导出器添加一个仪表板:它将详细显示“树莓派”发生的情况(CPU 负载、内存、网络、磁盘使用情况等)。

在此之后我认为 集群已准备好接受并运行应用程序!

组装注意事项

至少有两种用于构建 ARM 架构应用程序的选项。 首先,您可以在 ARM 设备上进行构建。 然而,在查看了目前对两个 Raspberry Pi 的处置后,我意识到它们也无法在组装后幸存下来。 因此,我为自己订购了一台新的 Raspberry Pi 4(它功能更强大,内存高达 4 GB) - 我计划在它的基础上进行构建。

第二种选择是在更强大的机器上构建多架构 Docker 镜像。 为此有 docker buildx 扩展。 如果应用程序采用编译语言,则需要针对 ARM 进行交叉编译。 我不会描述这条路径的所有设置,因为...... 这需要一篇单独的文章。 通过实施这种方法,可以实现“通用”镜像:运行在 ARM 机器上的 Docker 会自动下载与架构相对应的镜像。

结论

这个实验超出了我的所有预期:[至少]具有必要基础的“普通”Kubernetes 在 ARM 上感觉良好,并且其配置只出现了一些细微差别。

Raspberry Pi 3B+ 本身将负载保持在 CPU 上,但它们的 SD 卡是一个明显的瓶颈。 同事建议,在某些版本中可以从USB启动,您可以在其中连接SSD:那么情况很可能会变得更好。

以下是安装 Grafana 时的 CPU 负载示例:

在 Raspberry Pi 上从头开始完成 Kubernetes

对于实验和“尝试”,在我看来,“树莓派”上的 Kubernetes 集群比同一个 Minikube 传达的操作感觉要好得多,因为集群的所有组件都已安装并“像成人一样”工作。

未来,有一个想法是向集群添加整个 CI/CD 周期,完全在 Raspberry Pi 上实现。 如果有人分享他们在 AWS Graviton 上设置 K8 的经验,我也会很高兴。

PS 是的,“生产”可能比我想象的更接近:

在 Raspberry Pi 上从头开始完成 Kubernetes

聚苯硫醚

另请阅读我们的博客:

来源: habr.com

为具有 DDoS 保护、VPS VDS 服务器的站点购买可靠的主机 🔥 购买具备 DDoS 防护的可靠网站托管服务,包括 VPS 和 VDS 服务器 | ProHoster