はじめに
アプリケーションサービスからk8s環境にDeployment、Podリソースを提供する方法を調査し実装する。アプリケーションサービスもk8s上に提供されPodとして稼働する前提とします。
実装に利用する環境について
実装に利用したk8s環境は以下の通りです。
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master Ready control-plane 42d v1.31.6
worker1 NotReady <none> 14h v1.31.6
worker2 Ready <none> 14h v1.31.6
$ kubectl version
Client Version: v1.31.6
Kustomize Version: v5.4.2
Server Version: v1.31.5
$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"31", GitVersion:"v1.31.6", GitCommit:"6b3560758b37680cb713dfc71da03c04cadd657c", GitTreeState:"clean", BuildDate:"2025-02-12T21:31:09Z", GoVersion:"go1.22.12", Compiler:"gc", Platform:"linux/amd64"}
Kubernetes Python Client 用Podの作成
アプリケーションサービスを作成するのは面倒なので、今回はPythonを実行可能なPodを作成し、手動でpythonコマンドを実行することで実装します。Python用のPodは、以下の定義で作ります。
apiVersion: v1
kind: Pod
metadata:
name: python-pod
spec:
containers:
- name: python-container
image: python:3.11-slim
command: ["python3"]
tty: true
$ kubectl create -f pod-python.yaml
pod/python-pod created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
python-pod 1/1 Running 0 6s
$ kubectl exec -it python-pod -- bash
root@python-pod:/# python
Python 3.11.11 (main, Feb 25 2025, 02:38:55) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print("Hello Python!")
Hello Python!
>>>
root@python-pod:/# exit
Kubernetes Python Client モジュールのインストール
手動であらかじめ必要なパッケージをダウンロードしておき、python-dpo:/tmp配下にコピーしておきます。
$ kubectl exec -it python-pod -- bash
root@python-pod:/# python3 -m venv myenv
root@python-pod:/# source myenv/bin/activate
(myenv) root@python-pod:/# cd /tmp
(myenv) root@python-pod:/tmp# pip install certifi-2025.1.31-py3-none-any.whl six-1.17.0-py2.py3-none-any.whl charset_normalizer-2.0.12-py3-none-any.whl urllib3-1.26.20-py2.py3-none-any.whl pyasn1-0.5.1-py2.py3-none-any.whl rsa-4.9-py3-none-any.whl idna-3.10-py3-none-any.whl durationpy-0.9-py3-none-any.whl websocket_client-1.3.1-py3-none-any.whl oauthlib-3.2.2-py3-none-any.whl requests-2.27.1-py2.py3-none-any.whl requests_oauthlib-2.0.0-py2.py3-none-any.whl cachetools-4.2.4-py3-none-any.whl pyasn1_modules-0.3.0-py2.py3-none-any.whl python_dateutil-2.9.0.post0-py2.py3-none-any.whl google_auth-2.22.0-py2.py3-none-any.whl
〜省略〜
Installing collected packages: durationpy, websocket-client, urllib3, six, pyasn1, oauthlib, idna, charset-normalizer, certifi, cachetools, rsa, requests, python-dateutil, pyasn1-modules, requests-oauthlib, google-auth
Successfully installed cachetools-4.2.4 certifi-2025.1.31 charset-normalizer-2.0.12 durationpy-0.9 google-auth-2.22.0 idna-3.10 oauthlib-3.2.2 pyasn1-0.5.1 pyasn1-modules-0.3.0 python-dateutil-2.9.0.post0 requests-2.27.1 requests-oauthlib-2.0.0 rsa-4.9 six-1.17.0 urllib3-1.26.20 websocket-client-1.3.1
(myenv) root@python-pod:/tmp# gzip -dc PyYAML-6.0.1.tar.gz | tar xf -
(myenv) root@python-pod:/tmp# cd PyYAML-6.0.1
(myenv) root@python-pod:/tmp/PyYAML-6.0.1# python3 setup.py install
〜省略〜
gcc -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/myenv/include -I/usr/local/include/python3.11 -c yaml/_yaml.c -o build/temp.linux-x86_64-cpython-311/yaml/_yaml.o
Error compiling module, falling back to pure Python
〜省略〜
Installed /myenv/lib/python3.11/site-packages/PyYAML-6.0.1-py3.11-linux-x86_64.egg
Processing dependencies for PyYAML==6.0.1
Finished processing dependencies for PyYAML==6.0.1
(myenv) root@python-pod:/tmp/PyYAML-6.0.1# cd ..
(myenv) root@python-pod:/tmp# pip install kubernetes-32.0.1-py2.py3-none-any.whl
〜省略〜
Installing collected packages: kubernetes
Successfully installed kubernetes-32.0.1
(myenv) root@python-pod:/tmp#
途中でgccコマンドエラーが出力されていましたが、最終的にSuccessfullyのため一旦無視することにします。
呼び出し用 Deployment マニフェストの準備
Clientが呼び出すマニフェストは、以下内容をもとにPython Clientコードへ変換していきます。変換後のPythonコードはapply_in_cluster.pyになります。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
from kubernetes import client, config
config.load_incluster_config()
deployment = client.V1Deployment(
api_version="apps/v1",
kind="Deployment",
metadata=client.V1ObjectMeta(name="nginx-deployment"),
spec=client.V1DeploymentSpec(
replicas=3,
template=client.V1PodTemplateSpec(
spec=client.V1PodSpec(
containers=[
client.V1Container(
name="nginx",
image="nginx:latest",
ports=[client.V1ContainerPort(container_port=80)],
)
]
),
),
),
)
api = client.AppsV1Api()
namespace = "default"
try:
existing_deployment = api.read_namespaced_deployment("nginx-deployment", namespace)
print("Deployment already exists, Updating...")
api.patch_namespaced_deployment(name="nginx-deployment", namespace=namespace, body=deployment)
print("Deployment updated successfully.")
except client.exceptions.ApiException as e:
if e.status == 404:
print("Deployment does not exist. Creating...")
api.create_namespaced_deployment(namespace=namespace, body=deployment)
print("Deployment created successfully.")
else:
print(f"Error: {e}")
Clientスクリプトの実行
先程のpython-podにapply_in_cluster.pyをコピーします。
$ kubectl cp apply_in_cluster.py python-pod:/
python-podに接続し先程のPythonファイルを実行してみます。
$ kubectl exec -it python-pod -- bash
root@python-pod:/# source myenv/bin/activate
(myenv) root@python-pod:/# python apply_in_cluster.py
Error: (403)
Reason: Forbidden
HTTP response headers: HTTPHeaderDict({'Audit-Id': 'd598c4ea-0d8c-46cc-a90f-10fdf4de33d5', 'Cache-Control': 'no-cache, private', 'Content-Type': 'application/json', 'X-Content-Type-Options': 'nosniff', 'X-Kubernetes-Pf-Flowschema-Uid': '2094e53b-be81-4453-a7ce-d136df21c242', 'X-Kubernetes-Pf-Prioritylevel-Uid': 'e4dae82a-0dd7-4ac1-b0ef-4ad9e5a9b3a6', 'Date': 'Mon, 10 Mar 2025 09:53:29 GMT', 'Content-Length': '373'})
HTTP response body: {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"deployments.apps \"nginx-deployment\" is forbidden: User \"system:serviceaccount:default:default\" cannot get resource \"deployments\" in API group \"apps\" in the namespace \"default\"","reason":"Forbidden","details":{"name":"nginx-deployment","group":"apps","kind":"deployments"},"code":403}
(myenv) root@python-pod:/#
エラーが出力されました。どうやら403なので権限不足が原因の様です。エラーメッセージを整形し内容を確認してみる。HeaderとBodyパートに分かれているようです。ヘッダーにはエラー原因に繋がる内容は特になさそうです。Bodyにはいくつか情報がありそうです。
User "system:serviceaccount:default:default":
リクエストを実行したユーザーは default ネームスペースの default サービスアカウント みたいです。KubernetesではPodがAPIを操作する際、デフォルトのサービスアカウントとして (system:serviceaccount::default) が使用される様です。
"cannot get resource "deployments" in API group "apps":
deployments リソース(apps APIグループ)の get 操作(情報取得)を実行しようとしたが、権限がないようです。deployments get以外にも、Pods リソースのget操作やcreate操作等々、必要な感じがします。
Error: (403)
Reason: Forbidden
HTTP response headers: HTTPHeaderDict(
{
'Audit-Id': 'd598c4ea-0d8c-46cc-a90f-10fdf4de33d5',
'Cache-Control': 'no-cache,
private',
'Content-Type': 'application/json',
'X-Content-Type-Options': 'nosniff',
'X-Kubernetes-Pf-Flowschema-Uid': '2094e53b-be81-4453-a7ce-d136df21c242',
'X-Kubernetes-Pf-Prioritylevel-Uid': 'e4dae82a-0dd7-4ac1-b0ef-4ad9e5a9b3a6',
'Date': 'Mon, 10 Mar 2025 09:53:29 GMT',
'Content-Length': '373'
}
)
HTTP response body: {
"kind":"Status",
"apiVersion":"v1",
"metadata":{},
"status":"Failure",
"message":"deployments.apps "nginx-deployment" is forbidden: User "system:serviceaccount:default:default" cannot get resource "deployments" in API group "apps" in the namespace "default"",
"reason":"Forbidden",
"details":{
"name":"nginx-deployment",
"group":"apps",
"kind":"deployments"
},
"code":403
}
とりあえずAPI group "apps"でdeplyomentsリソースに対しget操作可能なServiceAccountやRole/RoleBindingを作成しpython-podに適用してみます。
ServiceAccount リソース
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-service-account
Role リソース
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: my-role
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get"]
RoleBinding リソース
作成したRoleリソースをServiceAccountリソースにバインドするためのRoleBindingリソースを作成します。
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: my-rolebinding
namespace: default
subjects:
- kind: ServiceAccount
name: my-service-account
namespace: default
roleRef:
kind: Role
name: my-role
apiGroup: rbac.authorization.k8s.io
apiVersion: v1
kind: Pod
metadata:
name: python-pod
spec:
serviceAccountName: my-service-account # 追加
containers:
- name: python-container
image: python:3.11-slim
command: ["python3"]
tty: true
用意したファイルを用いて適用していきます。どうやらデプロイ済みのコンテナに適用するとエラーになるようです。具体的にはServiceAccount: "default"をServiceAccount: "my-service-account"に変更することが許可されていないみたいです。
ServiceAccount リソースの適用
$ kubectl apply -f sa.yaml -f role.yaml -f rolebinding.yaml -f python.yaml
serviceaccount/my-service-account created
role.rbac.authorization.k8s.io/my-role created
rolebinding.rbac.authorization.k8s.io/my-rolebinding created
Warning: resource pods/python-pod is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
The Pod "python-pod" is invalid: spec: Forbidden: pod updates may not change fields other than `spec.containers[*].image`,`spec.initContainers[*].image`,`spec.activeDeadlineSeconds`,`spec.tolerations` (only additions to existing tolerations),`spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative)
core.PodSpec{
... // 7 identical fields
DNSPolicy: "ClusterFirst",
NodeSelector: nil,
- ServiceAccountName: "default",
+ ServiceAccountName: "my-service-account",
AutomountServiceAccountToken: nil,
NodeName: "worker2",
... // 21 identical fields
}
# Role リソースへの create操作権限追加
一度python-podを再作成し適用したところ今度は、create操作権限が足りていないみたいです。最終的にRoleリソースを以下のように修正しました。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: my-role
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "create"]
初回作成は正常終了、再度実行した場合はpatch操作権限がなくエラーになるみたいです。
(myenv) root@python-pod:~# python apply_in_cluster.py
Deployment does not exist. Creating...
Deployment created successfully.
(myenv) root@python-pod:~# python apply_in_cluster.py
Deployment already exists, Updating...
Error: (403)
Reason: Forbidden
HTTP response headers: HTTPHeaderDict({'Audit-Id': '2f827d2b-d7e4-4d2e-9a75-d0015c7345b4', 'Cache-Control': 'no-cache, private', 'Content-Type': 'application/json', 'X-Content-Type-Options': 'nosniff', 'X-Kubernetes-Pf-Flowschema-Uid': '2094e53b-be81-4453-a7ce-d136df21c242', 'X-Kubernetes-Pf-Prioritylevel-Uid': 'e4dae82a-0dd7-4ac1-b0ef-4ad9e5a9b3a6', 'Date': 'Mon, 10 Mar 2025 11:29:33 GMT', 'Content-Length': '386'})
HTTP response body: {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"deployments.apps \"nginx-deployment\" is forbidden: User \"system:serviceaccount:default:my-service-account\" cannot patch resource \"deployments\" in API group \"apps\" in the namespace \"default\"","reason":"Forbidden","details":{"name":"nginx-deployment","group":"apps","kind":"deployments"},"code":403}
(myenv) root@python-pod:/tmp#
Client実行により作成された nginx Pod
Deploymentリソースnginx-deploymentがk8s上に反映されていることを確認できました。
$ kubectl get deployments,pods
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx-deployment 3/3 3 3 3m40s
NAME READY STATUS RESTARTS AGE
pod/nginx-deployment-54b9c68f67-d7hlb 1/1 Running 0 3m40s
pod/nginx-deployment-54b9c68f67-dj2sj 1/1 Running 0 3m40s
pod/nginx-deployment-54b9c68f67-h2nh7 1/1 Running 0 3m40s
pod/nginx-deployment-54b9c68f67-ls9xv 1/1 Terminating 0 20h
pod/python-pod 1/1 Running 0 19m
動作確認
手を抜いてServiceリソースを作成していないため、対象ノードから直接Podに割り当てられたIPをリクエストしての動作確認になります。
$ kubectl describe pods nginx-deployment-54b9c68f67-d7hlb | grep -e ^Node: -e ^IP:
Node: worker2/192.168.11.51
IP: 10.244.223.166
$ ssh worker2
$ curl http://10.244.223.166
〜省略〜
<h1>Welcome to nginx!</h1>
〜省略〜
おわりに
当初目的の通り、PodからDeploymentを作成することができました。