从零开始在Raspberry Pi上完成Kubernetes





最近,一家著名的公司宣布将笔记本电脑产品线转移到ARM体系结构。听到这个消息,我想起来了:在再次查看AWS中EC2的价格时,我注意到Gravitons的价格非常可口。当然,要抓住的是它是ARM。那时我什至没有想到ARM是非常认真的……



对我而言,这种架构一直是移动和其他物联网领域的重要内容。 ARM上的“真实”服务器在某种程度上是不寻常的,甚至在某些方面甚至是荒谬的。但是,一个新的想法陷入了我的头脑,因此一个周末,我决定检查一下今天可以在ARM上启动什么。为此,我决定从一个亲密的亲爱的Kubernetes集群开始。不仅是一些有条件的“集群”,还包括“成人方式”的所有事物,因此它与我在生产中看到的尽可能多。



根据我的想法,应该可以从Internet访问该群集,应该在其中运行一些Web应用程序,并且至少应该进行监视。要实现此想法,您将需要一对(或更多)Raspberry Pi 3B +或更高版本。AWS可能已经成为进行实验的平台,但是对我来说很有趣的是“树莓”(它们仍然闲置)。因此,我们将部署一个带有Ingress,Prometheus和Grafana的Kubernetes集群。



制备“覆盆子”



安装操作系统和SSH



我对选择要安装的操作系统并没有太在意:我只是从官方网站上获取了最新的Raspberry Pi OS Lite 那里也提供安装文档,所有步骤必须在以后的群集的所有节点上执行。接下来,您需要执行以下操作(也在所有节点上)。



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



  1. 为了使群集正常工作,主服务器必须具有静态IP地址,工作节点必须具有静态IP地址。为了便于设置,我更喜欢静态地址。
  2. 可以在OS中配置静态地址(文件中/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


然后从更改类型vxlan,以在设置host-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允许您按自己的意愿配置某些组件,而无需编辑文件。实际上,这只是一个“不要求面包”的二进制文件。



因此,转到docs / installation部分中的helm.sh并从此处执行命令:



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


之后,添加图表存储库:



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


现在,让我们根据以下想法安装基础结构组件:



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


入口控制器



第一个组件Ingress控制器相当容易安装,可以直接使用。为此,只需转到站点上的裸机部分,然后从那里执行安装命令:



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


但是,此刻,“树莓”开始紧张,并进入磁盘IOPS。事实是,与Ingress控制器一起安装了大量资源,发出了许多API请求,因此,有很多数据写入etcd。通常,第10类存储卡的生产率不是很高,或者SD卡基本上不足以承受这种负载。但是,五分钟后一切开始。



创建了一个名称空间,并在其中显示了一个控制器及其所需的一切:



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安装。



找到Prometheus,创建一个名称空间并安装在其中:



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.yamlGit存储库中的文件中,并附带了本文的示例必须在要绑定Prometheus的节点的磁盘上预先创建PV的目录:在示例中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和证书经理



对于图表和仪表板,请安装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


要订购证书,请安装cert-manager要安装它,请参阅文档,其中提供了适用于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,则需要配置另一个群集发行者。可以在我们的文章“ Kubernetes上的使用cert-manager进行加密的SSL证书”中找到更多详细信息



我本人决定使用文档中示例中的版本,并确定LE的暂存版本就足够了。更改示例中的电子邮件,将其保存到文件并将其添加到群集(cert-manager-cluster-issuer.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。(端口是随机生成的,因此您将拥有不同的端口。)



以下是示例证书(cert-manager-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资源,通过该资源,我们将进行加密验证:



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。我们需要对其Helm版本进行一些修复,根据生成的证书更改TLS的设置。



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



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"}


我们检查grafana端口443是否已添加到Ingress并通过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仪表板外观如下:我







还建议为节点导出器添加一个仪表板:它将详细显示“树莓”发生了什么(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负载的示例:







我认为,对于实验和“尝试”,“树莓”上的Kubernetes集群比同一个Minikube更好地传达了运行的感觉,因为该集群的所有组件都已安装并且可以正常工作“以成人的方式。”



将来,有一种想法可以将整个CI / CD周期添加到群集中,该过程完全在Raspberry Pi上实现。如果有人分享他们在AWS Gravitons上设置K8的经验,我也将感到高兴。



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







PPS



另请参阅我们的博客:






All Articles