高橋  花子

高橋 花子

1652947500

Pythonを使用してすべての退屈なKubernetes操作を自動化する

Kubernetesは近年デファクトスタンダードになり、DevOpsエンジニアと開発者の両方が日常的にKubernetesを使用しています。ただし、実行するタスクの多くは同じで、退屈で、自動化が簡単です。

多くの場合、一連のコマンドを使用して簡単なシェルスクリプトを作成するのは簡単kubectlですが、より複雑な自動化タスクの場合、bashだけでは不十分であり、Pythonなどの適切な言語の能力が必要です。

そのため、この記事では、Kubernetes Pythonクライアントライブラリを活用して、煩わしいKubernetesタスクを自動化する方法を見ていきます。

遊び場

Kubernetesクライアントでのプレイを開始する前に、まず、安全にテストできるプレイグラウンドクラスターを作成する必要があります。ここからインストールできるKinD(DockerのKubernetes)を使用します。

次のクラスター構成を使用します。

# kind.yaml
# https://kind.sigs.k8s.io/docs/user/configuration/
apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
name: api-playground
nodes:
- role: control-plane
- role: worker
- role: worker
- role: worker

上記の構成からクラスターを作成するには、次のコマンドを実行できます。

kind create cluster --image kindest/node:v1.23.5 --config=kind.yaml

kubectl cluster-info --context kind-api-playground
# Kubernetes control plane is running at https://127.0.0.1:36599
# CoreDNS is running at https://127.0.0.1:36599/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

kubectl get nodes
# NAME                           STATUS     ROLES                  AGE   VERSION
# api-playground-control-plane   Ready      control-plane,master   58s   v1.23.5
# api-playground-worker          Ready      <none>                 27s   v1.23.5
# api-playground-worker2         NotReady   <none>                 27s   v1.23.5
# api-playground-worker3         NotReady   <none>                 27s   v1.23.5

クラスタが稼働している状態で、クライアントライブラリもインストールする必要があります(オプションで、仮想環境内に)。

python3 -m venv venv
source venv/bin/activate
pip install kubernetes

認証

Kubernetesクラスタ内でアクションを実行するには、最初に認証する必要があります。

長期間有効なトークンを使用するため、認証フローを繰り返し実行する必要はありません。ServiceAccount有効期間の長いトークンは、 :を作成することで作成できます。

kubectl create sa playground
kubectl describe sa playground

Name:                playground
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   playground-token-v8bq7
Tokens:              playground-token-v8bq7
Events:              <none>

export KIND_TOKEN=$(kubectl get secret playground-token-v8bq7 -o json | jq -r .data.token | base64 --decode)

サービスアカウントを使用すると、1人のユーザーに関連付けられないという利点もあります。これは、自動化の目的で常に推奨されます。

上記の出力からのトークンは、リクエストで使用できます。

curl -k -X GET -H "Authorization: Bearer $KIND_TOKEN" https://127.0.0.1:36599/apis

現在認証されていますが、多くのことを行う権限はありません。したがって、次に、を作成しRoleてバインドし、ServiceAccount リソースに対してアクションを実行できるようにする必要があります。コードは次のとおりです。


kubectl create clusterrole manage-pods \
    --verb=get --verb=list --verb=watch --verb=create --verb=update --verb=patch --verb=delete \
    --resource=pods
    
kubectl -n default create rolebinding sa-manage-pods \
    --clusterrole=manage-pods \
    --serviceaccount=default:playground

上記は、サービスアカウントに、default名前空間に限定されたポッドでのアクションを実行するためのアクセス許可を与えます。

常に役割を非常に狭く具体的に保つ必要がありますが、KinDで遊んでみると、以下に示すように、クラスター全体の管理者の役割を適用するのが理にかなっています。


kubectl create clusterrolebinding sa-cluster-admin \
  --clusterrole=cluster-admin \
  --serviceaccount=default:playground

生のリクエスト

何が行われているのか、またクライアントが内部で何をしているのかをよりよく理解するために、kubectlを使用した生のHTTPリクエストから始めますcurl

内部で行われている要求を見つける最も簡単な方法は、を使用して目的のkubectlコマンドを実行すること-v 10です。curlこれにより、以下に示す完全なコマンドが出力されます。

kubectl get pods -v 10
# <snip>
curl -k -v -XGET  -H "Accept: application/json;as=Table;v=v1;g=meta.k8s.io,application/json..." \
    'https://127.0.0.1:36599/api/v1/namespaces/default/pods?limit=500'
# <snip>

の出力はloglevel 10非常に冗長になりますが、どこかに上記のcurlコマンドがあります。

Bearer上記のコマンドに有効期間の長いトークンを使用してトークンヘッダーを追加すると、次のようなcurlと同じアクションを実行できるようになります。kubectl

curl -s -k -XGET -H "Authorization: Bearer $KIND_TOKEN" -H "Accept: application/json, */*" -H "Content-Type: application/json" \
    -H "kubernetes/$Format" 'https://127.0.0.1:36599/api/v1/namespaces/default/pods/example' | jq .status.phase
# "Running"

リクエスト本文が必要な場合は、リクエストに含める必要のあるフィールドを調べてください。たとえば、ポッドを作成するときに、ここで説明するAPIを使用できます。その場合、次のリクエストが表示されます。


curl -k -XPOST -H "Authorization: Bearer $KIND_TOKEN" -H "Accept: application/json, */*" -H "Content-Type: application/json" \
    -H "kubernetes/$Format" https://127.0.0.1:36599/api/v1/namespaces/default/pods -d@pod.json

# To confirm
kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
example   0/1     Running   0          7s

オブジェクト属性については、 KubernetesAPIリファレンスを参照してください。さらに、次のコマンドを使用してOpenAPI定義を表示することもできます。

curl -k -X GET -H "Authorization: Bearer $KIND_TOKEN" https://127.0.0.1:36599/apis

REST APIを使用してKubernetesと直接やり取りするのは少し不格好かもしれませんが、それを使用することが理にかなっている場合があります。kubectl状況によっては、OpenShiftなどのKubernetesの別のディストリビューションを使用しているときに、同等のコマンドを持たないAPIとやり取りすることが含まれます。これにより、kubectlまたはクライアントSDKでカバーされていない追加のAPIが公開されます。

Pythonクライアント

ここでPythonクライアント自体に移動します。kubectlまたはと同じ手順を実行する必要がありcurlます。最初のステップは認証です。これを以下に示します。

from kubernetes import client
import os

configuration = client.Configuration()
configuration.api_key_prefix["authorization"] = "Bearer"
configuration.host = "https://127.0.0.1:36599"
configuration.api_key["authorization"] = os.getenv("KIND_TOKEN", None)
configuration.verify_ssl = False  # Only for testing with KinD!
api_client = client.ApiClient(configuration)
v1 = client.CoreV1Api(api_client)

ret = v1.list_namespaced_pod(namespace="default", watch=False)
for pod in ret.items:
    print(f"Name: {pod.metadata.name}, Namespace: {pod.metadata.namespace} IP: {pod.status.pod_ip}")
    # Name: example, Namespace: default IP: 10.244.2.2

まず、構成オブジェクトを定義します。これは、Bearer トークンを使用して認証することをクライアントに通知します。KinDクラスターがSSLを使用していないことを考慮して、実際のクラスターではSSLを無効にします。ただし、決してそうすべきではありません。

構成をテストするためにlist_namespaced_pod、APIクライアントのメソッドを使用してdefault名前空間内のすべてのポッドを取得してから、それらの、、およびを出力namenamespace ますIP

Deploymentここで、より現実的なタスクのために、以下を作成しましょう。

deployment_name = "my-deploy"
deployment_manifest = {
    "apiVersion": "apps/v1",
    "kind": "Deployment",
    "metadata": {"name": deployment_name, "namespace": "default"},
    "spec": {"replicas": 3,
             "selector": {
                "matchLabels": {
                    "app": "nginx"
                }},
        "template": {"metadata": {"labels": {"app": "nginx"}},
            "spec": {"containers": [
                {"name": "nginx", "image": "nginx:1.21.6", "ports": [{"containerPort": 80}]}]
            }
        },
    }
}

import time
from kubernetes.client.rest import ApiException

v1 = client.AppsV1Api(api_client)

response = v1.create_namespaced_deployment(body=deployment_manifest, namespace="default")
while True:
    try:
        response = v1.read_namespaced_deployment_status(name=deployment_name, namespace="default")
        if response.status.available_replicas != 3:
            print("Waiting for Deployment to become ready...")
            time.sleep(5)
        else:
            break
    except ApiException as e:
        print(f"Exception when calling AppsV1Api -> read_namespaced_deployment_status: {e}\n")

の作成に加えてDeployment、ポッドが利用可能になるのも待ちます。これを行うには、ステータスを照会し、Deployment 使用可能なレプリカの数を確認します。

また、などの関数名のパターンにも注意してcreate_namespaced_deploymentください。それをより明確にするために、さらにいくつか見てみましょう:

  • replace_namespaced_cron_job
  • patch_namespaced_stateful_set
  • list_namespaced_horizontal_pod_autoscaler
  • read_namespaced_daemon_set
  • read_custom_resource_definition

これらはすべて、グローバルリソースの形式operation_namespaced_resourceまたは専用です。operation_resourceこれらには、などのリソースステータスまたはなどのリソーススケールで操作を実行するメソッドの_status接尾辞を付けることができます。_scaleread_namespaced_deployment_statuspatch_namespaced_stateful_set_scale

強調すべきもう1つの点は、上記の例では、にclient.AppsV1Api属するすべてのリソースを操作できるようにするを使用してアクションを実行したことapiVersion: apps/v1です。CronJobを使用する場合は、代わりにBatchV1ApiapiVersion: batch/v1YAML形式)を選択します。PVCの場合は、を選択しCoreV1ApiますapiVersion: v1。あなたは要点を理解します。

ご想像のとおり、これは多くの機能から選択できます。幸いなことに、それらはすべてドキュメントにリストされており、それらのいずれかをクリックして、その使用例を取得できます。

基本的なCRUD操作に加えて、オブジェクトの変更を継続的に監視することもできます。明らかな選択は見ることEventsです:

from kubernetes import client, watch

v1 = client.CoreV1Api(api_client)
count = 10
w = watch.Watch()
for event in w.stream(partial(v1.list_namespaced_event, namespace="default"), timeout_seconds=10):
    print(f"Event - Message: {event['object']['message']} at {event['object']['metadata']['creationTimestamp']}")
    count -= 1
    if not count:
        w.stop()
print("Finished namespace stream.")

# Event - Message: Successfully assigned default/my-deploy-cb69f686c-2dspd to api-playground-worker2 at 2022-04-19T11:18:25Z
# Event - Message: Container image "nginx:1.21.6" already present on machine at 2022-04-19T11:18:26Z
# Event - Message: Created container nginx at 2022-04-19T11:18:26Z
# Event - Message: Started container nginx at 2022-04-19T11:18:26Z

ここでは、default名前空間でイベントを監視することを選択しました。最初の10個のイベントを取得してから、ストリームを閉じます。リソースを継続的に監視したい場合は、timeout_secondsw.stop()呼び出しを削除するだけです。

最初の例では、プレーンPythondictを使用してDeploymentオブジェクトを定義し、それをクライアントに渡しました。または、以下に示すように、ライブラリによって提供されるAPIモデル(クラス)を使用して、より多くのOOPスタイルを使用することもできます。

v1 = client.AppsV1Api(api_client)

deployment_manifest = client.V1Deployment(
    api_version="apps/v1",
    kind="Deployment",
    metadata=client.V1ObjectMeta(name=deployment_name),
    spec=client.V1DeploymentSpec(
        replicas=3,
        selector=client.V1LabelSelector(match_labels={
         "app": "nginx"
        }),
        template=client.V1PodTemplateSpec(
            metadata=client.V1ObjectMeta(labels={"app": "nginx"}),
            spec=client.V1PodSpec(
                containers=[client.V1Container(name="nginx",
                                               image="nginx:1.21.6",
                                               ports=[client.V1ContainerPort(container_port=80)]
                                               )]))
        )
)

response = v1.create_namespaced_deployment(body=deployment_manifest, namespace="default")

各議論にどのモデルを使用すべきかを理解しようとすることは敗戦であり、それは困難です。上記のようなリソースを作成するときは、常にモデルのドキュメントを使用し、個々のサブオブジェクトを作成するときにリンクをトラバースして、各フィールドで期待される値/タイプを把握する必要があります。

便利な例

これで、クライアントがどのように機能するかについての基本的な考え方がわかったはずです。そこで、毎日のKubernetes操作を自動化するのに役立つ可能性のあるいくつかの便利な例とスニペットを見てみましょう。

実行する可能性のある非常に一般的なことは、Deployment ロールアウトです—通常はで実行されkubectl rollout restartます。ただし、これを行うためのAPIはありません。その方法kubectlは、デプロイメントアノテーションを更新することです。具体的にkubectl.kubernetes.io/restartedAtは、現在時刻に設定します。これが機能するのは、ポッド仕様に変更を加えると再起動が発生するためです。

Pythonクライアントを使用して再起動を実行する場合は、次の手順を実行する必要があります。

from kubernetes import dynamic
from kubernetes.client import api_client  # Careful - different import - not the same as previous client!
import datetime

client = dynamic.DynamicClient(api_client.ApiClient(configuration=configuration))

api = client.resources.get(api_version="apps/v1", kind="Deployment")

# Even though the Deployment manifest was previously created with class model, it still behaves as dictionary:
deployment_manifest["spec"]["template"]["metadata"]["annotations"] = {
    "kubectl.kubernetes.io/restartedAt": datetime.datetime.utcnow().isoformat()
}

deployment_patched = api.patch(body=deployment_manifest, name=deployment_name, namespace="default")

もう1つの一般的な操作は、スケーリングDeploymentです。幸い、これには使用できるAPI関数があります。これを以下に示します。

from kubernetes import client

api_client = client.ApiClient(configuration)
apps_v1 = client.AppsV1Api(api_client)

# The body can be of different patch types - https://github.com/kubernetes-client/python/issues/1206#issuecomment-668118057
api_response = apps_v1.patch_namespaced_deployment_scale(deployment_name, "default", {"spec": {"replicas": 5}})

トラブルシューティングの目的で、ポッドを調べて周りを見回し、場合によっては変数をexec取得して正しい構成を確認することが理にかなっていることがよくあります。environment そのためのコードは次のとおりです。

from kubernetes.stream import stream

def pod_exec(name, namespace, command, api_instance):
    exec_command = ["/bin/sh", "-c", command]

    resp = stream(api_instance.connect_get_namespaced_pod_exec,
                  name,
                  namespace,
                  command=exec_command,
                  stderr=True, stdin=False,
                  stdout=True, tty=False,
                  _preload_content=False)

    while resp.is_open():
        resp.update(timeout=1)
        if resp.peek_stdout():
            print(f"STDOUT: \n{resp.read_stdout()}")
        if resp.peek_stderr():
            print(f"STDERR: \n{resp.read_stderr()}")

    resp.close()

    if resp.returncode != 0:
        raise Exception("Script failed")

pod = "example"
api_client = client.ApiClient(configuration)
v1 = client.CoreV1Api(api_client)

pod_exec(pod, "default", "env", v1)

# STDOUT: 
# KUBERNETES_SERVICE_PORT=443
# KUBERNETES_PORT=tcp://10.96.0.1:443
# HOSTNAME=example
# HOME=/root
# ...

上記のスニペットでは、必要に応じてシェルスクリプト全体を実行することもできます。

したがって、Taint問題のあるノードにを適用するとします。クラスタ管理指向のタスクに集中できます。Node Taints用の直接APIがないため、方法を見つけることができます。これが私たちを助けるためのコードです:


from kubernetes import client

api_client = client.ApiClient(configuration)
v1 = client.CoreV1Api(api_client)

# kubectl taint nodes api-playground-worker some-taint=1:NoSchedule
v1.patch_node("api-playground-worker", {"spec": {"taints": [{"effect": "NoSchedule", "key": "some-taint", "value": "1"}]}})

# kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints --no-headers
# api-playground-control-plane   [map[effect:NoSchedule key:node-role.kubernetes.io/master]]
# api-playground-worker          [map[effect:NoSchedule key:some-taint value:1]]
# api-playground-worker2         <none>
# api-playground-worker3         <none>

また、クラスターリソースの使用率を監視して、クラスターのスケーリングを自動化することもできます。通常は、kubectl topインタラクティブに情報を取得するために使用しますが、クライアントライブラリを使用すると、次のことができます。

# https://github.com/kubernetes-sigs/kind/issues/398
# kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.5.0/components.yaml
# kubectl patch -n kube-system deployment metrics-server --type=json \
#   -p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

from kubernetes import client

api_client = client.ApiClient(configuration)
custom_api = client.CustomObjectsApi(api_client)

response = custom_api.list_cluster_custom_object("metrics.k8s.io", "v1beta1", "nodes")  # also works with "pods" instead of "nodes"  

for node in response["items"]:
    print(f"{node['metadata']['name']: <30} CPU: {node['usage']['cpu']: <10} Memory: {node['usage']['memory']}")

# api-playground-control-plane   CPU: 148318488n Memory: 2363504Ki
# api-playground-worker          CPU: 91635913n  Memory: 1858680Ki
# api-playground-worker2         CPU: 75473747n  Memory: 1880860Ki
# api-playground-worker3         CPU: 105692650n Memory: 1881560Ki

上記の例はmetrics-server、クラスターにインストールされていることを前提としています。実行kubectl topしてそれを確認できます。KinDを使用している場合は、スニペットのコメントを使用してインストールしてください。

最後になりましたが、クラスター内のオブジェクトのデプロイまたは変更に使用するYAMLまたはJSONファイルが多数ある場合、またはクライアントで作成したものをエクスポートおよびバックアップする場合は、簡単な方法があります。YAML / JSONファイルをKubernetesオブジェクトに変換し、ファイルに戻す方法は次のとおりです。

# pip install kopf  # (Python 3.7+)
import kopf

api_client = client.ApiClient(configuration)
v1 = client.CoreV1Api(api_client)

pods = []

# https://stackoverflow.com/questions/59977058/clone-kubernetes-objects-programmatically-using-the-python-api/59977059#59977059
ret = v1.list_namespaced_pod(namespace="default")
for pod in ret.items:
    # Simple conversion to Dict/JSON
    print(api_client.sanitize_for_serialization(pod))

    # Conversion with fields clean-up
    pods.append(kopf.AnnotationsDiffBaseStorage()
                .build(body=kopf.Body(api_client.sanitize_for_serialization(pod))))


# Conversion from Dict back to Client object
class FakeKubeResponse:
    def __init__(self, obj):
        import json
        self.data = json.dumps(obj)


for pod in pods:
    pod_manifest = api_client.deserialize(FakeKubeResponse(pod), "V1Pod")
    ...

既存のオブジェクトをPythonディクショナリ(JSON)に変換する最初の方法はsanitize_for_serialization、生成された/デフォルトのすべてのフィールドで生の出力を生成するを使用することです。より良いオプションは、kopfライブラリのユーティリティメソッドを使用することです。これにより、不要なフィールドがすべて削除されます。そこから、辞書を適切なYAMLまたはJSONファイルに変換するのは簡単です。

このプロセスを逆にしたい場合(辞書からクライアントオブジェクトモデルに移動するdeserialize場合)、APIクライアントのメソッドを使用できます。ただし、このメソッドは引数に属性があることを想定しているdataため、そのような属性を持つコンテナークラスインスタンスを渡します。

Pythonクライアントで使用したいYAMLファイルがすでにある場合は、ユーティリティ関数を使用できますkubernetes.utils.create_from_yaml

すべてのライブラリ機能の完全な概要を取得するには、リポジトリ内のexamplesディレクトリを確認することをお勧めします。

また、ライブラリリポジトリの問題を確認することをお勧めします。ライブラリリポジトリには、イベントの並列処理ConfigMapの更新の監視など、クライアントの使用法の優れた例がたくさんあります。

結論

Pythonクライアントライブラリには何百もの関数が含まれているため、そこにあるすべての小さな機能やユースケースを網羅することは困難です。それらのほとんどは、数分後にライブラリの使用法をかなり自然にするはずの一般的なパターンに従います。

上記で示して参照したもの以外の例を探している場合は、Kubernetesオペレーターを作成するためのライブラリなど、PythonKubernetesクライアントを利用する他の一般的なツールを調べることをお勧めします。また、ライブラリ自体のテストを見てみると、その使用目的が示されているので非常に便利です。これが研究するのに優れたクライアントテストスイートです。

ソース:https ://betterprogramming.pub/automate-all-the-boring-kubernetes-operations-with-python-7a31bbf7a387

#python 

What is GEEK

Buddha Community

Pythonを使用してすべての退屈なKubernetes操作を自動化する
高橋  花子

高橋 花子

1652947500

Pythonを使用してすべての退屈なKubernetes操作を自動化する

Kubernetesは近年デファクトスタンダードになり、DevOpsエンジニアと開発者の両方が日常的にKubernetesを使用しています。ただし、実行するタスクの多くは同じで、退屈で、自動化が簡単です。

多くの場合、一連のコマンドを使用して簡単なシェルスクリプトを作成するのは簡単kubectlですが、より複雑な自動化タスクの場合、bashだけでは不十分であり、Pythonなどの適切な言語の能力が必要です。

そのため、この記事では、Kubernetes Pythonクライアントライブラリを活用して、煩わしいKubernetesタスクを自動化する方法を見ていきます。

遊び場

Kubernetesクライアントでのプレイを開始する前に、まず、安全にテストできるプレイグラウンドクラスターを作成する必要があります。ここからインストールできるKinD(DockerのKubernetes)を使用します。

次のクラスター構成を使用します。

# kind.yaml
# https://kind.sigs.k8s.io/docs/user/configuration/
apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
name: api-playground
nodes:
- role: control-plane
- role: worker
- role: worker
- role: worker

上記の構成からクラスターを作成するには、次のコマンドを実行できます。

kind create cluster --image kindest/node:v1.23.5 --config=kind.yaml

kubectl cluster-info --context kind-api-playground
# Kubernetes control plane is running at https://127.0.0.1:36599
# CoreDNS is running at https://127.0.0.1:36599/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

kubectl get nodes
# NAME                           STATUS     ROLES                  AGE   VERSION
# api-playground-control-plane   Ready      control-plane,master   58s   v1.23.5
# api-playground-worker          Ready      <none>                 27s   v1.23.5
# api-playground-worker2         NotReady   <none>                 27s   v1.23.5
# api-playground-worker3         NotReady   <none>                 27s   v1.23.5

クラスタが稼働している状態で、クライアントライブラリもインストールする必要があります(オプションで、仮想環境内に)。

python3 -m venv venv
source venv/bin/activate
pip install kubernetes

認証

Kubernetesクラスタ内でアクションを実行するには、最初に認証する必要があります。

長期間有効なトークンを使用するため、認証フローを繰り返し実行する必要はありません。ServiceAccount有効期間の長いトークンは、 :を作成することで作成できます。

kubectl create sa playground
kubectl describe sa playground

Name:                playground
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   playground-token-v8bq7
Tokens:              playground-token-v8bq7
Events:              <none>

export KIND_TOKEN=$(kubectl get secret playground-token-v8bq7 -o json | jq -r .data.token | base64 --decode)

サービスアカウントを使用すると、1人のユーザーに関連付けられないという利点もあります。これは、自動化の目的で常に推奨されます。

上記の出力からのトークンは、リクエストで使用できます。

curl -k -X GET -H "Authorization: Bearer $KIND_TOKEN" https://127.0.0.1:36599/apis

現在認証されていますが、多くのことを行う権限はありません。したがって、次に、を作成しRoleてバインドし、ServiceAccount リソースに対してアクションを実行できるようにする必要があります。コードは次のとおりです。


kubectl create clusterrole manage-pods \
    --verb=get --verb=list --verb=watch --verb=create --verb=update --verb=patch --verb=delete \
    --resource=pods
    
kubectl -n default create rolebinding sa-manage-pods \
    --clusterrole=manage-pods \
    --serviceaccount=default:playground

上記は、サービスアカウントに、default名前空間に限定されたポッドでのアクションを実行するためのアクセス許可を与えます。

常に役割を非常に狭く具体的に保つ必要がありますが、KinDで遊んでみると、以下に示すように、クラスター全体の管理者の役割を適用するのが理にかなっています。


kubectl create clusterrolebinding sa-cluster-admin \
  --clusterrole=cluster-admin \
  --serviceaccount=default:playground

生のリクエスト

何が行われているのか、またクライアントが内部で何をしているのかをよりよく理解するために、kubectlを使用した生のHTTPリクエストから始めますcurl

内部で行われている要求を見つける最も簡単な方法は、を使用して目的のkubectlコマンドを実行すること-v 10です。curlこれにより、以下に示す完全なコマンドが出力されます。

kubectl get pods -v 10
# <snip>
curl -k -v -XGET  -H "Accept: application/json;as=Table;v=v1;g=meta.k8s.io,application/json..." \
    'https://127.0.0.1:36599/api/v1/namespaces/default/pods?limit=500'
# <snip>

の出力はloglevel 10非常に冗長になりますが、どこかに上記のcurlコマンドがあります。

Bearer上記のコマンドに有効期間の長いトークンを使用してトークンヘッダーを追加すると、次のようなcurlと同じアクションを実行できるようになります。kubectl

curl -s -k -XGET -H "Authorization: Bearer $KIND_TOKEN" -H "Accept: application/json, */*" -H "Content-Type: application/json" \
    -H "kubernetes/$Format" 'https://127.0.0.1:36599/api/v1/namespaces/default/pods/example' | jq .status.phase
# "Running"

リクエスト本文が必要な場合は、リクエストに含める必要のあるフィールドを調べてください。たとえば、ポッドを作成するときに、ここで説明するAPIを使用できます。その場合、次のリクエストが表示されます。


curl -k -XPOST -H "Authorization: Bearer $KIND_TOKEN" -H "Accept: application/json, */*" -H "Content-Type: application/json" \
    -H "kubernetes/$Format" https://127.0.0.1:36599/api/v1/namespaces/default/pods -d@pod.json

# To confirm
kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
example   0/1     Running   0          7s

オブジェクト属性については、 KubernetesAPIリファレンスを参照してください。さらに、次のコマンドを使用してOpenAPI定義を表示することもできます。

curl -k -X GET -H "Authorization: Bearer $KIND_TOKEN" https://127.0.0.1:36599/apis

REST APIを使用してKubernetesと直接やり取りするのは少し不格好かもしれませんが、それを使用することが理にかなっている場合があります。kubectl状況によっては、OpenShiftなどのKubernetesの別のディストリビューションを使用しているときに、同等のコマンドを持たないAPIとやり取りすることが含まれます。これにより、kubectlまたはクライアントSDKでカバーされていない追加のAPIが公開されます。

Pythonクライアント

ここでPythonクライアント自体に移動します。kubectlまたはと同じ手順を実行する必要がありcurlます。最初のステップは認証です。これを以下に示します。

from kubernetes import client
import os

configuration = client.Configuration()
configuration.api_key_prefix["authorization"] = "Bearer"
configuration.host = "https://127.0.0.1:36599"
configuration.api_key["authorization"] = os.getenv("KIND_TOKEN", None)
configuration.verify_ssl = False  # Only for testing with KinD!
api_client = client.ApiClient(configuration)
v1 = client.CoreV1Api(api_client)

ret = v1.list_namespaced_pod(namespace="default", watch=False)
for pod in ret.items:
    print(f"Name: {pod.metadata.name}, Namespace: {pod.metadata.namespace} IP: {pod.status.pod_ip}")
    # Name: example, Namespace: default IP: 10.244.2.2

まず、構成オブジェクトを定義します。これは、Bearer トークンを使用して認証することをクライアントに通知します。KinDクラスターがSSLを使用していないことを考慮して、実際のクラスターではSSLを無効にします。ただし、決してそうすべきではありません。

構成をテストするためにlist_namespaced_pod、APIクライアントのメソッドを使用してdefault名前空間内のすべてのポッドを取得してから、それらの、、およびを出力namenamespace ますIP

Deploymentここで、より現実的なタスクのために、以下を作成しましょう。

deployment_name = "my-deploy"
deployment_manifest = {
    "apiVersion": "apps/v1",
    "kind": "Deployment",
    "metadata": {"name": deployment_name, "namespace": "default"},
    "spec": {"replicas": 3,
             "selector": {
                "matchLabels": {
                    "app": "nginx"
                }},
        "template": {"metadata": {"labels": {"app": "nginx"}},
            "spec": {"containers": [
                {"name": "nginx", "image": "nginx:1.21.6", "ports": [{"containerPort": 80}]}]
            }
        },
    }
}

import time
from kubernetes.client.rest import ApiException

v1 = client.AppsV1Api(api_client)

response = v1.create_namespaced_deployment(body=deployment_manifest, namespace="default")
while True:
    try:
        response = v1.read_namespaced_deployment_status(name=deployment_name, namespace="default")
        if response.status.available_replicas != 3:
            print("Waiting for Deployment to become ready...")
            time.sleep(5)
        else:
            break
    except ApiException as e:
        print(f"Exception when calling AppsV1Api -> read_namespaced_deployment_status: {e}\n")

の作成に加えてDeployment、ポッドが利用可能になるのも待ちます。これを行うには、ステータスを照会し、Deployment 使用可能なレプリカの数を確認します。

また、などの関数名のパターンにも注意してcreate_namespaced_deploymentください。それをより明確にするために、さらにいくつか見てみましょう:

  • replace_namespaced_cron_job
  • patch_namespaced_stateful_set
  • list_namespaced_horizontal_pod_autoscaler
  • read_namespaced_daemon_set
  • read_custom_resource_definition

これらはすべて、グローバルリソースの形式operation_namespaced_resourceまたは専用です。operation_resourceこれらには、などのリソースステータスまたはなどのリソーススケールで操作を実行するメソッドの_status接尾辞を付けることができます。_scaleread_namespaced_deployment_statuspatch_namespaced_stateful_set_scale

強調すべきもう1つの点は、上記の例では、にclient.AppsV1Api属するすべてのリソースを操作できるようにするを使用してアクションを実行したことapiVersion: apps/v1です。CronJobを使用する場合は、代わりにBatchV1ApiapiVersion: batch/v1YAML形式)を選択します。PVCの場合は、を選択しCoreV1ApiますapiVersion: v1。あなたは要点を理解します。

ご想像のとおり、これは多くの機能から選択できます。幸いなことに、それらはすべてドキュメントにリストされており、それらのいずれかをクリックして、その使用例を取得できます。

基本的なCRUD操作に加えて、オブジェクトの変更を継続的に監視することもできます。明らかな選択は見ることEventsです:

from kubernetes import client, watch

v1 = client.CoreV1Api(api_client)
count = 10
w = watch.Watch()
for event in w.stream(partial(v1.list_namespaced_event, namespace="default"), timeout_seconds=10):
    print(f"Event - Message: {event['object']['message']} at {event['object']['metadata']['creationTimestamp']}")
    count -= 1
    if not count:
        w.stop()
print("Finished namespace stream.")

# Event - Message: Successfully assigned default/my-deploy-cb69f686c-2dspd to api-playground-worker2 at 2022-04-19T11:18:25Z
# Event - Message: Container image "nginx:1.21.6" already present on machine at 2022-04-19T11:18:26Z
# Event - Message: Created container nginx at 2022-04-19T11:18:26Z
# Event - Message: Started container nginx at 2022-04-19T11:18:26Z

ここでは、default名前空間でイベントを監視することを選択しました。最初の10個のイベントを取得してから、ストリームを閉じます。リソースを継続的に監視したい場合は、timeout_secondsw.stop()呼び出しを削除するだけです。

最初の例では、プレーンPythondictを使用してDeploymentオブジェクトを定義し、それをクライアントに渡しました。または、以下に示すように、ライブラリによって提供されるAPIモデル(クラス)を使用して、より多くのOOPスタイルを使用することもできます。

v1 = client.AppsV1Api(api_client)

deployment_manifest = client.V1Deployment(
    api_version="apps/v1",
    kind="Deployment",
    metadata=client.V1ObjectMeta(name=deployment_name),
    spec=client.V1DeploymentSpec(
        replicas=3,
        selector=client.V1LabelSelector(match_labels={
         "app": "nginx"
        }),
        template=client.V1PodTemplateSpec(
            metadata=client.V1ObjectMeta(labels={"app": "nginx"}),
            spec=client.V1PodSpec(
                containers=[client.V1Container(name="nginx",
                                               image="nginx:1.21.6",
                                               ports=[client.V1ContainerPort(container_port=80)]
                                               )]))
        )
)

response = v1.create_namespaced_deployment(body=deployment_manifest, namespace="default")

各議論にどのモデルを使用すべきかを理解しようとすることは敗戦であり、それは困難です。上記のようなリソースを作成するときは、常にモデルのドキュメントを使用し、個々のサブオブジェクトを作成するときにリンクをトラバースして、各フィールドで期待される値/タイプを把握する必要があります。

便利な例

これで、クライアントがどのように機能するかについての基本的な考え方がわかったはずです。そこで、毎日のKubernetes操作を自動化するのに役立つ可能性のあるいくつかの便利な例とスニペットを見てみましょう。

実行する可能性のある非常に一般的なことは、Deployment ロールアウトです—通常はで実行されkubectl rollout restartます。ただし、これを行うためのAPIはありません。その方法kubectlは、デプロイメントアノテーションを更新することです。具体的にkubectl.kubernetes.io/restartedAtは、現在時刻に設定します。これが機能するのは、ポッド仕様に変更を加えると再起動が発生するためです。

Pythonクライアントを使用して再起動を実行する場合は、次の手順を実行する必要があります。

from kubernetes import dynamic
from kubernetes.client import api_client  # Careful - different import - not the same as previous client!
import datetime

client = dynamic.DynamicClient(api_client.ApiClient(configuration=configuration))

api = client.resources.get(api_version="apps/v1", kind="Deployment")

# Even though the Deployment manifest was previously created with class model, it still behaves as dictionary:
deployment_manifest["spec"]["template"]["metadata"]["annotations"] = {
    "kubectl.kubernetes.io/restartedAt": datetime.datetime.utcnow().isoformat()
}

deployment_patched = api.patch(body=deployment_manifest, name=deployment_name, namespace="default")

もう1つの一般的な操作は、スケーリングDeploymentです。幸い、これには使用できるAPI関数があります。これを以下に示します。

from kubernetes import client

api_client = client.ApiClient(configuration)
apps_v1 = client.AppsV1Api(api_client)

# The body can be of different patch types - https://github.com/kubernetes-client/python/issues/1206#issuecomment-668118057
api_response = apps_v1.patch_namespaced_deployment_scale(deployment_name, "default", {"spec": {"replicas": 5}})

トラブルシューティングの目的で、ポッドを調べて周りを見回し、場合によっては変数をexec取得して正しい構成を確認することが理にかなっていることがよくあります。environment そのためのコードは次のとおりです。

from kubernetes.stream import stream

def pod_exec(name, namespace, command, api_instance):
    exec_command = ["/bin/sh", "-c", command]

    resp = stream(api_instance.connect_get_namespaced_pod_exec,
                  name,
                  namespace,
                  command=exec_command,
                  stderr=True, stdin=False,
                  stdout=True, tty=False,
                  _preload_content=False)

    while resp.is_open():
        resp.update(timeout=1)
        if resp.peek_stdout():
            print(f"STDOUT: \n{resp.read_stdout()}")
        if resp.peek_stderr():
            print(f"STDERR: \n{resp.read_stderr()}")

    resp.close()

    if resp.returncode != 0:
        raise Exception("Script failed")

pod = "example"
api_client = client.ApiClient(configuration)
v1 = client.CoreV1Api(api_client)

pod_exec(pod, "default", "env", v1)

# STDOUT: 
# KUBERNETES_SERVICE_PORT=443
# KUBERNETES_PORT=tcp://10.96.0.1:443
# HOSTNAME=example
# HOME=/root
# ...

上記のスニペットでは、必要に応じてシェルスクリプト全体を実行することもできます。

したがって、Taint問題のあるノードにを適用するとします。クラスタ管理指向のタスクに集中できます。Node Taints用の直接APIがないため、方法を見つけることができます。これが私たちを助けるためのコードです:


from kubernetes import client

api_client = client.ApiClient(configuration)
v1 = client.CoreV1Api(api_client)

# kubectl taint nodes api-playground-worker some-taint=1:NoSchedule
v1.patch_node("api-playground-worker", {"spec": {"taints": [{"effect": "NoSchedule", "key": "some-taint", "value": "1"}]}})

# kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints --no-headers
# api-playground-control-plane   [map[effect:NoSchedule key:node-role.kubernetes.io/master]]
# api-playground-worker          [map[effect:NoSchedule key:some-taint value:1]]
# api-playground-worker2         <none>
# api-playground-worker3         <none>

また、クラスターリソースの使用率を監視して、クラスターのスケーリングを自動化することもできます。通常は、kubectl topインタラクティブに情報を取得するために使用しますが、クライアントライブラリを使用すると、次のことができます。

# https://github.com/kubernetes-sigs/kind/issues/398
# kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.5.0/components.yaml
# kubectl patch -n kube-system deployment metrics-server --type=json \
#   -p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

from kubernetes import client

api_client = client.ApiClient(configuration)
custom_api = client.CustomObjectsApi(api_client)

response = custom_api.list_cluster_custom_object("metrics.k8s.io", "v1beta1", "nodes")  # also works with "pods" instead of "nodes"  

for node in response["items"]:
    print(f"{node['metadata']['name']: <30} CPU: {node['usage']['cpu']: <10} Memory: {node['usage']['memory']}")

# api-playground-control-plane   CPU: 148318488n Memory: 2363504Ki
# api-playground-worker          CPU: 91635913n  Memory: 1858680Ki
# api-playground-worker2         CPU: 75473747n  Memory: 1880860Ki
# api-playground-worker3         CPU: 105692650n Memory: 1881560Ki

上記の例はmetrics-server、クラスターにインストールされていることを前提としています。実行kubectl topしてそれを確認できます。KinDを使用している場合は、スニペットのコメントを使用してインストールしてください。

最後になりましたが、クラスター内のオブジェクトのデプロイまたは変更に使用するYAMLまたはJSONファイルが多数ある場合、またはクライアントで作成したものをエクスポートおよびバックアップする場合は、簡単な方法があります。YAML / JSONファイルをKubernetesオブジェクトに変換し、ファイルに戻す方法は次のとおりです。

# pip install kopf  # (Python 3.7+)
import kopf

api_client = client.ApiClient(configuration)
v1 = client.CoreV1Api(api_client)

pods = []

# https://stackoverflow.com/questions/59977058/clone-kubernetes-objects-programmatically-using-the-python-api/59977059#59977059
ret = v1.list_namespaced_pod(namespace="default")
for pod in ret.items:
    # Simple conversion to Dict/JSON
    print(api_client.sanitize_for_serialization(pod))

    # Conversion with fields clean-up
    pods.append(kopf.AnnotationsDiffBaseStorage()
                .build(body=kopf.Body(api_client.sanitize_for_serialization(pod))))


# Conversion from Dict back to Client object
class FakeKubeResponse:
    def __init__(self, obj):
        import json
        self.data = json.dumps(obj)


for pod in pods:
    pod_manifest = api_client.deserialize(FakeKubeResponse(pod), "V1Pod")
    ...

既存のオブジェクトをPythonディクショナリ(JSON)に変換する最初の方法はsanitize_for_serialization、生成された/デフォルトのすべてのフィールドで生の出力を生成するを使用することです。より良いオプションは、kopfライブラリのユーティリティメソッドを使用することです。これにより、不要なフィールドがすべて削除されます。そこから、辞書を適切なYAMLまたはJSONファイルに変換するのは簡単です。

このプロセスを逆にしたい場合(辞書からクライアントオブジェクトモデルに移動するdeserialize場合)、APIクライアントのメソッドを使用できます。ただし、このメソッドは引数に属性があることを想定しているdataため、そのような属性を持つコンテナークラスインスタンスを渡します。

Pythonクライアントで使用したいYAMLファイルがすでにある場合は、ユーティリティ関数を使用できますkubernetes.utils.create_from_yaml

すべてのライブラリ機能の完全な概要を取得するには、リポジトリ内のexamplesディレクトリを確認することをお勧めします。

また、ライブラリリポジトリの問題を確認することをお勧めします。ライブラリリポジトリには、イベントの並列処理ConfigMapの更新の監視など、クライアントの使用法の優れた例がたくさんあります。

結論

Pythonクライアントライブラリには何百もの関数が含まれているため、そこにあるすべての小さな機能やユースケースを網羅することは困難です。それらのほとんどは、数分後にライブラリの使用法をかなり自然にするはずの一般的なパターンに従います。

上記で示して参照したもの以外の例を探している場合は、Kubernetesオペレーターを作成するためのライブラリなど、PythonKubernetesクライアントを利用する他の一般的なツールを調べることをお勧めします。また、ライブラリ自体のテストを見てみると、その使用目的が示されているので非常に便利です。これが研究するのに優れたクライアントテストスイートです。

ソース:https ://betterprogramming.pub/automate-all-the-boring-kubernetes-operations-with-python-7a31bbf7a387

#python