k8s構築日記その3。
過去の奮闘記録。 構築日記その1 構築日記その2 実は今まで一回もK8S上でアプリを動かしたことがないという。 これは正直恥ずかしいですね。構築はしたが、動かせはしていないという話だな。勿体無い!! ってことで、今回は、K8S構築日記その3ということで、構築日記その1と構築日記その2を振り返りつつ、 ちゃんとアプリをクラスタ上で動かせるようにしたいと思います。はい。動かすアプリは、そうです、例の競馬アプリです。 そして、外部に公開して、しかも、インターネットから見えるようにしたいと思う。これができるとまじで最高だぜへへ。
まずは構築から昔の俺が残してくれた手順に従って構築をやります。
クラスター情報
master : controle plane gamma : worker zeta : worker
でやりたいと思います。ちなみにマスターのosはraspbianです。
バージョン情報
このサイト を参考にバージョンを決めた。 k8s (kubelet : kubeadm) : 1.27 containerd : 1.7.0 + なので、1.7.2でインストールする。 containerdのarmバイナリ
k8sを導入するまとめ
1. containerdを導入。
k8sのv1.25以降は、コンテナエンジンにdockerエンジンを使えません。(理由は、dockerがcriを満たしていないから)。criを満たしてるcontainerdをインストールする必要があります。 containerdのインストールは、githubのリポジトリからできます。同時に、runcとcniもインストールしてださい。 ちなみに、cniは、container network interfaceで、まあ、コンテナのネットワークを操るためのapiですね。 で、そのapiを操るのが、cniプラグイン。で、cniプラグインには、calicoだったり、flannelだったりがあるわけですね。で、プラグインには、3種類のモードがあって、オーバーレイと、ルーティングと、アンダーレイですね。オーバレイを使うことで、別のホスト上にいるコンテナ同士が同じセグメントにいるようになります。
で、だけど、/etc/cni/net.dにはデフォルトのやつ、おかなくて結構です。まったく問題ありませんので、お気になさらず。ということで。 ちなみに、これにはruncも入っているので安心してインストール/ダウンロードしてください。全く問題ないです。
wget https://github.com/containerd/containerd/releases/download/v1.7.20/cri-containerd-cni-1.7.20-linux-arm64.tar.gz
mkdir containerd_dir
mv cri-containerd-cni containerd_dir
tar xvfz cri
sudo mkdir /etc/containerd
必要なものを必要なディレクトリに置いてくれるように以下のスクリプトを実行
#!/bin/bash
# Define the paths to your installation directories
CONTAINERD_BIN_DIR="/usr/local/bin"
CONTAINERD_SBIN_DIR="/usr/local/sbin"
CONTAINERD_ETC_DIR="/etc/containerd"
CONTAINERD_SYSTEMD_DIR="/etc/systemd/system"
CONTAINERD_OPT_DIR="/opt/"
# Define the source directory where you extracted the Containerd files
SOURCE_DIR="/home/ray/containerd_dir"
# Move binary files to /usr/local/bin
cp -r "$SOURCE_DIR/usr/local/bin/"* "$CONTAINERD_BIN_DIR/"
# Move sbin files to /usr/local/sbin
cp -r "$SOURCE_DIR/usr/local/sbin/"* "$CONTAINERD_SBIN_DIR/"
# Move etc files to /etc/containerd
cp -r "$SOURCE_DIR/etc/"* "$CONTAINERD_ETC_DIR/"
# Move systemd service file to /etc/systemd/system
cp "$SOURCE_DIR/etc/systemd/system/containerd.service" "$CONTAINERD_SYSTEMD_DIR/"
# move cni and other utils to opt
cp -r "$SOURCE_DIR/opt/containerd" "CONTAINERD_OPT_DIR"
cp -r "$SOURCE_DIR/opt/cni" "CONTAINERD_OPT_DIR"
# Reload systemd to recognize the new service
systemctl daemon-reload
echo "Containerd installed successfully."
nerdctlも一緒にいんすとる
wget https://github.com/containerd/nerdctl/releases/download/v1.7.6/nerdctl-1.7.6-linux-arm64.tar.gz
tar xvfz nerdctl
raspbianでは、iptalbesがなくて走らなかったのでインストールしました。。
あと、nerdctlでのコンテナの立て方ですが、このようにしないとうまくいかないのでご注意を。
sudo nerdctl container create --name test -it ubuntu /bin/bash
よろピクミンです。
2. ホストの設定
swapのoff
swapoff -a
/boot/firmware/cmdline.txtに
cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
を追加!!
大事なこと忘れてました!!!!!!!!!!!!! worker nodesはport 10250 を開けてください。
/etc/containerd/config.tomlを作る
containerd config default > /etc/containerd/config.toml
Forwarding IPv4 and letting iptables see bridged traffic
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# sysctl params required by setup, params persist across reboots
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
# Apply sysctl params without reboot
sudo sysctl --system
To verify that everything goes well run following command
lsmod | grep br_netfilter
lsmod | grep overlay
check if the variables below are set to 1
sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward
cgoup drivers
On Linux, control groups are used to constrain resources that are allocated to processes.
Both the kubelet and the underlying container runtime need to interface with control groups to enforce resource management for pods and containers and set resources such as cpu/memory requests and limits. To interface with control groups, the kubelet and the container runtime need to use a cgroup driver. It’s critical that the kubelet and the container runtime use the same cgroup driver and are configured the same. とのことです。で、
cgroupfsとsystemdの2つのcgroup driverがあると。で、systemd cgroup driverが推奨されています。で、コンテナランタイムも、kubeletもどっちも同じcgroup driverを使わないといけないと。 To use the systemd cgroup driver in /etc/containerd/config.toml with runc, set
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
...
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
これを変更した後に、containerdを再起動しないとマジでバグります。よろしくおねげーします。まじd。
sudo systemctl restart containerd
3. kubeadm, kubelet, kubectlのインストール
sudo apt-get update
# apt-transport-https may be a dummy package; if so, you can skip that package
sudo apt-get install -y apt-transport-https ca-certificates curl
# This overwrites any existing configuration in /etc/apt/sources.list.d/kubernetes.list
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
sudo systemctl start kubelet
sudo systemctl enable kubelet
でkubeletを起動
この時点ではまだkubeletは起動失敗する。それは、kubeadmで生成されるkubeletのconfig fileがないからですね。
ここまでがワーカ、マスターに限らず必要な操作になります。
4. マスター上で、kubeadmを使って、各コンポーネントを起動させる
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
トークンの再発行
kubeadm token create --print-join-command
–print-join-commandをつけると、kubeadmのjoinに必要なコマンドとかも全部出してくれる。
5. ワーカ上で、4を実行後に得られるトークンを使って、マスターと接続する
sudo kubeadm join <master-node-ip>:<master-node-port> --token <token> --discovery-token-ca-cert-hash sha256:<hash>
ここで、
ray@master:~ $ mkdir -p $HOME/.kube
ray@master:~ $ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
cp: overwrite '/home/ray/.kube/config'?
ray@master:~ $ sudo chown $(id -u):$(id -g) $HOME/.kube/config
これで
kubectl get nodes
これで
NAME STATUS ROLES AGE VERSION
gamma Ready <none> 7m4s v1.28.12
master Ready control-plane 13m v1.28.12
ゲットできるようになったよ。いいじゃない。
6. cniプラグインをインストールする
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"
By default, tokens expire after 24 hours. If you are joining a node to the cluster after the current token has expired, you can create a new token by running the following command on the control-plane node:
kubeadm token create
でもちょっと待てよ。これってもしかして既にインストールされているのではないかという話。 いや、わからん。/etc/cni/net.d/bridgeってのは何だね。わからんねー-。
The file you've shown, nerdctl-bridge.conflist, is a configuration file for a CNI (Container Network Interface) plugin called "bridge." This CNI plugin is typically used for container runtimes like Docker or containerd to set up network connectivity for containers. It may not be directly related to Kubernetes networking, but it can be used by container runtimes on a Kubernetes node to manage networking for pods.
Let's break down the contents of the file:
ってことらしいのよね。つまり、k8sのネットワークとは関係ないってことなんですよね。マジで。 ってことでやっぱりcniプラグインをインストールする必要があるんですね。 はい、必ずインストールしてください。でないとpod間で通信ができません。ので必ず入れてください。よろしくおねがいします。flannelを入れてください。で、入れたらその設定が、全部のノードの/etc/cni/net.d/の中に書き込まれます。これは、kubeadmでノードを停止させたあとも残ります。ので、気をつけてください。もし違うプラグインをインストールしたいのであれば、必ず/etc/cni/net.dに入ってるものを一度消してください。よろしくおねがいします。
というのも、calicoを入れたあと、flannelを入れて、マジでうまく行かなかかった。これで結構時間も食ってしまった。。。
7. kubectlで、apiserverにアクセスするための設定ファイルをホームディレクトリに移動させる。
これやらないと無限にエラーでる。
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
って書いてあるんだけど、これではうまくいかなくてね。 ふつーに、
sudo cp -i /etc/kubernetes/admin.conf $HOME/
export KUBECONFIG=$HOME/admin.conf
して、設定ファイルにパスを通す感じ。 これで、
kubectl get nodes
ができる。
kubectl --kubeconfig ./admin.conf get nodes
でもおけ。設定ファイルを各ワーカに配置すれば、ワーカからも上のコマンドで確認ができるようになる。
8. remove the node
kubectl drain <node name> --delete-emptydir-data --force --ignore-daemonsets
その他、必要なtips
By default, your cluster will not schedule Pods on the control plane nodes for security reasons. If you want to be able to schedule Pods on the control plane nodes, for example for a single machine Kubernetes cluster, run:
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
名前解決あたりのtips
外部通信DNS
corednsがpodネットワークのdnsです。 で、こいつの設定方法が、
kubectl edit configmap -n kube-system coredns
で、
apiVersion: v1
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . 8.8.8.8 8.8.4.4 {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
forwardのところ、ちゃんと設定すれば問題なく行けるようになる。 で、この設定がpodの/etc/resolve.confに反映されるのが作られる瞬間だから、設定を変更したらポッドも作り直す必要ありです。
内部通信
,ray@master:/opt/cni $ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
network-debug 1/1 Running 0 15m 10.244.1.21 gamma <none> <none>
pod-one 1/1 Running 0 21m 10.244.1.19 gamma <none> <none>
pod-two 1/1 Running 0 21m 10.244.1.20 gamma <none> <none>
このコマンドで見られる。ポッド🥫の通信ができない状態です。 これどうやって解決するんだろう??わかりません。
いや、基本的にはpodにはDNS名が付与されないみたいですね。 >Pods don’t generally have DNS resolvable names. That’s what Services are for. There are some affordances for this, but they are the exception, rather than the rule. Why do you want to resolve an individual pod?
つまり、pod間でdnsで通信しようとしてもダメってことですね。なるほど。そして、これはchatGPTくんの供述ともあう。
In Kubernetes, individual pods are not typically assigned domain names in the same way that services are. Instead, each pod is assigned an IP address within the cluster, which can be used for communication within the cluster.
chatGPTに聞いてみた時の回答がこちら
In Docker Compose, containers can indeed communicate with each other using their container names as hostnames because Docker Compose sets up a default network with DNS resolution for container names.
In Kubernetes, while individual pods don't get DNS names by default, services in Kubernetes do provide a similar capability for stable DNS-based communication. Here's a comparison to clarify:
Docker Compose
Containers can communicate with each other using container names as hostnames directly.
Docker Compose sets up a default network with DNS resolution for the container names.
Kubernetes
Pods: Individual pods are ephemeral and can be replaced, hence they don't have stable DNS names by default.
Services: Services in Kubernetes provide stable DNS names and can be used to expose a set of pods. Pods behind a service can be accessed using the service's DNS name.
Communication in Kubernetes
Pod-to-Pod Communication: While direct communication between pods using their IP addresses is possible, it is not recommended due to the dynamic nature of pods.
Service-to-Pod Communication: Kubernetes services provide a stable DNS name for a group of pods, making it the preferred method for communication. For example, if you have a service named myservice in the default namespace, it can be accessed using myservice.default.svc.cluster.local.
ということなので、やはりdnsにはpodの名前は追加されないみたいだね。これだいぶ大きな発見だと思う、、、うん。かなり大きな発見。昨日はこれのせいで徹夜しそうになったね。 ってことで、新しくサービスって概念を学びましたね。docker composeでcontainer nameで名前解決ができていたから、こっちでもできるとばかり思っていた。
他の記述
A pod has its own IP, so each container inside shares that IP. If these containers should be independent (i.e. don't need to be collocated), they should each be in their own pod. Then, you can define a service for each that allows DNS lookups as either "$SERVICENAME" from pods in the same namespace, or "$SERVICENAME.$NAMESPACE" from pods in different namespaces.
Generally, Containers running inside a pod, shares pod's IP and Port space. The communication between the containers will happen through localhost by default. To communicate between the containers using the name(like DNS), the containers should run in the independent POD and expose it as a service to rest of application world.
ということなので、pod間の通信はまあipアドレスでできるけど、名前解決をしたいのであればserviceをつけなさいということですね。了解です。
最後です。では一体どうやって外部にサービスをpublishするのでしょうか??ここはかなり気になるところですね。
結論、ingressかLBを使います。 ingress = L7 loadbalancer Lb = L4 Loadbalancer といった感じです。ここでちょっと面倒くさいのは、うちの会社ではLB = l7 load balancerなところだね。 ところで、serviceっていう概念はVIPに非常に近いものがあるのではないかと思った。vipって本当はl4 lbのことなんだけどね。はい。 では、せっかくなので、l4のLBでサービスをpublishしたいと思います。
はい、manifestを投げてくださーい
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-group1
spec:
replicas: 2
selector:
matchLabels:
app: nginx
group: group1
template:
metadata:
labels:
app: nginx
group: group1
common: nginx-group
spec:
containers:
- name: nginx
image: nginx:1.21.6
ports:
- containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-group2
spec:
replicas: 2
selector:
matchLabels:
app: nginx
group: group2
template:
metadata:
labels:
app: nginx
group: group2
common: nginx-group
spec:
containers:
- name: nginx
image: nginx:1.21.6
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-loadbalancer
spec:
type: LoadBalancer
selector:
common: nginx-group
ports:
- protocol: TCP
port: 80
targetPort: 80
ちなみにnginxのプロキシ設定で気をつけてほしいこと
location /kube {
proxy_pass http://192.168.3.8:31630/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
ここ、proxy_pass http://hogehoge/ にするのと、proxy_pass http://hogehoge にするのは違うって話。前者は完全に書き換えてくれるけど、後者は/kubeが引き継がれてしまう。気をつけてください。
まとめ
- kubernetesをraspiクラスタにインストールした
- serviceについて少し詳しくなった。イメージ的にはVIP。ロードバランサ。podの名前はcorednsに登録されないが、serviceは登録される。
- podで名前解決をしようとしていて、ずっと手こずっていた。しかし、podはそもそもcorednsには登録されないという話。これは大事。どちらかというとserviceにpodを所属させるイメージだ。マジでそうだ。docker composeみたいにコンテナ名で名前解決がされるとか、そういう感じではない、という話だ。
- serviceを外部に公開するためには、serviceのタイプをloadbalancerにするのがいいという話。こうするとL4 LBが完成。ちなみに、L4LBってただのルータだよな、ってのはここだけの話。
- kubectlコマンド色々と使った。
最後にkubectlコマンドを紹介します。
- kubectl apply -f
- kubectl delete -f
- kubectl get pods | services | nodes -n
- kubectl exec -it –
- kubectl logs -n (kubectl get podsで探してからの方が早いかもしれない)
- kubectl edit configmap -n kube-flannel
- kubectl logs