1クール続けるブログ

とりあえず1クール続けるエンジニアの備忘録

GKEのマスターノードVerが上がったので変更点まとめる

2018年8月20日からGKEのマスターノードのVersionが1.8.xは1.9.7に、1.9.xは1.10.6に上がりました。
ノードの自動アップグレードonにしているクラスターは何もしなくても、ワーカーノードのVersionも上がってることかと思います。 ただ、バージョン上がると急に期待している動作と違う挙動が見られることもあります。 例えば、v1.9.7ではgcePersistentDiskのSubPathマウントがうまくいかず、Podが起動しないという事象が起こり得ます。

本番運用しているシステムでワーカーノードの自動アップグレードはリスクがあります。 そのため、マスターノードがバージョンアップしたタイミングで、新しいバージョンのノードプールを作成し、Podを新バージョンのノードプールで起動するのを確認していく方法を取るというプロジェクトが多いのではと思います。

バージョンアップに際し、変更点を認識することが必要です。ということで、ver.1.9.xになることでどのような変化があるのか調べてみました。 deploymentなどのワークロードがGAになったこともあり、デフォルト値の変更などが気になるところです。

以下を参考にいたしました。 気になったことがあれば順次拾っていきます。

www.mirantis.com

github.com

kubernetes.io

Workloads API

kindがDaemonSet, Deployment, ReplicaSet, StatefulSetのapiVersionがextensions / v1beta2からapps / v1へと変更になります。今後、apps / v1への開発は行われるが、その前のバージョンには行われないようなのでこの機会にマニフェストを変更しておきたいところです。apiVersionを新しくしたマニフェストkubectl applyした後に、kubectl get deployment sample -o yamlしても 、apiVersionはextensions / v1beta1と表示されることがあります。これは、登録したマニフェストがapiServer内部で互換性のあるバージョンに関しては変換されるからです。 そして、kubectl get deployment sample -o yaml --v=6と打つと、/apis/extensions/v1beta1/namespaces/…というリソースにアクセスしていることが分かる。
実は、kubectl get deployments.v1.appsと打つと、表示されるマニフェストのapiVersionはapps/v1となり、オプションで--v=6として再度叩くと、apis/apps/v1/namespaces…というリソースにアクセスしていることが分かる。

kubectl convertを使用して、グループバージョン間でマニフェストを変換できるとのことです。以下のコマンドで変換できます。 kubectl convert -f sample_deployment.yaml -o yaml --output-version='apps/v1'

デフォルト値として、今までセットされていたものが外れているケースもある。.spec.selectorだ。今までは、デフォルト値として.template.metadata.labelsに記載された値をセットしていた。apps/v1からは明確な記載となる。

API Machinery

Admission Control

Admission webhooksがbetaになった。Admission Controllerの拡張機能。 Admission Controllerは認証・認可の後、Objectが永続化する前にクライアントからの要求を受け入れるか判定する仕組み。2種類あり、mutatingはクライアントの要求書き換え、validatingはクライアントの要求を受け入れるかどうかを判断する。 mutatingはマニフェストに、決まったアノテーションを付けたり、別コンテナ(例えばEnvoy)を付与したりできる。validatingは、マニフェストの内容やクライアントによって受容するか決められる。

Admission Webhooksはkube-apiserverにCallback先としてHTTPサーバを登録しておくと、そこにリクエストが飛んで来て、それに対してレスポンスを返すことで、Admission Controlを成立させる機能。

GKEにおいても使えそうな感じがするのですが(Istioがこの仕組み使って自動でEnvoyを配置しているため)、方法がわかりませんでした。

参考:Learn more about Admission Webhooks - Speaker Deck

Custum Resource

Custom Resource Definition (CRD)というのがbetaになった。ユーザ独自で定義したリソースのこと。knativeもこの仕組みを使っているはず。 Controllerの作成に便利なツールも出てきている(kube-builderなど)

GitHub - kubernetes/sample-controller: Repository for sample controller. Complements sample-apiserver

KubernetesのCRD(Custom Resource Definition)とカスタムコントローラーの作成

Kubernetes勉強会第6回 〜Kubernetesの実行と管理、CustomResourceDefinitions(CRD)、Container Runtime Interface(CRI)〜 - 世界中の羊をかき集めて

Extending Kubernetes with Custom Resources and Operator Frameworks - Speaker Deck

Auth

RBAC

組み込み(デフォルト)のadmin/edit/viewロールが定義されていて、これをcluster role aggregationで使うことができる。以下のような形。以下に出てくるcronTabはカスタムリソースのため組み込みのRoleでは制御されていない。ClusterRoleなので、namespace関係なし。Roleであれば、namespaceで分割する。

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: aggregate-cron-tabs-edit
  labels:
    # Add these permissions to the "admin" and "edit" default roles.
    rbac.authorization.k8s.io/aggregate-to-admin: "true"
    rbac.authorization.k8s.io/aggregate-to-edit: "true"
rules:
- apiGroups: ["stable.example.com"]
  resources: ["crontabs"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

CLI

kubectl

  • kubectl cp /tmp/foo <some-namespace>/<some-pod>:/tmp/bar -c <specific-container>でremoteのファイルをlocalにcopyできる
  • kubectl create pdbでデフォルト値をセットしなくなったらしい

Network

IPv6

IPv6対応(alpha)したとのこと。ただ、alphaの機能を使用するためには、 GKEの場合にはクラスター作成時に指定をすることが必要なので、面倒かもしれない。

CoreDNS

CoreDNSがリリース。kube-dnsの代わりに使用することもできる(1.11でデフォルトがCoreDNSだったような)

Other

  • –cleanup-ipvsフラグができてkube-dns起動時に既存のルールをフラッシュするか決められるように
  • ホストの/etc/resolve.confまたは-resolv-confに "options"を追加することでpodのresolve.confに反映させることが出来るようになった

Strorage

  • PersistentVolumePersistentVolumeClaimのサイズは0より大きくなくてはいけない。
  • GCEマルチクラスタでは、ノードを持たないゾーンでPersistentVolumeオブジェクトが動的にプロビジョニングされなくなります。

まとめ

1.9でBetaになった機能は拡張するためのものが多い。よりエンタープライズでも運用できるように改善されてきたイメージです。 ただ、使いどころを間違えると管理がかなり大変になりそうので気をつけなきゃですね。

記事書いてて思いましたが、まだまだ勉強が全然足らないですね…。

Goの並行処理解説ブログを参考に少しソースを書き換えてみて理解を深める

Goの並行処理について書かれたブログ

medium.com

このブログがイラスト付きで本当に可愛くGoの平行処理の基礎を教えてくれて良いんです。

このブログを参考に整理してみる

このブログはある一つの例を取って、並行処理に記述しています。それは鉱物の発見・掘り出し・加工です。 元のブログでは、それぞれの動作をするGopher君が描かれています(めちゃめちゃ愛らしいので是非見てください)。

シングルスレッド処理とマルチスレッド処理の違い

シングルスレッド

Gopher族Gary君が1人で鉱物の加工までをこなします。なんてマルチな才能なんだ。 ブログではシングルスレッドのソースコードは載ってなかったので愚直に書きました。

出力

From Finder:  [Ore Ore Ore]
From Miner:  [minedOre minedOre minedOre]
From Smelter:  [smeltedOre smeltedOre smeltedOre]
package main

import (
    "fmt"
    "strings"
)

func main() {
   // すべてGary君のお仕事
    theMine := [5]string{"Rock", "Ore", "Ore", "Rock", "Ore"}
    foundOre := finder(theMine)
    minedOre := miner(foundOre)
    smelter(minedOre)
}

func finder(mine [5]string) []string {
    foundOre := make([]string, 0)
    for _, m := range mine {
        if m == "Ore" {
            foundOre = append(foundOre, m)
        }
    }
    fmt.Println("From Finder: ", foundOre)
    return foundOre
}

func miner(foundOre []string) []string {
    minedOre := make([]string, 0)
    for _, fo := range foundOre {
        minedOre = append(minedOre, fmt.Sprintf("mined%s", fo))
    }
    fmt.Println("From Miner: ", minedOre)
    return minedOre
}

func smelter(minedOre []string) {
    smeltedOre := make([]string, 0)
    for _, mo := range minedOre {
        smeltedOre = append(smeltedOre, strings.Replace(mo, "mined", "smelted", -1))
    }
    fmt.Println("From Smelter: ", smeltedOre)
}

マルチスレッド

Gary君は鉱物の発見、Janeは掘り出し、Peterは加工を行うようにしました。 ブログのほうではそれぞれをgoroutineにして処理を行っていました。そのまま写経するのも面白くないのでselectcontextを使って書いてみました。

出力

From Finder:  ore
From Miner:  minedOre
From Smelter: Ore is smelted
From Finder:  ore
From Miner:  minedOre
From Smelter: Ore is smelted
From Finder:  ore
From Miner:  minedOre
From Smelter: Ore is smelted
func main() {
    theMine := [5]string{"rock", "ore", "ore", "rock", "ore"}
    oreChannel := make(chan string, 5)
    minedOreChan := make(chan string, 5)

    ctx := context.Background()
    ctx, cancel := context.WithTimeout(ctx, time.Second)
    defer cancel()

    go func(ctx context.Context) {
        for {
            select {
            // Janeの仕事
            case foundOre := <-oreChannel:
                fmt.Println("From Finder: ", foundOre)
                minedOreChan <- "minedOre"
            // Peterの仕事
            case minedOre := <-minedOreChan:
                fmt.Println("From Miner: ", minedOre)
                fmt.Println("From Smelter: Ore is smelted")
            }
        }
    }(ctx)

    // Gary君の仕事
    go func(mine [5]string) {
        for _, item := range mine {
            if item == "ore" {
                oreChannel <- item
                time.Sleep(10 * time.Millisecond)
            }
        }
    }(theMine)

    <-ctx.Done()
}

channel

channelはgoroutineが相互にやり取りするときに使用されます。上の例だと、finderとminerがやり取りするときとminerがsmelterとやり取りするときに使用しています。goroutineはchannelに対して送信と受信ができます。<-を使用して表現します。

oreChannel <- item  // 送信
foundOre := <-oreChannel  // 受信

このchannelという機構のおかげでJaneはGary君がmineをすべて見つけるのを待たずして、見つけた分から処理をしていくことができます。

channelはいろんな状況でgoroutineをブロックします。これによって同期的な処理を行うことも可能です。 バッファ付きチャネルとそうでないチャネルの2種類があります。チャネルに送信するときバッファがない場合は、ブロックされます。oreChannel := make(chan string, 5)宣言のときにcapを指定しないとbufferなしのchannelになるはずです。

context

goの1.6まではcontextパッケージが無く自前で実装していたそうな。contextパッケージは、キャンセルを伝搬できるのも強みとのこと。 やはりdeeeetさんは神

Go1.7のcontextパッケージ | SOTA

まとめ

selectchannelの組み合わせで楽に並行処理を実装できる。場合によっては、selectじゃなくてfor~rangeのほうが良いかもしれない。 タイムアウト系はcontextパッケージを利用する。

CurlでGCSにファイルをアップロードする

gsutilがどうしても使えないけどGCS使いたい

例えば、KubernetesのPostStartとかPrestopでGCSからダウンロード/アップロードするとか。
Dockerfile内でGCS使わなくてはいけなくなった。でもそれだけのためにRUNして後でお片づけするのも時間ちょっと増えるし嫌みたいな時とか。

curlでの処理

アクセストークンを取得

jqが使えれば、もっとちゃんとしたのが書けるのですが今回は無い想定で書いてます。もっと良い方法があれば是非教えていただきたく。 以下を参照。

Creating and Enabling Service Accounts for Instances  |  Compute Engine Documentation  |  Google Cloud

TOKEN=$(curl "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token?alt=text" -H "Metadata-Flavor: Google" | grep access_token |awk '{ print $2 }')

POSTリクエストでファイルアップロードする

TEXT="文字列"
curl -X POST --data "${TEXT}" \
                    -H "Authorization: Bearer ${TOKEN}" \
                    -H "Content-Type: text/plain" \
                    "https://www.googleapis.com/upload/storage/v1/b/${bucket}/o?uploadType=media&name=${OBJECT_NAME}"

k8sのdeploymentのpostStartに記述してみる

アップロードするパスは後で見やすくなるように適宜スラッシュ入れて区切るべしってやつですね。アップロードが多くなるようなら日にちで一旦スラッシュ入れたりとかになるんでしょうか。 postStart / preStopで複数行の記述をする方法に関しては以下を参考にしました。

kubernetes - multiple command in postStart hook of a container - Stack Overflow

          preStop:
            exec:
              command:
              - "sh"
              - "-c"
              - >
                ACCESS_TOKEN=$(curl "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token?alt=text" -H "Metadata-Flavor: Google" | grep access_token |awk '{ print $2 }');
                TEXT="文字列";
                OBJECT_NAME=Bucket/$(date +"%Y%m%d%H%M%S")_$(hostname);
                curl -X POST --data "${TEXT}" \
                    -H "Authorization: Bearer ${ACCESS_TOKEN}" \
                    -H "Content-Type: text/plain" \
                    "https://www.googleapis.com/upload/storage/v1/b/${bucket}/o?uploadType=media&name=${OBJECT_NAME}";

docker参照コマンドチートシート

よく使うDockerコマンド

調査系のみ

コンテナの基本情報を絞って表示

docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
NAMES               IMAGE               STATUS
r1                  redis               Up 25 minutes

コンテナのIPアドレス一覧

docker ps -q | xargs docker inspect --format '{{ .Name }} - {{ .NetworkSettings.IPAddress }}'
/r1 - 172.18.0.2

コンテナがマウントしているボリュームを表示

docker run  -v /docker/redis-data:/data --name r1 -d redis
# マウント一覧を表示
docker ps -q   | xargs docker inspect --format '{{ .Name }} - {{range $mount :=.Mounts}}{{ $mount.Source }} <-> {{ $mount.Destination }}{{end}}'
/r1 - /docker/redis-data <-> /data

コンテナの統計情報

docker ps -q | xargs docker stats
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT     MEM %
     NET I/O             BLOCK I/O           PIDS
3c1719011282        r1                  0.08%               6.918MiB / 737.6MiB   0.94%     26.3kB / 90B        6.37MB / 0B         4

docker内のネットワーク

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
cc9ab9e4598e        bridge              bridge              local
fa054a9af353        host                host                local
f50397115ef2        none                null                local
$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "cc9ab9e4598e563b04c3c58f93ccdc15d9c1c682abbaf05e44034c63254cde4f",
        "Created": "2018-08-06T16:05:15.115331313Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.18.0.1/24",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "0e4a62e4d4a90d38f9f3d1be868f278807b390cdaba92951448ef64bdedd492e": {
                "Name": "tutorial_web_1",
                "EndpointID": "b396b59a93be30084d902e882eb21e961eac9d8599edbf32e832e1c05d3dc305",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/24",
                "IPv6Address": ""
            },
            "94158270f37a09f5ee28412dc569b048319fec166e1b0cf952fd8efa24c798a7": {
                "Name": "tutorial_redis_1",
                "EndpointID": "a14d820f0659721ebe1293e56871e1e68f94d6e07c5067f85c892e37a86bcfe4",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/24",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

まとめ

コンテナの起動や削除は開発環境(ローカル)はcompose、検証・本番環境はECS/GKEだから使用頻度が高いのは調査系のみだった。

ECSでnode exporterのネットワーク関係のメトリクスが取れない

ネットワークのメトリクス大事ですよね

障害が発生したときなどに、何が起因なのか切り分ける場合などにネットワークのメトリクスはとても大事になると思います。 例えば、TCPのコネクションなどはデータベースとの接続を見るときとかに役に立ちますよね。

node exporter で上手くメトリクスが取れない…?

PrometheusとGrafanaで監視ツールを構成していた場合に、node exporterを使って、コンテナが乗っているノードのメトリクスを取ることが多いと思います。

ある日、ECSでコンテナを動かしていたときに、ネットワーク関係のメトリクスが上手く取れていないことに気がつきました。

明らかに0でない値が入るであろうメトリクスなのに0で取得されるというものです。

何が原因だったのか

私の予想・考察的要素もあります。ご注意ください。

ECSでポートマッピングを使いたかったため、ネットワークモードをBridgeにして使用していました。デフォルトもBridgeだったはずです。

Bridgeモードはdockerデーモンを起動した際に作成される仮想ブリッジdocker0を使用します。 docker0に割り当てられるIPアドレス空間は、ホストの既存の空いているIPアドレスから特定の範囲を取ってきたものです。

このdocker0に紐付いたveth(Virtual Ethernet)インターフェースが割り当てられ、コンテナは外部と通信するときにに、このvethを経由することになる。 画像は以下から引用させていただきました。 Docker - Docker Reference Architecture: Designing Scalable, Portable Docker Container Networks

network namespaceが別々になっていることがわかります。netstatで取得できたメトリクスはこのコンテナのnetwork namespace内の情報だったと思われます。

実際に外部とのコネクションを持ってるのはホストなので、想定するメトリクスが得られないのではと結論づけました。

どのように対応したか

ホストのネットワークスタックを監視するようにコンテナのネットワークモードをhostに変更しました。

図を見てわかる通り、vethを経由せず、同じnetwork namespace内で通信を行います。 この場合だと期待されたメトリクスを取得することができました。

まとめ

監視の際のノードのメトリクス取得はdockerのネットワークモードを考慮する必要性がある。

pod(Kubernetes)のlifecycle.prestopの挙動

コンテナ削除時すぐにコンテナをSTOPされると困ることありません?

 

例えば….?

ApacheやNginxなどのWebサーバはSIGTERMが送られても、処理中のコネクションがCloseされないまま終了してしまう。 理想としては、残っている接続済みのコネクションを終了してからコンテナがSTOPして欲しいですよね。

Prestopを設定する

SIGTERMでの挙動は以下のようなもの(https://httpd.apache.org/docs/2.2/ja/stopping.htmlより引用)

TERM あるいは stop シグナルを親プロセスに送ると、即座に子プロセス全てを kill しようとします。 子プロセスを完全に kill し終わるまでに数秒かかるかもしれません。 その後、親プロセス自身が終了します。 処理中のリクエストは全て停止され、もはやリクエストに対する 応答はされません。

これでは、まずいのでPrestopフックを入れます。PrestopフックはPodのシャットダウンプロセスの最初に実行されるものです。Deploymentの.spec.template.spec.containers.lifecycle.preStopに処理を記載していきます。httpGet等でhttpリクエストを引っ掛けることもできますが、基本的にはexecで記述することが多いように思えます。execは記述したコマンドの同一のコンテナで実行します。

Apacheのwebサーバの場合だと、httpd -k graceful-stopをDeploymentの.spec.template.spec.containers.lifecycle.preStop.exec.commandに書いていくような形となるかと思います。Pod preStop フックの実行と Service エンドポイントからの Pod IP アドレス削除は並列して実行されるため、preStop フックの実行が先に実行されてしまいアクセスがまだ来る状態でコネクション作成をやめてしまうことになります。 なので、httpd -k graceful-stopの前に数秒置く必要があります。また、指定したコマンドが非同期の場合には、勝手にpreStopフックが完了されたとみなし、SIGTERMがPodのコンテナに送られてしまいます。そのため、非同期のコマンドのあとにはそのコマンドでかかる時間sleepしておくなどするべきです。

もしapacheのリクエストのタイムアウト時間を30秒としているならば、以下のような設定となるかと思います。 terminationGracePeriodSecondsはpodのシャットダウンプロセスが始まってからSIGKILLが送られるまでの時間です。preStopの設定を入れている場合は、preStop処理が終了後にSIGTERMが送られ、シャットダウンプロセスが始まってからterminationGracePeriodSecondsで指定した秒数が経過してもプロセスが完了していない場合にはSIGLKILLが送られます。デフォルトは30秒となっているはずです。

apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  containers:
  - name: lifecycle-demo-container
    image: httpd
    lifecycle:
      preStop:
        exec:
          command: ["sh", "-c", "sleep 2; httpd -k graceful-stop; sleep 30"]
  terminationGracePeriodSeconds: 40

kubernetesソースコード上では以下のように、preStopが実装されてます。 preStopの定義がDeploymentにない場合は、すぐにStopContainerが送られてしまうのがわかると思います。

        // Run the pre-stop lifecycle hooks if applicable and if there is enough time to run it
    if containerSpec.Lifecycle != nil && containerSpec.Lifecycle.PreStop != nil && gracePeriod > 0 {
        gracePeriod = gracePeriod - m.executePreStopHook(pod, containerID, containerSpec, gracePeriod)
    }
    // always give containers a minimal shutdown window to avoid unnecessary SIGKILLs
    if gracePeriod < minimumGracePeriodInSeconds {
        gracePeriod = minimumGracePeriodInSeconds
    }
    if gracePeriodOverride != nil {
        gracePeriod = *gracePeriodOverride
        glog.V(3).Infof("Killing container %q, but using %d second grace period override", containerID, gracePeriod)
    }

    err := m.runtimeService.StopContainer(containerID.ID, gracePeriod)
    if err != nil {
        glog.Errorf("Container %q termination failed with gracePeriod %d: %v", containerID.String(), gracePeriod, err)
    } else {
        glog.V(3).Infof("Container %q exited normally", containerID.String())
    }

Prestopフックですが、標準出力などに吐き出してもStackdriverなどでは拾えないので注意です。なお失敗した場合には、kubectl describe <pod>のイベントのところに表示されます。

KubernetesのDeploymentでややこしいところ

Deploymentに何回も同じキーワード出てきてない…?

Deploymentの中身ってこんな感じ

apiVersionのところは、1.9からDeploymentがベータでなくなったため、1.8以前とは書き方が異なります。注意。GKE使っている人で公式のマスターノードのバージョンを自動アップグレードに任せている人は、まだバージョンが1.8なのでextensions/v1beta1となっているはず。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

3つのLabel

.metadataに含まれるものと、.spec.selector.matchLabels.spec.template.metadata.labelsで3回もKeyValueでラベルを書くところがあるじゃないですか!Deploymentは、ECSでいうServiceとTask Definitionをガッチャンコした感じなので、要素が詰まってて混乱してしまいがち。 例えば、ECSのServiceで指定するクラスターや最小ヘルス率、タスク数などは、KubernetesのDeploymentではそれぞれ、.spec.template.spec.nodeSelector(厳密には全然違うけど)、.spec.strategy.rollingUpdate.maxUnavailable.spec.replicasとなる。

この3箇所で書くLabelはそれぞれ意味が違う

  • .metadata.labels : Deployment自体へのlabelを指す。kubectl get deployment -l app=nginxとかコマンドラインで使うことが多いかも。hpaで定義するときにこのlabelを使用すると思いがちだが、hpaでの定義は.metadata.nameを使う
  • .spec.selector.matchLabels : これはReplica Setで管理するpodのラベルを定義するもの。このラベルにマッチするpodを.spec.replicasの値に保とうとする。
  • spec.template.metadata.labels : これがtemplateで定義しているPodにひっつくLabel。基本的には、上のreplica setの管理対象として書いたpodのlabelと一致させる。PDBやServiceで指定するのに使ったりする。他のワークロードから参照されるので慎重に。

2つのspec

  • .spec : これはDeploymentという単位に対するspecification(仕様)。
  • .spec.template.spec : これはpodという単位に対するspecification(仕様)。

まとめ

spec.template.metadata.labelsで指定しているLabelは、PDBやServiceで宣言することになる。 .spec.template.spec.nodeSelectorも言ってしまえば、label記述だったか…。