大人気K8Sシリーズ。 今回でなんと第4回目 まずは、過去の振り返りをやっていきましょう。

振り返り

過去の奮闘記録。 構築日記その1

構築日記その2

構築日記その3 第1回目と第二回目はインフラ構築を頑張っていた感じです。引っ越しとかあって、K8Sの勉強は飛び飛びになっている感じでね。 そして、第3回にして、やっと色々とインフラ構築をスムーズにできて、さまざまな概念についても理解できてきた、という感じです。 はい、サービスね。これが大事な概念だった。POD間で通信をするためには、Serviceが必要だったのではないか?とね。ServiceでPODをcoreDNSに登録することができる。 そして、基本的に一つのサービス内での通信はしない。サービス間の通信はあるが。なので、docker composeのようにはいかないという話だな。

そして、今回の第4回目では、K8Sの使い方をもっと詳しく理解していきましょう、という回になっています。 具体的には、「つくって、こわして、直して学ぶkubernetes入門」という本に従って色々と進めていきたいと思っています。はい。 そして、第5回目に、私が開発した競馬システムをついにデプロイする、という感じに持っていきたいと思っています。よろしくお願いします。

まずは、harborにログインしておく

の前にロボットアカウントを作らないといけない感じ?

docker login 100.64.1.61:20080 -u username -p password server

nerdctlを使っているので、buildを使うためにbuildkitをインストールする必要がある。

しかも、buildkitdというのを走らせておく必要がある。

テストでgoで簡易サーバを作って走らせる

package main

import (
        "fmt"
        "log"
        "net/http"
)

func main() {
        http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w,"Hellow, wordl!")
        })

        log.Println("starting server on port 8880")
        err := http.ListenAndServe(":8880", nil)
        if err != nil {
                log.Fatal(err)
        }

}

dockerfile

FROM golang:1.23 AS builder
WORKDIR /app
COPY . .
ENV CGO_ENABLED=0
RUN go mod tidy
RUN go build -o hello .

FROM scratch
COPY --from=builder /app/hello /hello
ENTRYPOINT ["/hello"]

go.modを入れるのを忘れずに。 go mod init github.com/hogehoge とかで問題ない。

harborにpushする

sudo docker tag hello-server:1.0 harbor.ingenboy.com/your-project/hello-server:1.0
sudo docker login harbor.ingenboy.com
sudo docker push harbor.ingenboy.com/test/hello-server:1.0

INFO[0000] pushing as a reduced-platform image (application/vnd.docker.distribution.manifest.v2+json, sha256:829e18e060ed137fd22e9a9342c0f8e8a53ff5c5103e551d116020c8611c6ce5)
manifest-sha256:829e18e060ed137fd22e9a9342c0f8e8a53ff5c5103e551d116020c8611c6ce5: waiting        |--------------------------------------|
config-sha256:e2c92b75c6c63eb9edff9a6e8f3caa832b476d763a424c68ceb3a6e7d8d66b5a:   done           |++++++++++++++++++++++++++++++++++++++|
elapsed: 0.4 s                                                                    total:  598.0  (1.5 KiB/s)
FATA[0000] failed to copy: unexpected status from PUT request to https://harbor.ingenboy.com/v2/test/hello-server/blobs/uploads/c93fd610-3616-4e4a-85f6-eec987460bf0?_state=2_rJUktfTkKy8Ursd7Da3_Weiu7UzcSi_Q5vuLtFvOB7Ik5hbWUiOiJ0ZXN0L2hlbGxvLXNlcnZlciIsIlVVSUQiOiJjOTNmZDYxMC0zNjE2LTRlNGEtODVmNi1lZWM5ODc0NjBiZjAiLCJPZmZzZXQiOjAsIlN0YXJ0ZWRBdCI6IjIwMjUtMDEtMTRUMTM6MzM6MTYuOTU1MTI1MDcxWiJ9&digest=sha256%3A424ca45be863e2033647e5e492f2f820172e0a376b2dafef7d47083702bd5a78: 413 Request Entity Too Large

request entity too largeのエラー。 これはnginxの設定のせいらしい。何を変えればいいかというと、以下です。

server {
                listen 443 ssl;
                server_name harbor.ingenboy.com;
    ssl_certificate /etc/letsencrypt/live/harbor.ingenboy.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/harbor.ingenboy.com/privkey.pem; # managed by Certbot
                client_max_body_size 1024M;

                location / {
                        proxy_pass http://100.64.1.61:20080;
                        proxy_set_header Host harbor.ingenboy.com;
                        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;

                }

        }

これで問題ないです。

再度upload

ray@delta:~|⇒  sudo docker push harbor.ingenboy.com/test/hello-server:1.0

INFO[0000] pushing as a reduced-platform image (application/vnd.docker.distribution.manifest.v2+json, sha256:829e18e060ed137fd22e9a9342c0f8e8a53ff5c5103e551d116020c8611c6ce5)
manifest-sha256:829e18e060ed137fd22e9a9342c0f8e8a53ff5c5103e551d116020c8611c6ce5: done           |++++++++++++++++++++++++++++++++++++++|
config-sha256:e2c92b75c6c63eb9edff9a6e8f3caa832b476d763a424c68ceb3a6e7d8d66b5a:   done           |++++++++++++++++++++++++++++++++++++++|
elapsed: 1.8 s 

harborを見るとちゃんとuploadされています。素晴らしい!

work dir

/home/ray/k8s_manifests/tukutte_kowasite_naosite

です。

namespace

kubectl create namespace tkn

作って、壊して、直してのtkn:チキン。

4章:とりあえず作ってみる

以下のnginxを立てる

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.25.3
    ports:
    - containerPort: 80

デプロイ

kubectl -n tkn apply -f ./nginx.yam

確認

kubectl get po -n tkn
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          115s

もう一発

apiVersion: v1
kind: Pod
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  containers:
  - name: hello-server
    image: blux2/hello-server:1.0
    ports:
    - containerPort: 8080

デプロイ

kubectl apply -f ./myapp.yaml -n tkn

確認

ray@master:~/k8s_manifests/tukutte_kowasite_naosite/chapter-04 $ kubectl get po -n tkn
NAME    READY   STATUS    RESTARTS   AGE
myapp   1/1     Running   0          79s
nginx   1/1     Running   0          4m25s

いい感じ。

特定のpodの中に入る方法

kubectl exec -it -n <namespace> <pod-name> -- /bin/sh

今回でいうと以下

ray@master:~/k8s_manifests/tukutte_kowasite_naosite/chapter-04 $ kubectl exec -it -n tkn nginx -- /bin/bash
root@nginx:/# 

素晴らしいね。

deleteはこうやる(pod単体)

ray@master:~/k8s_manifests/tukutte_kowasite_naosite/chapter-04 $ kubectl get po -n tkn
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          32m
ray@master:~/k8s_manifests/tukutte_kowasite_naosite/chapter-04 $ kubectl delete po nginx -n tkn
pod "nginx" deleted
ray@master:~/k8s_manifests/tukutte_kowasite_naosite/chapter-04 $ kubectl get po -n tkn
No resources found in tkn namespace.

マニフェストファイルごとはこう

kubectl delete -f <manifest-file>

5章:トラブルシューティング

kubectl get po で得られるポッドのステータス

Pending:準備中 Running : 実行中 ほかにも囲炉裏おあるけど、 ErrImagePull: イメージのプルに失敗 OOMKilled : コンテナが、out of memoryで終了。リソースを増やそう!

-o wideは覚えておこう

どのノードで動いているかがわかりますよ

ray@master:~/k8s_manifests/tukutte_kowasite_naosite/chapter-04 $ kubectl get po -n tkn -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP            NODE   NOMINATED NODE   READINESS GATES
myapp   1/1     Running   0          11m   10.244.3.12   zeta   <none>           <none>
nginx   1/1     Running   0          14m   10.244.2.15   phi    <none>           <none>

-o yamlもある

yaml形式でリソースを取得することができる。

kubectl describe pod -n

ray@master:~/k8s_manifests/tukutte_kowasite_naosite/chapter-04 $ kubectl describe pod myapp -n tkn
Name:             myapp
Namespace:        tkn
Priority:         0
Service Account:  default
Node:             zeta/192.168.3.5
Start Time:       Thu, 16 Jan 2025 13:51:50 +0000
Labels:           app=myapp
Annotations:      <none>
Status:           Running
IP:               10.244.3.12
IPs:
  IP:  10.244.3.12
Containers:
  hello-server:
    Container ID:   containerd://715c1f7dc367ca604acdcf2dc2ce4889aed247e946cef3e5278194cb1ff556d7
    Image:          blux2/hello-server:1.0
    Image ID:       docker.io/blux2/hello-server@sha256:35ab584cbe96a15ad1fb6212824b3220935d6ac9d25b3703ba259973fac5697d
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Thu, 16 Jan 2025 13:51:56 +0000
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-d9mmt (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  kube-api-access-d9mmt:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  16m   default-scheduler  Successfully assigned tkn/myapp to zeta
  Normal  Pulling    16m   kubelet            Pulling image "blux2/hello-server:1.0"
  Normal  Pulled     16m   kubelet            Successfully pulled image "blux2/hello-server:1.0" in 4.947s (4.947s including waiting)
  Normal  Created    16m   kubelet            Created container hello-server
  Normal  Started    16m   kubelet            Started container hello-server

podのipにアクセスしてみると、こんな感じ

ray@master:~/k8s_manifests/tukutte_kowasite_naosite/chapter-04 $ curl 10.244.3.12:8080
Hello, world!

ログを見る方法

ray@master:~/k8s_manifests/tukutte_kowasite_naosite/chapter-04 $ kubectl logs myapp -n tkn -f
2025/01/16 13:51:56 Starting server on port 8080

便利ですねーーー -f をつけると、tailの-fみたいに、どんどん流れてくる感じになります。素晴らしい。

6章 DeploymentとReplicaset

ここからですよ。ここから!

ReplicasetはPodの複製を作ります。

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: httpserver
  labels:
    app: httpserver
spec:
  replicas: 3
  selector:
    matchLabels:
      app: httpserver
  template:
    metadata:
      labels:
        app: httpserver
    spec:
      containers:
      - name: nginx
        image: nginx:1.25.3

こんな感じですね

ray@master:~/k8s_manifests/bbf-kubernetes/chapter-06 $ kubectl get po -n tkn     
NAME               READY   STATUS    RESTARTS   AGE
httpserver-dwnql   1/1     Running   0          39s
httpserver-lckqz   1/1     Running   0          39s
httpserver-tcfqt   1/1     Running   0          39s

replicasetは実はあまり使わない

という話らしい。実際は、deploymentをめっちゃ使うという話です。 ReplicasetとDeploymentのdiffは Deploymentを使うとローリングアップデートができる、という点です。 Replicasetではできません。覚えておいてください。 というか、Repilicasetは「全く同じポッドの集合」という概念みたいなものです。 deploymentを使ってポッドをupdateするときは、二つのreplicasetができます。 そんなに難しくないですね。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.24.0
        ports:
        - containerPort: 80

Deploymentに紐づくPodのログを取得する方法

kubectl logs deploy/<Deployment名>

今回の例でいうと

kubectl logs deploy/nginx-deployment -n tkn

結果はこんな感じになります。

ray@master:~/k8s_manifests/bbf-kubernetes/chapter-06 $ kubectl logs deploy/nginx-
deployment -n tkn
Found 3 pods, using pod/nginx-deployment-595dff4799-8zrm2
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/        
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2025/01/17 14:19:58 [notice] 1#1: using the "epoll" event method
2025/01/17 14:19:58 [notice] 1#1: nginx/1.24.0
2025/01/17 14:19:58 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6) 
2025/01/17 14:19:58 [notice] 1#1: OS: Linux 5.15.0-1066-raspi
2025/01/17 14:19:58 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576      
2025/01/17 14:19:58 [notice] 1#1: start worker processes
2025/01/17 14:19:58 [notice] 1#1: start worker process 29
2025/01/17 14:19:58 [notice] 1#1: start worker process 30
2025/01/17 14:19:58 [notice] 1#1: start worker process 31
2025/01/17 14:19:58 [notice] 1#1: start worker process 32

DeploymentとReplicaset,Podに関する僕の誤解を解く

まず、Deployment一つにつき、Replicasetが一つ紐づけられます。(というのも正確ではない) Deploymentを一つ作ると、自動的にReplicasetが一つできます。 そして、Replicaが9つある、という状態は、Podが9つある状態です。 updateの時は、deployment内に、新しい状態のポッドのレプリカセットと古い状態のポッドのレプリカセットの二つができます。

chatgpt先生に聞いた答えはこちら

k8sでdeployment内のpodをローリングアップデートするときは、
deployment内に、新しい状態のポッドのレプリカセットと古い状態のポッドのレプリカセットの二つができる、という認識で会っていますか? 

はい、その認識で正しいです。Kubernetes(k8s)でDeploymentをローリングアップデートする際には、古いバージョンのポッドのReplicaSetと新しいバージョンのポッドのReplicaSetが同時に存在することがあります。アップデートプロセス中、新しいReplicaSetのポッドが徐々に増加し、古いReplicaSetのポッドが徐々に減少していきます。これにより、サービスの中断を最小限に抑えながらアップデートが行われます。各ステップで指定された数のポッドが常に稼働していることを保証し、新旧のバージョンが一定の割合で共存する状態を作り出します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.24.0
        ports:
        - containerPort: 80

こちらのファイルでは、Podが9つあります。それを管理しているのは1つのdeploymentで、1つのReplicasetです。 OKですか?? で、ローリングアップデートするときは、各Podに対して一つ一つ実行されていく感じでございます。 よろしくお願いします。ということですね。

strategy typeについて

PODのUpdateをするときにローリングアップデートをするか、recreate updateをするかで選べる 以下はrecreate updateの例

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 10
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.24.0
        ports:
        - containerPort: 80
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 10"]

kubectl get po -n tkn –watch で反映の様子を見る

全断されていた。なるほどな。

rolling updateについて

まあ、これは順番にアップデートしていくやつですね。 で、全体の内のどれくらいのPODを止めてもいいかという指標がmaxUnavailable で、どれくらいの勢いで増やしていいかが、maxSurge

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 10%
      maxSurge: 10%
  replicas: 10
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.25.3
        ports:
        - containerPort: 80
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 10"]

これは、maxSurgeが10%、maxUnavailableが10%なので、10個のPodに対して、 1個ずつrollingUpdateしていることになりますね。

ちょうだいじ。サービスについて。

サービスは、PODやdeploymentを外部にサービスとして公開するためのネットワークルーティング 設定のようなものです。 書き方はこうです

apiVersion: v1
kind: Service
metadata:
  name: hello-server-service
spec:
  selector:
    app: hello-server
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080

ここでい一番大事だと個人的に思っているのは、spec.selector.app です。ここで、どのPODやdeploymentがこのサービスに紐づくかが決まるのです。

対応するデプロイメントがこちらです。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
  labels:
    app: hello-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-server
  template:
    metadata:
      labels:
        app: hello-server
    spec:
      containers:
      - name: hello-server
        image: blux2/hello-server:1.0
        ports:
        - containerPort: 8080

metadata.lables.app でつけた名前のリソースをサービスのspec.selector.appで指定します。 これがガチで大事だと思います。

DNS問い合わせでserviceにアクセスする方法

これまたおもろいですわ。 すごいです。 サービスのドメイン名は以下の規則でcoreDNSに登録されます。

<service名>.<Namespace名>.svc.cluster.local

という感じですね。

ray@master:~/k8s_manifests/bbf-kubernetes/chapter-06 $ kubectl exec -it -n tkn ubuntu-bash-deployment-cd49b75dd-nrl6x -- /bin/bash

実際がこちらです。 適当に作ったubuntuのコンテナに入って

root@ubuntu-bash-deployment-cd49b75dd-nrl6x:/# curl hello-server-service.tkn.svc.c
luster.local:8080

serviceのtypeについて

デフォルトではClusterIpです。これは、クラスタ内部からアクセスできるserviceです。 実際はingressを作って外部とやり取りをします。

後はNodePortもあります。これは、どのクラスタでも指定したIPでそのサービスにアクセスできるという、 穴だらけな感じのサービスです。

ray@master:~/k8s_manifests/bbf-kubernetes/chapter-06 $ cat service-nodeport.yaml 
apiVersion: v1
kind: Service
metadata:
  name: hello-server-external
spec:
  type: NodePort
  selector:
    app: hello-server
  ports:
    - port: 8080
      targetPort: 8080
      nodePort: 30599

外からアクセスしてみよう

ray@master:~/k8s_manifests/bbf-kubernetes/chapter-06 $ curl zeta.ingenboy.local:30
599
ray@master:~/k8s_manifests/bbf-kubernetes/chapter-06 $ chaptphi.ingenboy.local:3059b
ray@master:~/k8s_manifests/bbf-kubernetes/chapter-06 $ curl phi.ingenboy.local:30599
Hello, world!ray@master:~/k8s_manifests/bbf-kubernetes/chapter-06 $

えぐいて、ノードポート。何が起こっているや。 まあ、本番ではノードが故障することも考えて、やはりClusterIp + ingressがよさそうです。

configMapについて

これはかなり大事です。ないがしろにしない方がいいで。 ConfigMapを利用して、環境変数をPODに流し込む。わけです。

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
  labels:
    app: hello-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello-server
  template:
    metadata:
      labels:
        app: hello-server
    spec:
      containers:
      - name: hello-server
        image: blux2/hello-server:1.4
        env:
        - name: PORT
          valueFrom:
            configMapKeyRef:
              name: hello-server-configmap
              key: PORT
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: hello-server-configmap
data:
  PORT: "8081"

こんな感じで、spec.env.nameでPORTを指定しているよね。 どこから読み込むのかというのも指定している。 hello-server-configmapから読み込むって指定しているわけです。 その下にhello-server-configmapというConfigMapがあるわけですね。 そこでdata.PORTを指定しているわけですね。

7章 HC(ヘルス✓)等

Rediness probeとLiveness Probeというのがありますね。 Rediness probeでは、指定したHCエンドポイントへアクセスできるまで、そのポッドにはリクエストを投げない設定です。これも便利ですねー。

liveness probeというのは、HCが失敗したときにそのPODを再起動する方法です。 どちらのHCもHC間隔を決められます。便利です。

7章 リソースについて

k8sではポッドに割り当てるリソース量を決められます。 これはマジでslurmとかと同じですね。 最大値と最小値を決められます。それに応じて、どのNodeにスケジュールするかが決まります。便利ですねー。

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: hello-server
  name: hello-server
spec:
  containers:
  - name: hello-server
    image: blux2/hello-server:1.6
    resources:
      requests:
        memory: "64Mi"
        cpu: "10m"
      limits:
        memory: "64Mi"
        cpu: "10m"

9章 腕試し!!

はい、原因調査完了して、bug fixしました

NAME                    TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
hello-server-external   NodePort   10.97.198.195   <none>        8081:30599/TCP   7h48m
ray@master:~/k8s_manifests/bbf-kubernetes/chapter-08 $ curl localhost:8081
curl: (7) Failed to connect to localhost port 8081 after 0 ms: Couldn't connect to server
ray@master:~/k8s_manifests/bbf-kubernetes/chapter-08 $ curl localhost:8081
curl: (7) Failed to connect to localhost port 8081 after 0 ms: Couldn't connect to server
ray@master:~/k8s_manifests/bbf-kubernetes/chapter-08 $ curl localhost:30599
Hello, world! Let's build, break and fix!ray@master:~/k8s_manifests/bbf-kubernetes/chapter-08 $ curl 10.97.198.1910.97.198.195:80801
curl: (3) URL using bad/illegal format or missing URL
ray@master:~/k8s_manifests/bbf-kubernetes/chapter-08 $ curl 10.97.198.195:8081
Hello, world! Let's build, break and fix!

まあ原因は以下でしたね。

ConfigMapのポート番号が間違っていたこと。 maxUnavailableが10%で、3つの10%て、0.3で変えられないじゃんってこと 後、liveness probeのinitialDelaySecondsが短すぎて、PODが立ち上がる前にリスタートしていたこと。そして、health checkのパスが間違っていたこと diffはこんなかんじ 

ray@master:~/k8s_manifests/bbf-kubernetes/chapter-08 $ diff hello-server-fix.yaml hello-server.yaml 
32c32
<         image: blux2/hello-server:2.0.1
---
>         image: blux2/hello-server:1.8
48c48
<             path: /healthz
---
>             path: /health
54c54
<             path: /healthz
---
>             path: /health
56c56
<           initialDelaySeconds: 30
---
>           initialDelaySeconds: 10
85c85
<   maxUnavailable: 50%
---
>   maxUnavailable: 10%

答え合わせと行きましょうか。 maxUnavailable:10%はあまり関係なかったみたいだけど、ほかは問題なさそうだ。OK!

9章 アーキテクチャを理解しよう

ここまででIngressに関して何もやってないのがすごく心配になるが、、どうなんだ!! まあいいか、、、Ingressやりたかったんだけどなー。 まあ、ここではとりあえずk8sのアーキテクチャの説明をしています。

コントロールプレーンと、ワーカーノードがあって、 コントロールプレーンには、etcdとkubeapi_server、kube_scheduler、 ワーカノードには、kubeletと、kube-proxy、そして、コンテナラインタイム(containerd)がいる。 kubectlで、kubeapi_serverと通信して、etcdにマニフェストの内容を登録する。 それを見てkube-schedulerがPODをスケジューリングする。 で、コントロールプレーンが死んでもサービスは生きる、っていうのが特徴かな。これはすごい。 まあ、ほとんどのc-planeとD-planeでそういう設計になっているのだけれどもね。

10章

argo cdの説明がされています。 gitopsというci/cdの手法にカテゴライズされるみたいです。 別にgitを使っているからというわけではないらしい。すごい。

そして、マニフェストファイルの管理にkustomizeを使え!!ってはなし。 仕事でも使ったけど、環境ごとに地味に設定が違うときとかにすごい便利って話だよね。 baseディレクトリに共通事項を書いておく。 そして、overlays/prod, overlays/dev, overlays/stg に各環境ごとの設定を書いておく。 これは実際のやつを見て勉強しながらの方が早いですね。

11章 o11yについて

prometheusとgrafanaについて書かれています。 まあ、どちらもやってみるのがいいと思います。楽しいだろうしね。

12章 今後の勉強指針

やってみたいのはやっぱりk8s the hard way. 後は、青山さんのk8s大全を読め!という話と。 cncfからk8s関連の資格が2つあるよ。アプリケーション運用者向けと、システム運用者向けと。という感じ。 後は、Linuxに詳しくなれ!というお話と、実装よめという話以上!

結局Ingressに関して書かれていなかったという話。たのみますよー

なので、k8s大全の6章。Service APIs カテゴリを読みたいと思いますよー。 うーん、難しい。IngressのバックエンドにServiceをおいて、それらはNodePortにしないとだめらしい。 で、Ingressの設定でバックエンドにNodePort Serviceを設定する感じ。 chatgptに書いてもらったのがこちら。

apiVersion: v1
kind: Namespace
metadata:
  name: my-api-namespace
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-api-service
  labels:
    app: my-api-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-api-service
  template:
    metadata:
      labels:
        app: my-api-service
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 1
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  values:
                  - hello-server
                  operator: In
              topologyKey: kubernetes.io/hostname
      containers:
      - name: hello-server
        image: blux2/hello-server:2.0.1
        env:
        - name: PORT
          valueFrom:
            configMapKeyRef:
              name: hello-server-configmap
              key: PORT
        resources:
          requests:
            memory: "256Mi"
            cpu: "10m"
          limits:
            memory: "256Mi"
            cpu: "10m"
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8081
          initialDelaySeconds: 5
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8081
          initialDelaySeconds: 30
          periodSeconds: 5
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: hello-server-configmap
data:
  PORT: "8081"
  HOST: "localhost"
---
apiVersion: v1
kind: Service
metadata:
  name: my-api-service
  namespace: my-api-namespace
spec:
  selector:
    app: my-api-service
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8081
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-api-ingress
  namespace: my-api-namespace
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  ingressClassName: nginx
  rules:
  - host: my-api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-api-service
            port:
              number: 80

serviceはデフォルトでClusterIpになると思うので、これだと青山さんの言っていることと違うけどどうなんだろう。 いったんデプロイしてみるか。

apiVersion: v1
kind: Namespace
metadata:
  name: my-api-namespace
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-api-service
  namespace: my-api-namespace
  labels:
    app: my-api-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-api-service
  template:
    metadata:
      labels:
        app: my-api-service
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 1
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  values:
                  - hello-server
                  operator: In
              topologyKey: kubernetes.io/hostname
      containers:
      - name: hello-server
        image: blux2/hello-server:2.0.1
        env:
        - name: PORT
          valueFrom:
            configMapKeyRef:
              name: hello-server-configmap
              key: PORT
        resources:
          requests:
            memory: "256Mi"
            cpu: "10m"
          limits:
            memory: "256Mi"
            cpu: "10m"
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8081
          initialDelaySeconds: 5
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8081
          initialDelaySeconds: 30
          periodSeconds: 5
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: hello-server-configmap
  namespace: my-api-namespace
data:
  PORT: "8081"
  HOST: "localhost"
---
apiVersion: v1
kind: Service
metadata:
  name: my-api-svc
  namespace: my-api-namespace
spec:
  selector:
    app: my-api-service
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8081
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-api-ingress
  namespace: my-api-namespace
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  ingressClassName: nginx
  rules:
  - host: my-api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-api-svc
            port:
              number: 80

やはりこれではアクセスできず。NodePortにする必要があるのではないか?と思っている。

ray@master:~/k8s_manifests/bbf-kubernetes $ kubectl get svc -n my-api-namespace
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
my-api-service   ClusterIP   10.96.59.54     <none>        80/TCP         27m
my-api-svc       NodePort    10.110.149.87   <none>        80:31337/TCP   21m
ray@master:~/k8s_manifests/bbf-kubernetes $ curl localhost:31337
Hello, world! Let's build, break and fix!ray@master:~/k8s_manifests/bbf-kubernetes $ ^C
ray@master:~/k8s_manifests/bbf-kubernetes $ curl 10.110.149.87
Hello, world! Let's build, break and fix!ray@master:~/k8s_manifests/bbf-kubernetes $

ちゃんと読んでなかったけど、ingress controllerというものを導入する必要があるみたいです。 はい、以下のmanifestをapplyした。 https://github.com/kubernetes/ingress-nginx/blob/main/deploy/static/provider/cloud/deploy.yaml で、デプロイしたんだけど、それでもマジでアクセスで金くて困っているのだが。

ray@master:~/k8s_manifests/install_ingress_ctl $ kubectl get svc -n ingress-nginx
NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.108.157.49   <pending>     80:31129/TCP,443:31892/TCP   23m
ingress-nginx-controller-admission   ClusterIP      10.96.58.201    <none>        443/TCP                      23m
ray@master:~/k8s_manifests/install_ingress_ctl $ curl 10.108.157.49 -H "Host:my-api.example.com"
Hello, world! Let's build, break and fix!

ingressを通してのアクセスはできた。しかしどうやら31129なのだよね。

ray@master:~/k8s_manifests/install_ingress_ctl $ curl localhost:31129 -H "Host:my-api.example.com"
Hello, world! Let's build, break and fix!

できている。

ほかのノードからはどうか。

ray@delta:~|⇒  curl master.ingenboy.local:31129 -H "Host:my-api-example.com"

だめだね。タイムアウト。なんで??? どうやら、ingressのsvcでloadbalancerのやつがpendingになってるでしょ?これはクラウド環境用のファイルを適応してしまったかららしい。

今回はラズパイで構築しているので、こっちでないとだめ https://github.com/kubernetes/ingress-nginx/blob/main/deploy/static/provider/baremetal/deploy.yaml ベアメタル用。

はい、外部からアクセスしてみますねー そしたらこれで行けましたね。

ray@delta:~|⇒  curl master.ingenboy.local:30492 -H "Host:my-api.example.com"
Hello, world! Let's build, break and fix!%   

まあ確かにこれで内部で複数のサービスを保持できることにはなるがな。しかし面倒くさすぎるな。 ingressのノードポートのポートを指定することはできないのかな?

できなくはない。ingressControllerのインストールスクリプトで以下のようにポート番号を指定すればいい。

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - appProtocol: http
    name: http
    port: 80
    protocol: TCP
    targetPort: http
    nodePort: 30080
  - appProtocol: https
    name: https
    port: 443
    protocol: TCP
    targetPort: https
    nodePort: 30443
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  type: NodePort

しかし、nodeportで指定できるのは30000以上らしいので80とかは無理。 ただ、nodeportで指定可能なぽーとレンジを変更することもできる。実はね。 それがこんな感じです。

ray@master:~/k8s_manifests/install_ingress_ctl $ sudo cat /etc/kubernetes/manifests/kube-apiserver.yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 192.168.3.8:6443
  creationTimestamp: null
  labels:
    component: kube-apiserver
    tier: control-plane
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=192.168.3.8
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --client-ca-file=/etc/kubernetes/pki/ca.crt
    - --enable-admission-plugins=NodeRestriction
    - --enable-bootstrap-token-auth=true
    - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
    - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
    - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
    - --etcd-servers=https://127.0.0.1:2379
    - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
    - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
    - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
    - --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
    - --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
    - --requestheader-allowed-names=front-proxy-client
    - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
    - --requestheader-extra-headers-prefix=X-Remote-Extra-
    - --requestheader-group-headers=X-Remote-Group
    - --requestheader-username-headers=X-Remote-User
    - --secure-port=6443
    - --service-account-issuer=https://kubernetes.default.svc.cluster.local
    - --service-account-key-file=/etc/kubernetes/pki/sa.pub
    - --service-account-signing-key-file=/etc/kubernetes/pki/sa.key
    - --service-cluster-ip-range=10.96.0.0/12
    - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
    - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
    image: registry.k8s.io/kube-apiserver:v1.28.15
    imagePullPolicy: IfNotPresent
    livenessProbe:
      failureThreshold: 8
      httpGet:
        host: 192.168.3.8
        path: /livez
        port: 6443
        scheme: HTTPS
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 15
    name: kube-apiserver
    readinessProbe:
      failureThreshold: 3
      httpGet:
        host: 192.168.3.8
        path: /readyz
        port: 6443
        scheme: HTTPS
      periodSeconds: 1
      timeoutSeconds: 15
    resources:
      requests:
        cpu: 250m
    startupProbe:
      failureThreshold: 24
      httpGet:
        host: 192.168.3.8
        path: /livez
        port: 6443
        scheme: HTTPS
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 15
    volumeMounts:
    - mountPath: /etc/ssl/certs
      name: ca-certs
      readOnly: true
    - mountPath: /etc/ca-certificates
      name: etc-ca-certificates
      readOnly: true
    - mountPath: /etc/kubernetes/pki
      name: k8s-certs
      readOnly: true
    - mountPath: /usr/local/share/ca-certificates
      name: usr-local-share-ca-certificates
      readOnly: true
    - mountPath: /usr/share/ca-certificates
      name: usr-share-ca-certificates
      readOnly: true
  hostNetwork: true
  priority: 2000001000
  priorityClassName: system-node-critical
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  volumes:
  - hostPath:
      path: /etc/ssl/certs
      type: DirectoryOrCreate
    name: ca-certs
  - hostPath:
      path: /etc/ca-certificates
      type: DirectoryOrCreate
    name: etc-ca-certificates
  - hostPath:
      path: /etc/kubernetes/pki
      type: DirectoryOrCreate
    name: k8s-certs
  - hostPath:
      path: /usr/local/share/ca-certificates
      type: DirectoryOrCreate
    name: usr-local-share-ca-certificates
  - hostPath:
      path: /usr/share/ca-certificates
      type: DirectoryOrCreate
    name: usr-share-ca-certificates
status: {}

ここに以下を追加する。

- command:
  - kube-apiserver
  - --service-node-port-range=20000-40000

まあもう面倒くさいからいいや。 k8sなかなか理解できました!!ありがとう!!そんなに難しくないわ!楽しかった!!これはただの道具ですわ。 使いこなせばいいサービスが作れるのではないでしょうか??お疲れ様です。