Kubernetes学習メモ(4)

Kubernetes APIの内部動作やRBACによる詳細な権限管理、ユーザー認証の仕組みをハンズオン形式で体系的に解説しています。 あわせて、ストレージの永続化、スケジューリングの制御、Network PolicyやHelmチャートの活用など、実運用に不可欠な応用トピックを網羅した学習メモです。
目次
これまでのもの
教材
- Kubernetes Certified (KCNA) + Hands On Labs + Practice Exams
- 内容はKubernetes Deep Diveの章
事前準備
環境構築については(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を編集する。imageとserviceAccountについて編集する。
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の中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー
