【技術書紹介】つくって、壊して、直して学ぶ Kubernetes入門

## 書籍情報
書籍名：つくって、壊して、直して学ぶ Kubernetes入門
著者名：高橋 あおい

https://www.shoeisha.co.jp/book/detail/9784798183961


## 本書を選んだ背景
実務経験はないものの、DockerやKubernetesに興味があったことと、以前参加した勉強会で本書が紹介されており興味を持ったことがきっかけです。

## 本書について
Kubernetesの実践的入門書として位置付けられています。ローカルクラスタを構築し手を動かしながら学べます。
ハンズオンは主に、リソースをつくる→意図的に壊す→設定ファイルなどを調査し直す、という流れで構成されており、トラブルシュートを体験できるようになっています。
また、イラストや漫画が豊富で、とても読みやすいのが特徴です。
Kuberneteについて、わからない→なんとなくわかる→もうちょっと知りたい　に達することが本書の目標とのこと。

## 本書の内容
以下、個人的なメモを残します。
### Part1 つくってみようKubernetes
- Kubernetesは大きく分けてControl PlaneとWorker Nodeの２つの役割に分かれる。Control Planeとによって決まった内容に応じてWorker Nodeで実際にコンテナを起動する
- アプリケーションサーバであるkube-apiserverにkubectlでアクセスして、インフラ環境を構築する
- Worker NodeがControl Planeにアクセスすることで、Control Planeが壊れても即座にWorker Node上に起動するコンテナが破壊されるわけではない
- 本書でのローカルクラスタ構築にはkindを使う。マルチノードクラスタを作れるのが特徴。Dockerの環境の中にDockerを立ち上げてクラスタを作っているため、Dockerが必須
クラスタ構築は次のコマンドで行う（kindest/node:v1.29.0を利用する場合）
    ```
    kind create cluster --image=kindest/node:v1.29.0
    ```
    バージョン確認は次のコマンド
    ```
    kubectl cluster-info --context kind-kind
    ```
- kubectlのconfig情報はhomeディレクトリの.kube/configに記載されている
- Namespaceは、kubernetesにおいて単一クラスタ内のリソース群を分離するメカニズムを提供する。本書ではdefaultのNamespaceを使用する

- マニフェストの適用
    ```
    kubectl apply --filename <ファイル名> --namespace default
    ```

### Part2 アプリケーションを壊して学ぶKubernetes
#### トラブルシューティングガイドとkubectlコマンドの使い方
- Podの確認
    ```
    kubectl get pod <Pod名>
    ```
    - --output wideオプションでIPアドレスやNode情報が取得できる
    - --output yamlオプションでYAMLファイオル形式でリソース情報を取得する
    - --output jsonpath='{.spec.containers[].images}'オプションでJSON形式で出力
    - --v=<ログレベル>オプションで出力結果のログレベルを変更する

- リソースの詳細を取得する。エラーの原因調査にも役立つ
    ```
    kubectl describe pod <Pod名>
    ```
- コンテナのログを取得
    ```
    kubectl logs <Pod名>
    ```
    - --containerオプションでコンテナを指定する
    - --selector <labelのキー名>=<labelの値>でラベルを指定して参照する
- デバッグ用コンテナの起動
    ```
    kubuctl debug --stdin --tty <デバッグ対象Pod名> --image=<デバッグ用コンテナのimage> --target=<デバッグ対象のコンテナ名>
    ```
- コンテナを即座に実行する
    ```
    kubectl run <Pod名> --image=<image名>
    ```
- クラスタ外からのアクセス
    ```
    kubectl port-forward <Pod名> <転送先ポート番号>:<転送元ポート番号>
    ```
- マニフェストの編集
    ```
    kubectl edit <リソース名>
    ```
- リソースの削除
    ```
    kubectl delete <リソース名>
    ```
#### Kubernetesリソースをつくって壊そう
- ReplicaSetはPodの複製。作成について、マニフェストファイルのkindをReplicasetに指定、specのreplicasに作成するPod数を指定する
次のコマンドで確認
    ```
    kubectl get replicaset
    ```
- DeploymentはReplicaSetの上位概念で、ローリングアップデートが可能となる。作成について、マニフェストファイルのkindをDeploymentに指定する
次のコマンドで確認
    ```
    kubectl get deployment
    ```
- Deploymentで作ったリソース（Pod）にアクセスするためにServiceというリソースを利用する。マニフェストファイルのkindをServiceに指定する
また、マニフェストファイル内でTypeを指定できる（デフォルトでClusterIP、他にNodePortなど）
次のコマンドで確認
    ```
    kubectl get service <Service名>
    ```
- ConfigMapはPodの外部から値を設定したいときに利用するリソース。マニフェストファイルのkindをConfigMapに指定する
次のコマンドで確認
    ```
    kubectl get configmap <ConfigMap名>
    ```
- Jobは１回限りのタスクを実行するのに利用するリソース。マニフェストファイルのkindをJobに指定する
また、kindをCronJobにすることで定期的にJobを実行できる
次のコマンドで確認
    ```
    kubectl get job
    kubectl get cronjob
    ```
#### 安全なステートレス・アプリケーションをつくるには
- Readines probeはコンテナがReadyになる時間やエンドポイントを制御
ここでは/healthzというヘルスチェック用エンドポイントの8080番ポートに対して、5秒に一度ヘルスチェック用リクエストを送る設定なっている。initialDelaySecondsは最初のProbeが実施されるまで５秒まつ
    ```.YAML
    〜略〜
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
    ```
- Liveness probeはReadines probeと似ているが、Probeが失敗した時の挙動が変わる。Readines probehはServiceから接続を外すのに対し、Liveness probeはPodを再起動する
    ```.YAML
    〜略〜
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
    ```
- Startup probeは初回起動時のみに利用するProbeで、起動が遅いアプリケーションでの利用が想定される
このマニフェストでは最大30秒×10回＝300秒コンテナの起動を待つ
    ```.YAML
    〜略〜
        startupProbe:
          httpGet:
            path: /healthz
            port: liveness-port
          failureThreshold: 30
          periodSeconds: 10

    ```
- コンテナのリソース使用量の要求、制限は以下
    ```.YAML
    〜略〜
        resources:
          requests:
            memory: "64Mi"
            cpu: "10m"
          limits:
            memory: "64Mi"
            cpu: "10m"
    ```
- Nodeを指定する。次の例では、SSD使っているNodeのみをスケジュールする
    ```.YAML
    〜略〜
      nodeSelector:
        disktype: ssd
    ```
- Node Affinityは「可能ならスケジューリングする」
    ```.YAML
    〜略〜
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:　#対応するNodeが見つからない場合、適当なNodeにスケジュールする
          - weight: 1
            preference:
              matchExpressions:
              - key: disktype
                operator: In
                values:
                - ssd
    〜略〜
    ```
- Node Affinity/Node Anti-affinityは「Pod間のAffinity」で、すでにNodeにスケジュールされているPodのラベルに基づいてスケジューリングする
    ```.YAML
     〜略〜
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - nginx
              topologyKey: kubernetes.io/hostname
     〜略〜
    ```
- Pod Topology Spread ConstraintsはPodを分散するための設定
    ```.YAML
     〜略〜
    spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: nginx
     〜略〜
    ```

- Taint/Tolerationは「あるNodeが特定のPod(とくに指定のないPodはスケジュールを拒否したい)」といった指定
NodeにTaintは次の方法で付けられる
   ```
   kubectl taint nodes <対象Node名>　<label名>=<labelの値>:<Taintの効果>
   ```
   このTaintに対応するtolerationは次のように指定する
   ```.YAML
     〜略〜
       tolerations:
      　　　　key: "disktype"
        value: "ssd"
        operator: "Equal"
        effect: "NoSchedule"
     〜略〜
   ```
- Horizontal Pod Autoscaler（HPA）を利用し水平スケール。事前にmetrics-serverをインストールしておく
次のようにマニフェストに書くことで水平スケールを実現する
    ```.YAML
     〜略〜
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: hello-server-hpa
    spec:
      minReplicas: 1
      maxReplicas: 10
      metrics:
      - resource:
          name: cpu
          target:
            averageUtilization: 50
            type: Utilization
        type: Resource
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: hpa-handson
     〜略〜
    ```
- Vertical Pod Autoscaler（VPA）を利用し垂直スケール。ただしVPAはHPAと同じリソースに対して同時に利用できないため、HPAのみ利用しているケースが多い

#### トラブルシューティングにおける原因の切り分け
原因を切り分けるにあたって、なるべくアプリケーションに近いところから切り分けると良い。まずPodについてdescribeとlogsで調査し、次にデバッグ用コンテナを立ち上げる。例えば次のように行う。
1. Pod内からアプリケーションの接続確認を行う
2. クラスタ内かつ別Podから接続確認を行う
3. クラスタ内かつ別PodからService経由で接続確認を行う

#### よくあるエラーの例
- 設定ファイル内のリポジトリ名のタグをタイプミスする。kubectl describe で調べてみる

### Part3 壊れても動くKubernetes
- Control Planeについて
    -　kube-apiserverはRESTで通信可能なAPIサーバ。etcdは分散型キーバリューストアで、データベースの一種
    -　kube-schedulerはPodをNodeにスケジュールする役割を担っている
    -　kube-controller-managerはKubernetesを最低限動かすための複数のコントローラーを動かしている
- Worker Nodeについて
    -　kubeletは各コンテナ内で動いている。kubeletが起動しているNodeにPodがスケジュールされると、コンテナランタイムに指示してコンテナを起動する
    -　kube-proxyはKubernetes Serviceリソースなどに応じてネットワーク設定を行う
    -　コンテナランタイムはコンテナを実行するソフトウェア
- デプロイ手法
    -　CIOpsはPush型のデプロイ手法。シンプルでわかりやすく、構築しやすい
    -　GitOpsはPull型のデプロイ手法。セキュリティリスクに強く、CIとCDを明確に分けることができる
- マニフェスト管理
    -　Helmはパッケージマネージャとなっており、チャートというテンプレートを使用してデプロイや管理を簡略化する
    -　Kustomizeは基本設定を使い回して、環境ごとに微調整できる。基本設定をbaseディレクトリ、各環境固有の設定をoverlaysディレクトリ内のファイルに書く
- オブザーバビリティとモニタリング
    -　 Prometheusを使用しメトリクスを収集する。まずはPort-forwardを行う
    ```
    kubectl port-forward service/kube-prometheus-stack-prometheus --namespace monitoring 9090:9090
    ```
   ブラウザでhttp\://localhost:9090にアクセスすると、メトリクスが確認できる
   ![スクリーンショット 2024-09-22 01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/607438/8710f7a8-aa33-989a-dc92-280cb1655aee.png)
    -　 続いてGrafanaにアクセスする。Port-forwardを行う
    ```
    kubectl port-forward service/kube-prometheus-stack-grafana --namespace monitoring 8080:80
    ```
   ブラウザでhttp\://localhost:8080にアクセスすると、ログイン後にメトリクスが確認できる
   ![スクリーンショット 2024-09-22 02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/607438/1b6664a4-73c1-d7cd-5d43-a70f1a3c0863.png)

## 本書の感想
- 解説がわかりやすく、図も豊富なことから、特に躓くことなくハンズオンを行うことができました
- イラストや漫画が多く、視覚型の学習スタイルの人に合う書籍かと思います