こしあん
2026-01-29

Kubernetes学習メモ(4)


1{icon} {views}

Kubernetes APIの内部動作やRBACによる詳細な権限管理、ユーザー認証の仕組みをハンズオン形式で体系的に解説しています。 あわせて、ストレージの永続化、スケジューリングの制御、Network PolicyやHelmチャートの活用など、実運用に不可欠な応用トピックを網羅した学習メモです。

これまでのもの

教材

事前準備

環境構築については(3)を参照

Kubernetes API

コマンドの裏で呼ばれているKubernetes APIの表示

kubectl get nodes --v=6

Kubernetes APIとの対応を確認

kubectl api-resources | more

Kubernetes APIのエンドポイントへのアクセス(Unauthorizedになる)

curl --insecure https://127.0.0.1:6443/api/v1/nodes?limit=500

Kube Proxyを起動し、ポートを確認し、HTTPでアクセスすればいける。

# Starting to serve on 127.0.0.1:8001と表示される
kubectl proxy &

# HTTP + Kube Proxyでアクセス
curl --insecure http://127.0.0.1:8001/api/v1/nodes?limit=500

OpenAPIで仕様を確認

# バージョンによる違いはある
curl localhost:8001/openapi/v2

# v2はJSON直でAPIの定義を書いているのに対し、v3はAPIの定義URLへのリンク
curl localhost:8001/openapi/v3

kubectl get nodes相当を低レベルのKubernetes APIを直でコールする。他言語での対応や他エンドポイントでの対応では、SwaggerをPostmanに読ませて、コードを生成すると便利らしい。

curl http://localhost:8001/api/v1/nodes --header 'Accept: application/json'

kubectl run nginxのKubernetes APIの確認

kubectl run nginx --image=nginx --v=9

存在しないPodを消そうとした場合

kubectl delete pod/nginxX --now --v=9

# Kubernetes APIのエンドポイントが存在しないためうまくいかない
# DELETE https://127.0.0.1:6443/api/v1/namespaces/default/pods/nginxX 404 Not Found in 3 milliseconds

Kubernetes RBAC

以下のコマンドでKubernetesクラスタの証明書データが見れる

# 証明書の生データが除外された表示
kubectl config view

# 証明書の生データも見る
kubectl config view --raw

実際には次のようなデータになっている

root@control-plane:~# kubectl config view --raw
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS...0K
    server: https://127.0.0.1:6443
  name: default
contexts:
- context:
    cluster: default
    user: default
  name: default
current-context: default
kind: Config
preferences: {}
users:
- name: default
  user:
    client-certificate-data: LS...o=
    client-key-data: LS...o=

Base64でエンコードされているので、デコードすると証明書が読める

echo LS...0K | base64 -d

# こんな表示になる
# -----BEGIN CERTIFICATE-----
# MII...w==
#-----END CERTIFICATE-----

証明書のデータを見るには、opensslをパイプさせる

echo LS...0K | base64 -d | openssl x509 -text --noout
# 表示例
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 0 (0x0)
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN = k3s-server-ca@...

誰に、どのクラスタ権限を与えているかのリストアップ

kubectl get clusterrolebindings -o wide

ClusterRoleに割り当てられている権限の確認

kubectl describe ClusterRole/cluster-admin

全権限を持ったClusterRoleの作成

kubectl create clusterrole cluster-superhero --verb='*' --resource='*'

ClusterRoleとグループの関連付け

kubectl create clusterrolebinding cluster-superhero --clusterrole=cluster-superhero --group=cluster-superheros

作成したロールの確認(cluster-adminとcluster-superheroが表示される)

kubectl get clusterrolebindings -o wide | egrep 'Name|^cluster-'

今の認証されているユーザーが、どんな操作(verb)でも、どんなリソース(resource)でも、できる?(yes)

kubectl auth can-i '*' '*'

cluster-superherosに属するユーザーなら全てできる(全部yesが返ってくる)

kubectl auth can-i '*' '*' --as-group="cluster-superheros" --as="batman"
kubectl auth can-i '*' '*' --as-group="cluster-superheros" --as="superman"
kubectl auth can-i '*' '*' --as-group="cluster-superheros" --as="wonder-woman"

クライアント証明書を作るための秘密鍵”と“CSR(署名要求)を作成。CSR(Certificate Signing Request)は証明書を発行するための申請書。CNはCommon Nameでユーザー名、OはOrganizationでグループ名。

openssl genrsa -out batman.key 4096
openssl req -new -key batman.key -out batman.csr -subj "/CN=batman/O=cluster-superheros" -sha256

CSR作成のためのYAMLを作成

CSR_DATA=$(base64 batman.csr | tr -d '\n')
CSR_USER=batman

# YAMLの作成
cat <<EOF > batman-csr-request.yaml
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: ${CSR_USER}
spec:
  request: ${CSR_DATA}
  signerName: kubernetes.io/kube-apiserver-client
  usages:
  - client auth
EOF

CSRの作成

# apply→YAML
kubectl apply -f batman-csr-request.yaml
kubectl get certificatesigningrequest

# 以下でも可能
kubectl get csr

CSRはあくまでリクエストなのでこれだと「Pending」になる。承認が必要

kubectl certificate approve batman

# approvedになっている
kubectl get csr batman

# 詳細をJSONで表示
kubectl get csr batman -o json

証明書の中をデコード

# 証明書の中のBase64値を取得
kubectl get csr batman -o jsonpath='{.status.certificate}'

# Base64でデコード(-- BEGIN CERTIFICATE --で始まるテキストが表示)
kubectl get csr batman -o jsonpath='{.status.certificate}' | base64 -d

証明書の中身を確認

kubectl get csr batman -o jsonpath='{.status.certificate}' | base64 -d > batman.crt

# Subject: O = cluster-superheros, CN = batmanになってるのがポイント
openssl x509 -in batman.crt -text --noout

カレントディレクトリにKube configをコピーしてみる。

cp /root/.kube/config batman-clustersuperheros.config

batman 用の新しい kubeconfig の土台として掃除。操作対象をそのファイルに限定して編集しています(~/.kube/config 本体は触らない)。

KUBECONFIG=batman-clustersuperheros.config kubectl config unset users.default
KUBECONFIG=batman-clustersuperheros.config kubectl config delete-context default
KUBECONFIG=batman-clustersuperheros.config kubectl config unset current-context

cat batman-clustersuperheros.config
# batmanとしてログインするための身分証(証明書/鍵)を登録
KUBECONFIG=batman-clustersuperheros.config kubectl config set-credentials batman --client-certificate=batman.crt --client-key=batman.key --embed-certs=true
# defaultという接続プロファイルを作って、defaultクラスタにbatmanで行くように設定
KUBECONFIG=batman-clustersuperheros.config kubectl config set-context default --cluster=default --user=batman
# このファイルで使う接続プロファイルを default に固定
KUBECONFIG=batman-clustersuperheros.config kubectl config use-context default

# users, contexts, current-contextが増えている
cat batman-clustersuperheros.config

batmanとしてコマンドを実行できる

KUBECONFIG=batman-clustersuperheros.config kubectl get nodes
KUBECONFIG=batman-clustersuperheros.config kubectl run nginx --image=nginx
KUBECONFIG=batman-clustersuperheros.config kubectl delete pod/nginx --now

あまりにコマンドが長すぎるので、Kube configを手軽に作るためのOSSを入れる

apt update && apt install -y git jq openssl
git clone https://github.com/spurin/kubeconfig-creator.git
cd kubeconfig-creator

ユーザー=superman、グループ=cluster-superherosとして作成

./kubeconfig_creator.sh -u superman -g cluster-superheros

テストコマンド

KUBECONFIG=./superman-clustersuperheros.config kubectl get nodes

同様にユーザー=wonderwomanでも作成

./kubeconfig_creator.sh -u wonderwoman -g cluster-superheros

今のユーザーって何でもできてしまうので、cluster-watcherというロールを作り、cluster-watchersというグループにバインドさせる。

kubectl create clusterrole cluster-watcher --verb=list,get,watch --resource='*'
kubectl create clusterrolebinding cluster-watcher --clusterrole=cluster-watcher --group=cluster-watchers

限定されたロールのグループで、全権限に何でもできるか

# noと表示される
kubectl auth can-i '*' '*' --as-group="cluster-watchers" --as="uatu"

# yes
kubectl auth can-i 'list' '*' --as-group="cluster-watchers" --as="uatu"

実際にユーザーを作って試す

./kubeconfig_creator.sh -u uatu -g cluster-watchers
# 実行可能
KUBECONFIG=./uatu-clusterwatchers.config kubectl get nodes

# 実行不可能
KUBECONFIG=./uatu-clusterwatchers.config kubectl run nginx --image=nginx
# Error from server (Forbidden): pods is forbidden: User "uatu" cannot create resource "pods" in API group "" in the namespace "default"

別の権限を持ったロールを作成

kubectl create clusterrole cluster-pod-manager --verb=list,get,create,delete --resource='pods'
kubectl create clusterrolebinding cluster-pod-manager --clusterrole=cluster-pod-manager --user=deadpool

権限の確認

# no : リソースがすべてなので
kubectl auth can-i 'list' '*' --as="deadpool"

# yes : Podに絞っているため
kubectl auth can-i 'list' 'pods' --as="deadpool"

ロールのバインド一覧

kubectl get clusterrolebindings -o wide

ユーザーdeadpoolの作成

./kubeconfig_creator.sh -u deadpool

コマンドの実行

# OK
KUBECONFIG=./deadpool.config kubectl get pods

# Forbidden
KUBECONFIG=./deadpool.config kubectl get secrets

Namespaceとの関係

# グリフィンドールのネームスペース、ロールの作成、関連付け
kubectl create namespace gryffindor
kubectl -n gryffindor create role gryffindor-admin --verb='*' --resource='*'
kubectl -n gryffindor create rolebinding gryffindor-admin --role=gryffindor-admin --group=gryffindor-admins

# namespaceないとnoになる(上がnoで、下がyes)
kubectl auth can-i '*' '*' --as-group="gryffindor-admins" --as="harry"
kubectl -n gryffindor auth can-i '*' '*' --as-group="gryffindor-admins" --as="harry"

実際にユーザーharryを作ってみる

./kubeconfig_creator.sh -u harry -g gryffindor-admins -n gryffindor

作られたconfigの確認

root@control-plane:~/kubeconfig-creator# cat harry-gryffindoradmins.config
apiVersion: v1
kind: Config
clusters:
- cluster:
    certificate-authority-data: ...
    server: https://127.0.0.1:6443
  name: default
users:
- name: harry-gryffindoradmins
  user:
    client-certificate-data: ...
    client-key-data: ...
contexts:
- context:
    cluster: default
    user: harry-gryffindoradmins
    namespace: gryffindor
  name: harry-gryffindoradmins-default
current-context: harry-gryffindoradmins-default

現在のネームスペースがgryffindorなので、get podsは可能。ネームスペースをdefaultにすると不可能(2,3行目)。

get nodesが失敗したのは、付与した権限が「gryffindor ネームスペース内だけ」に限定されていて、nodes は「クラスタスコープ」のリソースなため。

root@control-plane:~/kubeconfig-creator# KUBECONFIG=./harry-gryffindoradmins.config kubectl get nodes
Error from server (Forbidden): nodes is forbidden: User "harry" cannot list resource "nodes" in API group "" at the cluster scope
root@control-plane:~/kubeconfig-creator# KUBECONFIG=./harry-gryffindoradmins.config kubectl -n default get pods
Error from server (Forbidden): pods is forbidden: User "harry" cannot list resource "pods" in API group "" in the namespace "default"
root@control-plane:~/kubeconfig-creator# KUBECONFIG=./harry-gryffindoradmins.config kubectl get pods
No resources found in gryffindor namespace.

クリーンアップ

ls -l
cd ..
ls -l

rm -rf batman* kubeconfig-creator

Kubernetesのリソースも削除

kubectl delete namespace/gryffindor

# 確認用
kubectl get clusterrolebinding -o wide

kubectl delete clusterrole/cluster-superhero clusterrole/cluster-watcher clusterrole/cluster-pod-manager
kubectl delete clusterrolebinding/cluster-superhero clusterrolebinding/cluster-watcher clusterrolebinding/cluster-pod-manager

サービスアカウントについて

シンプルなcurlのPodの作成

kubectl run curl --image=curlimages/curl:8.4.0 -- sleep infinity

Podの詳細の確認(Service Account: defaultと表示されているのの確認)

kubectl describe pod/curl | more

サービスアカウントに割り当てられたトークンの確認

TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
echo $TOKEN

KubernetesのAPIエンドポイントにクエリを実行する。ここでは、PodがAPIと通信できるようにデフォルトで用意されているDNS名https://kubernetes.default.svcを使用する。現状defaultのサービスアカウントにはアクセス権限がないので制限される。

curl --header "Authorization: Bearer $TOKEN" --insecure https://kubernetes.default.svc/api/v1/pods

Podを終了し、クリーンアップ

exit
kubectl delete pod/curl --now

サービスアカウントpod-serviceaccountの作成

kubectl create serviceaccount pod-serviceaccount

ClsterRoleを作成し、サービスアカウントに関連付ける

kubectl create clusterrole cluster-pod-manager --verb=list,get,create,delete --resource='pods'
kubectl create clusterrolebinding cluster-pod-manager --clusterrole=cluster-pod-manager --serviceaccount=default:pod-serviceaccount

新しく作成したサービスアカウントでPodを起動

kubectl run curl --image=curlimages/curl:8.4.0 --overrides='{ "spec": { "serviceAccount": "pod-serviceaccount" }  }' -- sleep infinity

Podの詳細を確認(Service Accountがpod-serviceaccountになっていることの確認)

kubectl describe pod/curl | more

Podの詳細の確認

kubectl exec -it curl -- sh

同様にPodの中に入ってKubernetes APIをクエリ。今度はサービスアカウントに権限を割り当てているため起動できる。

TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
echo $TOKEN

curl --header "Authorization: Bearer $TOKEN" --insecure https://kubernetes.default.svc/api/v1/pods
exit
kubectl delete pod/curl serviceaccount/pod-serviceaccount clusterrole/cluster-pod-manager clusterrolebinding/cluster-pod-manager --now

Kubernetes Pod and Scheduler

nginxのYAMLを作らせる。Schedulerをカスタマイズするため。

kubectl run nginx --image=nginx -o yaml --dry-run=client | tee nginx_scheduler.yaml
# YAMLでのSchedulerの書き方について
kubectl explain pod.spec | more

nginx_scheduler.yamlを編集する。spc.schedulerNameを追加する。

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx
  name: nginx
spec:
  schedulerName: my-scheduler
  containers:
  - image: nginx
    name: nginx
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

Podを起動する。スケジューラーが起動していないためPendingのまま。

kubectl apply -f nginx_scheduler.yaml
kubectl get pods -o wide

スケジューラーを起動する。必要なら、実行するmy-scheduler.shの中身を見る。

# 必要なら
# apt install git jq

git clone https://github.com/spurin/simple-kubernetes-scheduler-example.git

cd simple-kubernetes-scheduler-example

./my-scheduler.sh

「Successfully bound the pod nginx to node control-plan」と表示されたあと、Ctrl+CしてPodが起動したことを確認する。

kubectl get pods -o wide

いったんPodを削除する

kubectl delete pod/nginx --now
cd ..

Schedulerではなく、ノード名を変更する

# 必要なら
kubectl explain pod.spec | more

YAMLを以下のように変更する

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx
  name: nginx
spec:
  nodeName: worker-2
  containers:
  - image: nginx
    name: nginx
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

Podを作成する。worker-2のNodeに作られている。

kubectl apply -f nginx_scheduler.yaml
kubectl get pods -o wide

クリーンアップ

kubectl delete pod/nginx --now
rm -rf simple-kubernetes-scheduler-example nginx_scheduler.yaml

Kubernetes Storage

Meemory based Storage(一時ストレージ)を作成するためのYAMLを生成

kubectl run --image=ubuntu ubuntu -o yaml --dry-run=client --command sleep infinity | tee ubuntu_emptydir.yaml

volumeMounts, volumesを付け足す。メモリ上に一時ディレクトリを保存する

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: ubuntu
  name: ubuntu
spec:
  containers:
  - command:
    - sleep
    - infinity
    image: ubuntu
    name: ubuntu
    resources: {}
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  dnsPolicy: ClusterFirst
  restartPolicy: Always
  volumes:
  - name: cache-volume
    emptyDir: {
      medium: Memory,
    }
status: {}

Podを起動し、シェルに入る

kubectl apply -f ubuntu_emptydir.yaml

kubectl exec -it ubuntu -- bash

以下シェル内での操作

root@ubuntu:/# cd /cache
root@ubuntu:/cache# df -h .
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           7.6G     0  7.6G   0% /cache

# メモリベースなので非常に早い
root@ubuntu:/cache# dd if=/dev/zero of=output oflag=sync bs=1024k count=1000
1048576000 bytes (1.0 GB, 1000 MiB) copied, 0.250929 s, 4.2 GB/s

exit

クリーンアップ

kubectl delete pod/ubuntu --now

PersistentVolume(永続ストレージ)。本来、かなり準備が長くなるがここのデモで使っているk33s側で用意されている。

kubectl get storageclass

storageclassのオプションの表示。重要になるのが、reclaimPolicyで「Delete, Recycle, Retain」がある。これらを使い分けることが大事。

kubectl explain storageclass | more

PersistentVolume(PV)の作り方は、マニュアルのアプローチと動的なアプローチがある。マニュアルなアプローチから。まずは以下のYAMLを作る。

vim manual_pv.yaml

ノード上のディレクトリ /var/lib/rancher/k3s/storage/manual-pv001 を、
容量 10Gi の永続ストレージとして Kubernetes に登録している、という意味。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: manual-pv001
  labels:
    type: local
spec:
  storageClassName: local-path
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/var/lib/rancher/k3s/storage/manual-pv001"
    type: DirectoryOrCreate

PVを作成する。Reclaim PolicyがRetainになっていることに注目

kubectl apply -f manual_pv.yaml
kubectl get pv


# NAME           CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
# manual-pv001   10Gi       RWO            Retain           Available           local-path     <unset>                          21s

新しいYAMLを作成する

vim manual_pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: manual-claim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 10Gi
  storageClassName: local-path
  volumeName: manual-pv001

PVCを作成して確認

kubectl apply -f manual_pvc.yaml

# 以下は同じ
kubectl get persistentvolumeclaim
kubectl get pvc

次にDynamicなPVCを作成する。YAMLをコピーし、dynamic_pvc.yamlを以下のように変更

cp manual_pvc.yaml dynamic_pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dynamic-claim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 10Gi
  storageClassName: local-path

作成して確認(Pending)

kubectl apply -f dynamic_pvc.yaml

root@control-plane:~# kubectl get pvc
NAME            STATUS    VOLUME         CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
dynamic-claim   Pending                                            local-path     <unset>                 18s
manual-claim    Bound     manual-pv001   10Gi       RWO            local-path     <unset>                 7m21s

Pendingになっている理由の確認(Waiting for first consumer…)

kubectl describe pvc/dynamic-claim

ManualとDynamicの挙動の違い。まずはYAMLを作る

kubectl run --image=ubuntu ubuntu -o yaml --dry-run=client --command sleep infinity | tee ubuntu_with_volumes.yaml

vim ubuntu_with_volumes.yaml

以下のようなYAMLにする

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: ubuntu
  name: ubuntu
spec:
  nodeSelector:
    kubernetes.io/hostname: worker-1 # k3s特有
  containers:
  - command:
    - sleep
    - infinity
    image: ubuntu
    name: ubuntu
    resources: {}
    volumeMounts:
    - mountPath: /manual
      name: manual-volume
    - mountPath: /dynamic
      name: dynamic-volume
  dnsPolicy: ClusterFirst
  restartPolicy: Always
  volumes:
    - name: manual-volume
      persistentVolumeClaim:
        claimName: manual-claim
    - name: dynamic-volume
      persistentVolumeClaim:
        claimName: dynamic-claim
status: {}

Ubuntu の Pod を 1個起動して、2つの PVC をそれぞれ別パスにマウントする。さらに nodeSelector で worker-1 という特定ノードに固定しています。

重要:この指定をすると、PVC/PV 側が “そのノードにあるストレージ” でないと詰みます。
(特に hostPath / local 系 PV はノード依存なので、ここは整合させる必要あり)

claimName は既存の PVC 名です。つまりこの Pod を apply する前に:

  • PersistentVolumeClaim/manual-claim
  • PersistentVolumeClaim/dynamic-claim

が 同じ namespace に存在していて、できれば Bound になっている必要があります。

Podを作成する。PVのRecalim Policyについて、Manual(手動)はRetainであるが、Dynamic(自動)はDeleteである。PVCは各ボリュームに紐づけられている。

kubectl get pod

kubectl get pv

kubectl get pvc

kubectl exec -it ubuntu -- bash

コンテナの中に入って各ボリュームにファイルを作成する

root@ubuntu:/# cd /manual
root@ubuntu:/manual# df -h .
Filesystem      Size  Used Avail Use% Mounted on
/dev/root        14G   14G  424M  97% /manual
root@ubuntu:/manual# cd /dynamic
root@ubuntu:/dynamic# df -h .
Filesystem      Size  Used Avail Use% Mounted on
/dev/root        14G   14G  424M  97% /dynamic
root@ubuntu:/dynamic# cd 
root@ubuntu:~# echo hello > /manual/test.txt
root@ubuntu:~# echo hello > /dynamic/test.txt
root@ubuntu:~# exit

Podを再作成し、コンテナの中に入る

kubectl delete pod/ubuntu --now && kubectl apply -f ubuntu_with_volumes.yaml

kubectl exec -it ubuntu -- bash

DynamicとManual両方ファイルは残っている

root@ubuntu:/# cat /manual/test.txt
hello
root@ubuntu:/# cat /dynamic/test.txt 
hello

クリーンアップ兼PVCを削除してみる。Manualは残っていて、DynamicはPVCを消すとPVも消える。これがRetain Policyの違い。

kubectl delete pod/ubuntu pvc/dynamic-claim pvc/manual-claim --now

kubectl get pv
# NAME           CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                  STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
# manual-pv001   10Gi       RWO            Retain           Released   default/manual-claim   local-path     <unset>                          71m

kubectl delete pv/manual-pv001 --now
# manual-pv001も消える

rm -rf dynamic_pvc.yaml manual_pv.yaml manual_pvc.yaml ubuntu_emptydir.yaml ubuntu_with_volumes.yaml

Kubernetes Statefulset

YAMLを作成して編集

kubectl create deployment nginx --image=nginx --replicas=3 --dry-run=client -o yaml | tee statefulset.yaml

vim statefulset.yaml

以下のようにYAMLを編集。serviceNameは明示的に与える必要がある。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  serviceName: nginx
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx

Stateful Setをデプロイ

kubectl apply -f statefulset.yaml

kubectl get statefulset

kubectl get pods -o wide

メリット

  • ネットワークの識別名がユニーク値になる
    • ヘッドレスで動作し、ClusterIPのIPがいらない

ClusterIPを作成してみる

kubectl create service clusterip --clusterip=None nginx

kubectl get service

kubectl get endpoints

kubectl get endpoints/nginx -o yaml

{hostname}.{servicename}.{namespace}.svc.cluster.localという固定名でアクセスできる(例:nginx-0.nginx.default.svc.cluster.local)。curlimagesを使って試してみる

kubectl run --rm -i --tty curl --image=curlimages/curl --restart=Never -- sh

# 以下シェル内(Welcome to nginxと表示される)
curl nginx-1.nginx.default.svc.cluster.local
exit

UpdateStrategyに注目(デフォルトはrollingUpdateでpartitionが0)。これはnginx-0, 1, 2の順でローリングアップデートしていく。

kubectl get statefulset/nginx -o yaml | more

partitionの値を2に変更する

kubectl edit statefulset/nginx
  updateStrategy:
    rollingUpdate:
      partition: 2 # ここを0から変更
    type: RollingUpdate

ローリングアップデートしてみる。特定Podのみアップデートできるので、テストが便利。

kubectl set image statefulset/nginx nginx=nginx:alpine && kubectl rollout status statefulset/nginx

# アップデートされていない
kubectl describe pod/nginx-1 | more
# アップデートされている
kubectl describe pod/nginx-2 | more

StatefulSetにPersistentVolumeを追加してみる

vim statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  serviceName: nginx
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        volumeMounts:
        - name: nginx
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: nginx
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "local-path"
      resources:
        requests:
          storage: 1Gi
kubectl delete -f statefulset.yaml && kubectl apply -f statefulset.yaml

エラーが出た場合は、EC2のEBSの空きがない場合があるので、ホスト側でdf -hを実行してみること。

kubectl apply -f statefulset.yaml

kubectl get pods -o wide

kubectl get pvc
# nginx-nginx-0, 1, 2がBoundになっていれば正しい

kubectl get pv

PVによる永続化の効果を確認する

kubectl exec -it nginx-0 -- bash

root@nginx-0:/# cd /data
root@nginx-0:/data# df -h .
root@nginx-0:/data# touch this_is_nginx-0
root@nginx-0:/data# exit

すぐにPodが再起動する。

kubectl delete pod/nginx-0 --now; watch --differences kubectl get pods -o wide

kubectl exec -it nginx-0 -- bash

PersistentVolumeの効果で、Podを削除してもデータが残っている

root@nginx-0:/# ls /data 
this_is_nginx-0

root@nginx-0:/# exit

StatefulSetを削除してもPVCやPVは残る

kubectl delete -f statefulset.yaml

# 以下は残る
kubectl get pvc
kubectl get pv

再作成して再接続すると、やはり残っている

kubectl apply -f statefulset.yaml
kubectl exec -it nginx-0 -- bash
root@nginx-0:/# ls /data # 残っている
root@nginx-0:/# exit

クリーンアップ

kubectl delete -f statefulset.yaml --now

for i in 0 1 2
do
kubectl delete pvc/nginx-nginx-$i --now
done

kubectl delete service/nginx

rm statefulset.yaml

Kubernetes Network Policy

nginxのPodとテスト用のcurlのPodの作成

kubectl run nginx --image=nginx

kubectl expose pod/nginx --port=80

kubectl run --rm -i --tty curl --image=curlimages/curl --restart=Never -- sh

curlのPodの内で

curl nginx.default.svc.cluster.local

Network PolicyのYAMLを作成。podSelector.matchLabels.runでPodを選択

vim networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-nginx-access
  namespace: default
spec:
  podSelector:
    matchLabels:
      run: nginx
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              run: curl

ネットワークポリシーを作成し、curlのPodを作成

kubectl apply -f networkpolicy.yaml

kubectl run --rm -i --tty curl --image=curlimages/curl --restart=Never -- sh

# Pod内でcurl(これはつながる)
~ $ curl nginx.default.svc.cluster.local

しかし、Pod名をcurl2にすると繋がらない

kubectl run --rm -i --tty curl2 --image=curlimages/curl --restart=Never -- sh

# Pod内でcurl(つながらない)
~ $ curl nginx.default.svc.cluster.local
# curl: (7) Failed to connect to nginx.default.svc.cluster.local port 80 after 1 ms: Could not connect to server

クリーンアップ

kubectl delete -f networkpolicy.yaml

rm networkpolicy.yaml

kubectl delete service/nginx

kubectl delete pod/nginx --now

Kubernetes Pod Disruption Budgets

Podを5個作成

kubectl create deployment nginx --image=nginx --replicas=6

kubectl get pods -o wide

結果は以下のようだとする(control-plane, worker-1, worker-2に2個ずつ)

NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE            NOMINATED NODE   READINESS GATES
nginx-676b6c5bbc-7t69x   1/1     Running   0          4s    10.42.3.10   worker-2        <none>           <none>
nginx-676b6c5bbc-dd6x8   1/1     Running   0          4s    10.42.0.11   control-plane   <none>           <none>
nginx-676b6c5bbc-lf8r8   1/1     Running   0          5s    10.42.1.13   worker-1        <none>           <none>
nginx-676b6c5bbc-mjcfx   1/1     Running   0          4s    10.42.0.10   control-plane   <none>           <none>
nginx-676b6c5bbc-n55lk   1/1     Running   0          4s    10.42.3.9    worker-2        <none>           <none>
nginx-676b6c5bbc-qktf7   1/1     Running   0          4s    10.42.1.14   worker-1        <none>           <none>

control-planeをこれ以上増えないようにして、2個Podを消す(worker-1, worker-2が3個ずつになる)

kubectl cordon control-plane && kubectl delete pod/nginx-676b6c5bbc-dd6x8 pod/nginx-676b6c5bbc-mjcfx --now

kubectl get pods -o wide

worker-1をこれ以上増えないようにして、Podを3個消す(worker-2×6になる)

kubectl cordon worker-1 && kubectl delete pod/nginx-676b6c5bbc-92tjr pod/nginx-676b6c5bbc-lf8r8 pod/nginx-676b6c5bbc-qktf7 --now

kubectl get pods -o wide

worker-2をドレインする。内部的にはcordonした上で、別のNodeに追い出すという意味。しかし、他のノートもcordonされているので、エラーになる

kubectl drain worker-2 --delete-emptydir-data=true --ignore-daemonsets=true

以下のようになる

root@control-plane:~# kubectl get deployment
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
nginx   0/6     6            0           12m
root@control-plane:~# kubectl get pods -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP       NODE     NOMINATED NODE   READINESS GATES
nginx-676b6c5bbc-5drtf   0/1     Pending   0          30s   <none>   <none>   <none>           <none>
nginx-676b6c5bbc-6td4q   0/1     Pending   0          30s   <none>   <none>   <none>           <none>
nginx-676b6c5bbc-6xbnz   0/1     Pending   0          30s   <none>   <none>   <none>           <none>
nginx-676b6c5bbc-gsrd6   0/1     Pending   0          30s   <none>   <none>   <none>           <none>
nginx-676b6c5bbc-jwxdb   0/1     Pending   0          30s   <none>   <none>   <none>           <none>
nginx-676b6c5bbc-v7jnk   0/1     Pending   0          30s   <none>   <none>   <none>           <none>

すべてのノードのcordonを解除する

kubectl uncordon control-plane worker-1 worker-2

# すべてcontrol-planeにデプロイされているはず
kubectl get pods -o wide

PDB(Pod Disruption Budget)を作成する。ラベル類はdescribeで確認する

# Selectorの確認
kubectl describe deployment/nginx | more

# PDBの作成
kubectl create pdb nginx --selector=app=nginx --min-available=2

# 全ノードをcordonして、すべてドレインする
kubectl cordon control-plane worker-1 worker-2
kubectl drain control-plane worker-1 worker-2 --delete-emptydir-data=true --ignore-daemonsets=true

2個は消せない。PDBで最小維持数が効いているため。drain状態のまま、別ウィンドウでworker-1, 2をuncordonする。こうすることでwprker-1, 2に×3でデプロイができる

kubectl uncordon worker-1 worker-2

クリーンアップ

kubectl uncordon control-plane
kubectl delete deployment/nginx --now

Kubernetes Security

YAMLの生成してapplyしてPodにアクセス。rootユーザーとして実行されてしまう。

kubectl run ubuntu --image=spurin/rootshell:latest -o yaml --dry-run=client -- sleep infinity | tee ubuntu_secure.yaml

kubectl apply -f ubuntu_secure.yaml

kubectl exec -it ubuntu -- bash

root@ubuntu:/# exit

YAMLを編集し、ルートユーザーとして実行されないようにする。spec.securityContextを追加する。

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: ubuntu
  name: ubuntu
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 1000
  containers:
  - args:
    - sleep
    - infinity
    image: spurin/rootshell:latest
    name: ubuntu
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

Podを入れ替えて実行すると、nonpriv@ubuntuというユーザー名になる。

kubectl replace --force -f ubuntu_secure.yaml

kubectl exec -it ubuntu -- bash

現状は、/rootshellコマンドで実行権限を切り替えられる。apt updateもできるてしまう。

nonpriv@ubuntu:/$ id
uid=1000(nonpriv) gid=1000(nonpriv) groups=1000(nonpriv)
nonpriv@ubuntu:/$ /rootshell
root@ubuntu:/# id
uid=0(root) gid=1000(nonpriv) groups=1000(nonpriv)

allowPrivilegeEscalation: falseを追加することで、rootへの昇格を防止できる。

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: ubuntu
  name: ubuntu
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 1000
  containers:
  - args:
    - sleep
    - infinity
    image: spurin/rootshell:latest
    name: ubuntu
    resources: {}
    securityContext:
      allowPrivilegeEscalation: false
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

/rootshellを実行してもrootに昇格されない

kubectl replace --force -f ubuntu_secure.yaml

kubectl exec -it ubuntu -- bash

nonpriv@ubuntu:/$ /rootshell
nonpriv@ubuntu:/$ 

クリーンアップ

kubectl delete -f ubuntu_secure.yaml --now

rm -rf ubuntu_secure.yaml

Helmチャート

Helmのインストール(ローカルに直置きは本番環境ではやってはいけない)。最新のv4を使用。

helm version

HelmでAppを作る

helm create flappy-app

cd flappy-app

apt install tree
tree

Helmチャートを編集する。descriptionを編集する

vim Chart.yaml
description: Flappy Dock Game Helm chart

values.yamlを編集する。imageserviceAccountについて編集する。

flappy-app# 
root@control-plane:~/flappy-app# 
root@control-plane:~/flappy-app# vim values.yaml
root@control-plane:~/flappy-app# cat values.yaml
# Default values for flappy-app.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
replicaCount: 1

# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/
image:
  repository: spurin/flappy-dock
  # This sets the pull policy for images.
  pullPolicy: IfNotPresent
  # Overrides the image tag whose default is the chart appVersion.
  tag: latest

# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
imagePullSecrets: []
# This is to override the chart name.
nameOverride: ""
fullnameOverride: ""

# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
serviceAccount:
  # Specifies whether a service account should be created.
  create: false
  # Automatically mount a ServiceAccount's API credentials?
  automount: false
  # Annotations to add to the service account.
  annotations: {}
  # The name of the service account to use.
  # If not set and create is true, a name is generated using the fullname template.
  name: ""

# This is for setting Kubernetes Annotations to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
podAnnotations: {}
# This is for setting Kube

Helmをインストールする

# 直接インストール
helm install flappy-app .

# パッケージ化してインストール
helm package .
helm install flappy-app flappy-app-0.1.0.tgz

HelmがKubernetesを仲介しているのを確認する。表示されているコマンドをコピーすることでポートフォワーディングができる

export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=flappy-app,app.kubernetes.io/instance=flappy-app" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT

Kubernetsのコマンドでも確認

kubectl get deployment

kubectl get pods

kubectl get svc

Helmのリスト

helm list

helm uninstall flappy-app

クリーンアップ

rm -rf flappy-app


Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

北海道の駅巡りコーナー


Add a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です