尝试自己组建 Kubernetes 集群并进行故障排除【The Hard Way】【ニフクラ】

本文是富士通云技术的2021年圣诞日历的第八篇文章。

7日目是由@HanchA进行的”安装Postfix,Dovecot和Zabbix来监控服务器”活动。他利用在分配工作中学到的操作知识从零开始建立了环境,我认为这非常了不起。我也希望自己能够努力不懈。

今天,是2021年度新员工加藤为您带来的。在新人培训期间,我有幸体验了我们公司的托管Kubernetes服务- Hatoba,并对Kubernetes进行了深入学习。虽然我仍在努力学习中,为了获取CKA,但今天我想在这里分享其中之一,即KubernetesThe Hard Way的相关内容!

目标读者

    • Kubernetes 聞いたことあるけど全然分かんない!という人

CKA (Certified Kubernetes Administrator) の資格取得を考えている人
Kubeadm とかでクラスターは作ったことあるけど中身をちゃんと知りたい人

Kubernetes The Hard Way 是什么意思?

Kubernetes The Hard Way 是一个用于手动搭建 Kubernetes 集群的教程。它被定位为初学者的学习任务,并且通过按照步骤一步一步地构建 Kubernetes 集群的组件来理解集群内部的结构。

当先辈教练告诉我要去学习「以最艰难的方式掌握 Kubernetes !」的时候,我对 Kubernetes 的知识一无所知,感觉非常困难。但到最后完成的时候,我有一种「啊~完全理解了 Kubernetes 哈」的心情。

提前准备:将Kubernetes的配置图深深铭刻在脑海中。

我发现有一个名为Kubernetes组件的页面,通过提前阅读该页面,我在进行教程时对其有了更深入的理解。

コントロールプレーンには etcd や kube-apiserver がいて、ワーカーノードには kubelet がいる……。 kube-apiserver はユーザーが kubectl などのクライアントから送ってきたリクエストを受け付けていて、 kubelet はワーカーノードで実際のコンテナの指揮をしたり、 kube-apiserver との通信をしているんだ……みたいな感じでイメージをつけていきました。

components-of-kubernetes

The Hard Way を往く

作業ログはあまりにも長いので、 GitHub のプロジェクトにまとめています。

以 NIFCLOUD 方式进行 Kubernetes 的困难之旅

环境

    • ニフクラ VM (e-medium4)

 

    • プライベートLAN

 

    • マルチロードバランサー

 

    • Ubuntu 20.04

 

    Kubernetes 1.21.1

苦労したポイント

恥を忍びつつ、躓きから得た教訓や学びをいくつかご紹介しようと思います。初心者なので間違っていることがありましたらご指摘いただけると嬉しいです。

    • ネットワーク構成

 

    • kube-apiserver から kubelet への通信 (kube-apiserver の設定)

 

    • CoreDNS がループしてしまい名前解決に失敗する問題

 

    Pod が名前解決に失敗する問題 (kube-proxy)

网络配置

Kubernetes 是一个能够协调多个节点(物理服务器或虚拟机)以便部署应用程序的工具,因此网络配置是至关重要的一部分。

在构建集群的最后阶段,启动控制平面上的组件以及启动工作节点上的组件后,检查节点的状态时可能会发现其为 NotReady(未准备就绪)状态。
检查 kubelet 的日志后发现,注册节点到 kube-apiserver 中失败了。建议重新检查防火墙和路由设置,并确认网络通信是否正常,这是基本的故障排除步骤。

私の場合は、ワーカーからロードバランサー経由で kube-apiserver に到達した通信が、ルーティングの設定がおかしくなっていたことで、ロードバランサーを経由せずに返ってくることが原因でした2。

私はニフクラ上で構築しましたが、私のネットワーク知識が少なかったことも相まって、 GCP からニフクラへの読み替えに苦労して、何度も試行錯誤を繰り返しました。
しかしそのおかげで、ネットワーク全般においてもニフクラの使い方においても、知識がだいぶ増えたので結果オーライです ^^

kube-apiserver から kubelet への通信 (kube-apiserver の設定)

kubectl でクラスター操作をしているときは、 kubectl が kube-apiserver にアクセスし、 kube-apiserver が kubelet に対して指示を送っています。なので例えば、 kubectl exec をしたときにうまくコンテナに接続できなければ、まず kube-apiserver と kubelet 間の接続を疑ってみることが必要です。

私の環境の場合は /etc/hosts などを設定していなかったので kube-apiserver が worker0 などのホスト名で kubelet にアクセスしようとすると名前解決ができずに失敗します。
これは kube-apiserver の設定を変更することで回避できます。 Kubernetes The Hard Way では systemd のサービスとして kube-apiserver を起動しているため、第8章で作成している systemd の unit ファイルに項目を追加することで変更ができます。

我們將添加 –kubelet-preferred-address-types=InternalIP,ExternalIP 選項,使其使用 IP 地址而非名稱。

cat <<EOF | sudo tee /etc/systemd/system/kube-apiserver.service
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-apiserver \\
  --advertise-address=${INTERNAL_IP} \\
  --allow-privileged=true \\
  --apiserver-count=3 \\
  --audit-log-maxage=30 \\
  --audit-log-maxbackup=3 \\
  --audit-log-maxsize=100 \\
  --audit-log-path=/var/log/audit.log \\
  --authorization-mode=Node,RBAC \\
  --bind-address=0.0.0.0 \\
  --client-ca-file=/var/lib/kubernetes/ca.pem \\
  --enable-admission-plugins=NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \\
  --etcd-cafile=/var/lib/kubernetes/ca.pem \\
  --etcd-certfile=/var/lib/kubernetes/kubernetes.pem \\
  --etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \\
  --etcd-servers=https://10.240.0.10:2379,https://10.240.0.11:2379,https://10.240.0.12:2379 \\
  --event-ttl=1h \\
  --encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \\
  --kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \\
  --kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \\
  --kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \\
  --kubelet-preferred-address-types=InternalIP,ExternalIP \\
  --runtime-config='api/all=true' \\
  --service-account-key-file=/var/lib/kubernetes/service-account.pem \\
  --service-account-signing-key-file=/var/lib/kubernetes/service-account-key.pem \\
  --service-account-issuer=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \\
  --service-cluster-ip-range=10.32.0.0/24 \\
  --service-node-port-range=30000-32767 \\
  --tls-cert-file=/var/lib/kubernetes/kubernetes.pem \\
  --tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

CoreDNS 循环并导致名称解析失败的问题。

私の場合は上の対処で kube-apiserver から kubelet への通信に成功してもなお、第12章でセットアップした DNS アドオンの検証がうまくいきませんでした。

在检查CoreDNS的状态时,我们发现它持续启动失败,并显示CrashLoopBackOff错误。这是因为从Ubuntu 18.04开始,默认使用本地DNS stub解析器(systemd-resolved),而这就是问题的根源。

Kubernetes The Hard Way のデフォルトの設定値では、 kubelet が名前解決のために /run/systemd/resolve/resolv.conf を見に行くよう設定されています。 kubelet はその設定値に基づき、該当ファイルを CoreDNS のコンテナに渡します。ですが、そのファイルには 127.0.0.1 が指定されているため、結果的にループしてしまいクラッシュします。つまり、 127.0.0.1 ではなく 8.8.8.8 などの外部 DNS を参照するようにすればループの発生を抑えられそうです。

/run/systemd/resolve/resolv.conf は systemd-resolved によって生成されるファイルのため直接編集してはいけないので、 /etc/systemd/resolved.conf を編集してから、 systemd-resolved のデーモンを再起動することで /run/systemd/resolve/resolv.conf を再生成させます。

{
  NAMESERVER_ADDRESSES=8.8.8.8
  sed -i "s/^DNS=127.0.0.1$/DNS=${NAMESERVER_ADDRESSES}/" /etc/systemd/resolved.conf
  sudo systemctl restart systemd-resolved
}

在运行上述脚本后,执行 sudo systemctl restart kubelet 以重新启动 kubelet,就可以将 CoreDNS 成功置于 Running 的状态了。

追記:我已经撰写了一篇关于此事的独立文章。
https://sogo.dev/posts/2022/12/kubernetes-coredns-loop

Pod 的名字解析失败问题(kube-proxy)

这是在教程结束后,在我创建的集群中进行验证时发现的问题,我创建的集群居然出现了 Pod 在 CoreDNS 中无法解析名称的失败!(我在第12章中已验证了设置的 DNS 插件,所以可能是在玩耍时弄坏了……。)

検証用の Pod 内で kubectl exec -ti dnsutils — dig kubernetes で名前解決を試していたときに、以下のエラーが返ってきました3。

;; reply from unexpected source: 10.200.2.177#53, expected 10.32.0.10#53

コンテナ内の /etc/resolv.conf に nameserver 10.32.0.10 が設定されており、これが kube-dns の Cluster IP なのですが、実際に Pod が置かれているノードの IP (10.200.2.177) で返ってきているためエラーになっています。

この問題は GitHub の Issue にも報告されており、同一のノード上に Pod と KubeDNS があるとうまく動かないという問題のようです。
modprobe br_netfilter をすることで直ります。詳しくないのでよくわかりませんが、このモジュールをロードすることでいい感じに NAT してくれるようになるんだと思います。
ちなみに、 kube-proxy のオプションにある –masquerade-all をつける方法はうまくいきませんでした。

总结

当对这些问题进行总结的时候,发现全部都是与网络有关的问题,比如路由和DNS。虽然很平常,但是我的感觉是 “Kubernetes网络真难啊~~”。另外,由于Kubernetes在各个地方都使用证书,所以每当负载均衡器的IP发生变化时,就必须重新生成和重新分发所有证书,这真的很麻烦。

希望这个结构复杂的长文能对打算使用 Kubernetes The Hard Way 的人们有所帮助!

明天是 @ablengawa 的“使用 Blender 生成相对逼真的图像”。期待吧!

额外: 我想要通过Sonobuoy!编辑

有一种名为Sonobuoy(读作ソノブイ)的工具,可用于对Kubernetes集群进行端到端测试。通过运行该工具,我们可以将新建的集群视为与Certified Kubernetes相等的集群。因此,我们将对这个新建的集群进行测试。

结果很糟糕

デバッグしたりして色々頑張ったのですが、何も分からずに終わりました。弊社のマネージド Kubernetes の Hatoba で作ったクラスターを同テストにかけてみたら、あっさりクリアしたので、「さすが Certified Kubernetes は違うわ〜」と思いました。

还有很长的路要走呢……

文献引用

kelseyhightower/kubernetes-the-hard-way
Kubernetes 组件
集群网络
kube-apiserver
kube-proxy
coredns/coredns/plugin/loop
当 pod 内的容器与 kube-dns 容器在同一节点上时,KubeDNS 在 pod 内部无法工作。

他还在内部博客上写了一篇文章,标题为“尝试使用Nifcloud Kubernetes Service的新功能LoadBalancer部署Elasticsearch / Kibana”。↩当时,多路负载均衡器是全球和用于Kubernetes的私有LAN的2-口架构,并且连接到与默认网关设置的路由器也连接到了全球。当Worker节点通过多路负载均衡器访问kube-apiserver时,返回的通信不会通过负载均衡器流回,而是通过路由器返回,导致通信失败。解决方法是将多路负载均衡器改为1-口架构(仅全球)。↩

参见调试DNS解析kubectl apply -f https://k8s.io/examples/admin/dns/dnsutils.yaml。↩

广告
将在 10 秒后关闭
bannerAds