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コマンドを紹介します。

  1. kubectl apply -f
  2. kubectl delete -f
  3. kubectl get pods | services | nodes -n
  4. kubectl exec -it –
  5. kubectl logs -n (kubectl get podsで探してからの方が早いかもしれない)
  6. kubectl edit configmap -n kube-flannel
  7. kubectl logs