記録。

めも。

IstioのSidecarリソースについてのちょっとメモ。

なぜ書いているのか?

Istioをドキュメントを読んだり、動かしてみたりしていました。

しかし、タイトルに記載したSidecarリソースについてはいまいち理解できていませんでした。

個人的に抱えているこのモヤモヤをなくすためにIstioのSidecarリソースについて理解したことを書いていこうと思います。

IstioのSidecarリソースはいったい何者なのか

Istioの1.1にリリースされたカスタムリソースです。

ドキュメントには下記のように記載されています。

Sidecar describes the configuration of the sidecar proxy that mediates inbound and outbound communication to the workload instance it is attached to.

The Sidecar configuration provides a way to fine tune the set of ports, protocols that the proxy will accept when forwarding traffic to and from the workload. In addition, it is possible to restrict the set of services that the proxy can reach when forwarding outbound traffic from workload instances.

https://istio.io/latest/docs/reference/config/networking/sidecar/より抜粋

  • Fine-tune the set of ports and protocols that an Envoy proxy accepts.
  • Limit the set of services that the Envoy proxy can reach.

You can specify that you want a sidecar configuration to apply to all workloads in a particular namespace, or choose specific workloads using a workloadSelector.

https://istio.io/latest/docs/concepts/traffic-management/#sidecarsより抜粋

デフォルトの状態でアプリケーションをデプロイするとistio proxyはメッシュ内のすべてのワークロードと通信できるように設定を持ちます。

このSidecarリソースは、istio proxyが持つ通信の設定を調整したりすることができるリソースです。

例えば、ある特定のNamespaceにデプロイされているPodはそのNamespace内の他のPodとしか通信を行わないのに

デフォルトのままだと他のNamespaceと通信できるようにistio proxyに設定が注入されてしまいます。

セキュリティ面や後述するistio proxyのリソースの面でもSidecarリソースを使うことによって通信できる範囲を絞ることができます。

このあたりは実際にデプロイされているistio proxyの設定を確認したほうが見たほうがよさそうです。

しかし、設定をどう見るかについはEnvoyを少し知る必要があったので、少し整理してみたいと思います。

Envoyをざっくり理解する

ここではEnvoyが何者なのかは割愛しますが、istio proxyの設定を見るためにEnvoyで出てくる用語などについて書きます。

主な用語

downstream:Envoyに接続する。 Envoyをプロキシとしたサイドカーアプリケーションがデプロイされている場合

リクエストを送るクライアントがダウンストリームにあたる。 レスポンスを返すサイドカーアプリケーションがダウンストリームにあたる。

upstream : Envoyが受け取ったリクエストを転送する先を指す。 Envoyをプロキシとしたサイドカーアプリケーションがデプロイされている場合

Envoyがクラアントから受け取ったリクエストを転送する先であるサイドカーアプリケーションがアップストリームにあたる。 Envoyがサイドカーアプリケーションから受け取ったレスポンスを転送する先であるクライアントがアップストリームにあたる。

listener : Envoyがダウンストリームからの接続を受け入れるためのIPやポートなどの情報。

cluster : Envoyがアップストリームへ接続するため(転送するため)のエンドポイントのグループ

endpoint : クラスタが持つエンドポイントグループの1つ。クラスタとは親子関係になる。

router: Envoyが受け取ったリクエストをどのクラスタを選択してアップストリームに転送するかを決める経路情報。 HTTPヘッダで転送先を切り替えたり、リクエストのリトライの回数を制御したり、 パーセンテージをベースにしたトラフィックの分割、優先順位ベースのルーティング、、、などルートで設定できることは多岐にわたります。

Envoyがリクエストを受け取る情報はリスナーで管理され、そのリクエストを転送するための情報がクラスタで管理されている形になります。 routerによってリスナーとクラスタが紐づき、ダウンストリームからアップストリームへと転送できるようになります。

参考 :

https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request

EnvoyのxDSについて

Envoyは静的にも、動的にも設定を適用することができますが、ここではIstioが採用している形式に合わせて動的な設定にフォーカスします。

Envoyはファイルシステムによる設定変更やREST APIや、gRPCなど様々な手段で設定を変更することができますが、

特にREST APIやgRPCによる設定変更では、Envoyが管理サーバーに問い合わせて動的に設定を受け取り更新します。

この設定を提供するサービスとAPIをまとめてxDSと呼ばれています。

xには用語で説明したclusterやlistenerがあてはまります。

LDS(Listener Discovery Service) : Listenerの設定を受け取るためのサービス

CDS(Cluster Discovery Servide) : Clusterの設定を受け取るためのサービス

EDS(Endpoint Discovery Service) : Endpointの設定を受け取るためのサービス

RDS(Route Discovery Service) : Routeの設定を受け取るためのサービス

(ADSやSDSは省略しています。)

設定を提供するAPIのサーバーをコントロールプレーンと呼び、問い合せるEnvoyプロキシをデータプレーンと呼びます。

Istioのコントロールプレーン内のPilotというコンポーネントがまさにIstio Proxyの設定を提供していますね。

Istio Proxyの設定を見てみる

Envoyについてざっくりと確認したため実際にIstio Proxyにはどんな設定されているか見てみたいと思います。

例として Namespace foobar にそれぞれdeployment(Pod1台)をapplyしてみます。

(それぞれのNamespaceにデプロイされたPod間での内部通信する要件はないとします。)

Namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: foo
  labels:
    istio-injection: enabled
---
apiVersion: v1
kind: Namespace
metadata:
  name: bar
  labels:
    istio-injection: enabled

Deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-app
  namespace: foo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-app
  template:
    metadata:
      labels:
        app: test-app
    spec:
      containers:
        - name: test-app
          image: test-app
          ports:
            - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-app
  namespace: bar
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-app
  template:
    metadata:
      labels:
        app: test-app
    spec:
      containers:
        - name: test-app
          image: test-app
          ports:
            - containerPort: 80

Service.yaml

apiVersion: v1
kind: Service
metadata:
  name: foo
  namespace: foo
spec:
  selector:
    app: test-app
  ports:
    - port: 80
      targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: bar
  namespace: bar
spec:
  selector:
    app: test-app
  ports:
    - port: 80
      targetPort: 80

これでIngressの設定次第などでは、Port 80で通信できるような構成になりました。

NamespaceとDeploymentをapplyしたので、istio-proxyの設定を確認してみたいと思います。

Istioにはデバッグ目的も含めproxyのステータスを確認するコマンドが用意されています。

% istioctl proxy-status

NAME                                                   CDS        LDS        EDS        RDS          ISTIOD                     VERSION
istio-ingressgateway-7576658c9b-gqrgh.istio-system     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-c85d85ddd-6nfmg     1.8.1
test-app-9dfd8f5-58t4d.foo                             SYNCED     SYNCED     SYNCED     SYNCED       istiod-c85d85ddd-6nfmg     1.8.1
test-app-9dfd8f5-x9m2q.bar                             SYNCED     SYNCED     SYNCED     SYNCED       istiod-c85d85ddd-6nfmg     1.8.1

前述のEnvoyのxDSに沿ってそれぞれのステータスが記載されています。

test-appのistio-proxyはどのサービスディスカバリでも SYNCED になっているためistio-proxyは正常にinjectされているようです。

ちなみにステータスの状態は3種類存在します。

SYNCED : Istiodが最後に送った情報をistio-proxyが受け取り、確認応答を返した状態

NOT SENT : 送信するべき設定がなく、Istiodが何も送ってないことを示す状態

STALE : Istiodが送った設定の確認応答が返ってきていない状態

istio-proxyが正常に動いていることを確認することができたため、各設定も確認していこうと思います。

Listener

 % istioctl proxy-config listener test-app-9dfd8f5-58t4d.foo
ADDRESS      PORT  MATCH                                                               DESTINATION
10.68.0.10   53    ALL                                                                 Cluster: outbound|53||kube-dns.kube-system.svc.cluster.local
0.0.0.0      80    Trans: raw_buffer; App: HTTP                                        Route: 80
0.0.0.0      80    ALL                                                                 PassthroughCluster
10.68.13.187 80    Trans: raw_buffer; App: HTTP                                        Route: bar.bar.svc.cluster.local:80
10.68.13.187 80    ALL                                                                 Cluster: outbound|80||bar.bar.svc.cluster.local
10.68.4.198  80    Trans: raw_buffer; App: HTTP                                        Route: foo.foo.svc.cluster.local:80
10.68.4.198  80    ALL                                                                 Cluster: outbound|80||foo.foo.svc.cluster.local
10.68.0.1    443   ALL                                                                 Cluster: outbound|443||kubernetes.default.svc.cluster.local
10.68.0.60   443   ALL                                                                 Cluster: outbound|443||istio-ingressgateway.istio-system.svc.cluster.local
10.68.10.133 443   ALL                                                                 Cluster: outbound|443||istiod.istio-system.svc.cluster.local
10.68.11.98  443   Trans: raw_buffer; App: HTTP                                        Route: metrics-server.kube-system.svc.cluster.local:443
10.68.11.98  443   ALL                                                                 Cluster: outbound|443||metrics-server.kube-system.svc.cluster.local
0.0.0.0      15001 ALL                                                                 PassthroughCluster
0.0.0.0      15001 Addr: *:15001                                                       Non-HTTP/Non-TCP
0.0.0.0      15006 Addr: *:15006                                                       Non-HTTP/Non-TCP
0.0.0.0      15006 Trans: tls; App: TCP TLS; Addr: 0.0.0.0/0                           InboundPassthroughClusterIpv4
0.0.0.0      15006 Trans: raw_buffer; Addr: 0.0.0.0/0                                  InboundPassthroughClusterIpv4
0.0.0.0      15006 Trans: tls; App: HTTP TLS; Addr: 0.0.0.0/0                          InboundPassthroughClusterIpv4
0.0.0.0      15006 Trans: raw_buffer; App: HTTP; Addr: 0.0.0.0/0                       InboundPassthroughClusterIpv4
0.0.0.0      15006 Trans: tls; App: istio-http/1.0,istio-http/1.1,istio-h2; Addr: *:80 Cluster: inbound|80||
0.0.0.0      15006 Trans: raw_buffer; App: HTTP; Addr: *:80                            Cluster: inbound|80||
0.0.0.0      15006 Trans: tls; App: TCP TLS; Addr: *:80                                Cluster: inbound|80||
0.0.0.0      15006 Trans: raw_buffer; Addr: *:80                                       Cluster: inbound|80||
0.0.0.0      15006 Trans: tls; Addr: *:80                                              Cluster: inbound|80||
0.0.0.0      15010 Trans: raw_buffer; App: HTTP                                        Route: 15010
0.0.0.0      15010 ALL                                                                 PassthroughCluster
10.68.0.60   15012 ALL                                                                 Cluster: outbound|15012||istio-ingressgateway.istio-system.svc.cluster.local
10.68.10.133 15012 ALL                                                                 Cluster: outbound|15012||istiod.istio-system.svc.cluster.local
0.0.0.0      15014 Trans: raw_buffer; App: HTTP                                        Route: 15014
0.0.0.0      15014 ALL                                                                 PassthroughCluster
0.0.0.0      15021 ALL                                                                 Inline Route: /healthz/ready*
10.68.0.60   15021 Trans: raw_buffer; App: HTTP                                        Route: istio-ingressgateway.istio-system.svc.cluster.local:15021
10.68.0.60   15021 ALL                                                                 Cluster: outbound|15021||istio-ingressgateway.istio-system.svc.cluster.local
0.0.0.0      15090 ALL                                                                 Inline Route: /stats/prometheus*
10.68.0.60   15443 ALL                                                                 Cluster: outbound|15443||istio-ingressgateway.istio-system.svc.cluster.local

routes

% istioctl proxy-config routes test-app-9dfd8f5-58t4d.foo
NOTE: This output only contains routes loaded via RDS.
NAME                                                          DOMAINS                               MATCH                  VIRTUAL SERVICE
80                                                            bar.bar                               /*
80                                                            default-http-backend.kube-system      /*
80                                                            foo                                   /*
80                                                            istio-ingressgateway.istio-system     /*
15010                                                         istiod.istio-system                   /*
15014                                                         istiod.istio-system                   /*
istio-ingressgateway.istio-system.svc.cluster.local:15021     istio-ingressgateway.istio-system     /*
metrics-server.kube-system.svc.cluster.local:443              metrics-server.kube-system            /*
foo.foo.svc.cluster.local:80                                  foo                                   /*
bar.bar.svc.cluster.local:80                                  bar.bar                               /*
                                                              *                                     /stats/prometheus*
InboundPassthroughClusterIpv4                                 *                                     /*
InboundPassthroughClusterIpv4                                 *                                     /*
inbound|80||                                                  *                                     /*
                                                              *                                     /healthz/ready*
inbound|80||                                                  *                                     /*

cluster

% istioctl proxy-config cluster test-app-9dfd8f5-58t4d.foo
SERVICE FQDN                                            PORT      SUBSET     DIRECTION     TYPE             DESTINATION RULE
                                                        80        -          inbound       STATIC
BlackHoleCluster                                        -         -          -             STATIC
InboundPassthroughClusterIpv4                           -         -          -             ORIGINAL_DST
PassthroughCluster                                      -         -          -             ORIGINAL_DST
agent                                                   -         -          -             STATIC
bar.bar.svc.cluster.local                               80        -          outbound      EDS
default-http-backend.kube-system.svc.cluster.local      80        -          outbound      EDS
foo.foo.svc.cluster.local                               80        -          outbound      EDS
istio-ingressgateway.istio-system.svc.cluster.local     80        -          outbound      EDS
istio-ingressgateway.istio-system.svc.cluster.local     443       -          outbound      EDS
istio-ingressgateway.istio-system.svc.cluster.local     15012     -          outbound      EDS
istio-ingressgateway.istio-system.svc.cluster.local     15021     -          outbound      EDS
istio-ingressgateway.istio-system.svc.cluster.local     15443     -          outbound      EDS
istiod.istio-system.svc.cluster.local                   443       -          outbound      EDS
istiod.istio-system.svc.cluster.local                   15010     -          outbound      EDS
istiod.istio-system.svc.cluster.local                   15012     -          outbound      EDS
istiod.istio-system.svc.cluster.local                   15014     -          outbound      EDS
kube-dns.kube-system.svc.cluster.local                  53        -          outbound      EDS
kubernetes.default.svc.cluster.local                    443       -          outbound      EDS
metrics-server.kube-system.svc.cluster.local            443       -          outbound      EDS
prometheus_stats                                        -         -          -             STATIC
sds-grpc                                                -         -          -             STATIC
xds-grpc                                                -         -          -             STATIC
zipkin                                                  -         -          -             STRICT_DNS

endpoints

% istioctl proxy-config endpoints test-app-9dfd8f5-58t4d.foo
ENDPOINT                         STATUS      OUTLIER CHECK     CLUSTER
10.64.0.10:80                    HEALTHY     OK                outbound|80||foo.foo.svc.cluster.local
10.64.0.4:53                     HEALTHY     OK                outbound|53||kube-dns.kube-system.svc.cluster.local
10.64.0.8:443                    HEALTHY     OK                outbound|443||metrics-server.kube-system.svc.cluster.local
10.64.0.9:8080                   HEALTHY     OK                outbound|80||default-http-backend.kube-system.svc.cluster.local
10.64.1.3:53                     HEALTHY     OK                outbound|53||kube-dns.kube-system.svc.cluster.local
10.64.1.4:8080                   HEALTHY     OK                outbound|80||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:8443                   HEALTHY     OK                outbound|443||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15012                  HEALTHY     OK                outbound|15012||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15021                  HEALTHY     OK                outbound|15021||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15443                  HEALTHY     OK                outbound|15443||istio-ingressgateway.istio-system.svc.cluster.local
10.64.2.2:15010                  HEALTHY     OK                outbound|15010||istiod.istio-system.svc.cluster.local
10.64.2.2:15012                  HEALTHY     OK                outbound|15012||istiod.istio-system.svc.cluster.local
10.64.2.2:15014                  HEALTHY     OK                outbound|15014||istiod.istio-system.svc.cluster.local
10.64.2.2:15017                  HEALTHY     OK                outbound|443||istiod.istio-system.svc.cluster.local
10.64.2.3:80                     HEALTHY     OK                outbound|80||bar.bar.svc.cluster.local
127.0.0.1:80                     HEALTHY     OK                inbound|80||
127.0.0.1:15000                  HEALTHY     OK                prometheus_stats
127.0.0.1:15020                  HEALTHY     OK                agent
35.189.153.189:443               HEALTHY     OK                outbound|443||kubernetes.default.svc.cluster.local
unix://./etc/istio/proxy/SDS     HEALTHY     OK                sds-grpc
unix://./etc/istio/proxy/XDS     HEALTHY     OK                xds-grpc

どの設定もたくさんあります。

ちょっと気になるのがfooのPodはbarのPodとは通信する予定はないのに、通信できるような設定があります。

冒頭に記載しているようにデフォルトではサービスメッシュ内のすべてのワークロードを通信できるような設定が適用されてしまいます。

本来であればbarのPodとは疎通できなくても問題ないため上記のbarに関する設定は消したいところです。

消すためにはSidecarリソースを使用します。

やっと本題であるSidecarリソースを使っていきたいと思います。

Sidecarリソースを使ってみる

ドキュメントを参考にSidecarリソースをapplyしてみようと思います。

今回はメッシュ内の、istio-systemと疎通できるような設定にしてみます。

Sidecar.yaml

apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
  name: foo
  namespace: foo
spec:
  workloadSelector:
    labels:
      app: test-app
  ingress:
  - port:
      number: 80
      protocol: HTTP
    defaultEndpoint: 127.0.0.1:80
  egress:
  - hosts:
    - "istio-system/*"
---
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
  name: bar
  namespace: bar
spec:
  workloadSelector:
    labels:
      app: test-app
  ingress:
  - port:
      number: 80
      protocol: HTTP
    defaultEndpoint: 127.0.0.1:80
  egress:
  - hosts:
    - "istio-system/*"

適用後のendpointsの設定を確認してみます。

% istioctl proxy-config endpoints test-app-9dfd8f5-58t4d.foo
ENDPOINT                         STATUS      OUTLIER CHECK     CLUSTER
10.64.1.4:8080                   HEALTHY     OK                outbound|80||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:8443                   HEALTHY     OK                outbound|443||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15012                  HEALTHY     OK                outbound|15012||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15021                  HEALTHY     OK                outbound|15021||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15443                  HEALTHY     OK                outbound|15443||istio-ingressgateway.istio-system.svc.cluster.local
10.64.2.2:15010                  HEALTHY     OK                outbound|15010||istiod.istio-system.svc.cluster.local
10.64.2.2:15012                  HEALTHY     OK                outbound|15012||istiod.istio-system.svc.cluster.local
10.64.2.2:15014                  HEALTHY     OK                outbound|15014||istiod.istio-system.svc.cluster.local
10.64.2.2:15017                  HEALTHY     OK                outbound|443||istiod.istio-system.svc.cluster.local
127.0.0.1:80                     HEALTHY     OK                inbound|80||
127.0.0.1:15000                  HEALTHY     OK                prometheus_stats
127.0.0.1:15020                  HEALTHY     OK                agent
unix://./etc/istio/proxy/SDS     HEALTHY     OK                sds-grpc
unix://./etc/istio/proxy/XDS     HEALTHY     OK                xds-grpc

先ほどまで存在していたbarへの通信の設定がなくなりました。

Sidecarリソースを使うことで設定を必要以上に持たなくて済みました。

Sidecarリソースをもう少し理解したい

先ほどのSidecarリソースを使ってみた例ではいまいち恩恵を受けることができているのか、いないのかという印象は薄かったかもしれません。

しかし、istio-proxyのリソース使用量などパフォーマンスの側面で見るとSidecarリソースを使用することはとても大切になるようです。

大規模なシステムでistio-proxyが必要以上に設定を持ってしまうとCPUやメモリの消費も増えてしまいます。

issueなどにもVitualServiceリソースが増加するごとにメモリの使用量が増加した事例などがありました。

自身もどれくらいの負荷の増減が発生するのか検証してみました。

今回はVirtualServiceやリクエストによる負荷ではなく、単純にSidecarリソースを使用せずにNamespaceとPodを増やしてみてリソースの使用量に差異が見られるのか検証してみました。

Pod数を初回6台くらいから徐々に増やしていき最終的に120台まで増やしてみました。120台まで増やした後にSidecarリソースを適用してみました。

以下がその際のメモリの使用量の推移です。

f:id:jksdaba:20210329010836p:plain

全体として見ると増減は見られますが、5MB~10MBほどの増減でした。

時刻で見ると19:00頃から少しずつ使用量が増加しています。

19:00頃の Podの台数は50~60台ほどでした。そこから120台まで徐々に増加していったことが確認できました。

2:00前くらいから使用量が減少していますが、このときにSidecarリソースを適用しました。

一応メモリ使用量の変化を確認することができましたが、増減の値が小さかったり、、と少し検証内容は不十分だったのでは?と考えています。

絶えずリクエストを送ったり、VirtualServiceを使用して、サーキットブレーカーや割合によるリクエスト制御など様々な機能を利用した複雑なProxy設定を行った上でメトリクスを確認したら、より違った効果が見られたかもしれません。

Podを増やしていく上で、追加されたPodのistio-proxy、既に存在しているPodのistio-proxyどちらにも設定の更新が発生するためControl PlaneのIstiodにも負荷がかかってしまうということもわかりました。このあたりはPodの立ち上がりに影響が出てしまうかもしれないため別途検証してみたほうがよさそうです。

最後に

今回はなんとなく、Sidecarリソースを理解することができましたが、パフォーマンスに関する領域についてはもう少し確認したほうがいいことや、不十分なことが多く見られました。

また、時間を取って今回以上の検証ができればと思います。

もしくは、とても詳しい方がいたら教えてほしいですし、より詳しい記事があったら教えてほしいです。

参考

https://github.com/istio/istio/issues/19925

https://banzaicloud.com/blog/istio-sidecar/

Everything We Learned Running Istio In Production — Part 2 | by Craig Huber | HelloTech

envoyproxy - Reducing memory usage by ISTIO side car - Stack Overflow

Istio / Announcing Istio 1.1

https://linkerd.io/2020/12/03/why-linkerd-doesnt-use-envoy/

Potential memory leak on istio-proxy sidecar · Issue #29505 · istio/istio · GitHub

https://istio.io/latest/docs/concepts/traffic-management/#sidecars

High memory utilization of istio-proxy proportional to number of services · Issue #7912 · istio/istio · GitHub

https://tech.bigbasket.com/bigbaskets-experience-with-istio/

High memory usage with long HTTP/2 connections (GRPC) · Issue #9891 · envoyproxy/envoy · GitHub

Sidecar uses a large amount of memory · Issue #9367 · istio/istio · GitHub

https://github.com/istio/istio.io/issues/7700

https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request

kube-dnsとDNS resolveのことで気になったこと

個人的に気になる記事を発見したので、読んだり、試してみたのでそのメモなど。

気になった記事

NodeLocal DNSCacheを導入するまでの過程. この記事は ミクシィグループ Advent Calendar… | by Naohiro Kurihara | mixi developers | Dec, 2020 | Medium

Karan Sharma | DNS Lookups in Kubernetes

kube-dnsがどのようにクエリをさばいているか確認してみる

環境

GKE 1.16.15-gke.6000

どんなクエリが生成されるか見てみる

通信先のserviceやdeployment

apiVersion: v1
kind: Service
metadata:
  name: hoge-go
  namespace: hoge
spec:
  selector:
    app: hoge-go
  ports:
    - port: 80
      targetPort: 80

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hoge-go
  namespace: hoge
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hoge-go
  template:
    metadata:
      labels:
        app: hoge-go
    spec:
      containers:
        - name: hoge-go
          image: hoge-go
          resources:
            limits:
              memory: "50Mi"
              cpu: "50m"
          ports:
            - containerPort: 80

通信するクライアント用のpod生成

kubectl run multitool --image=praqma/network-multitool --replicas=1

tcpdumpでどれくらいクエリが発行されるか見てみる

  1. nslookup hoge-go.hoge
bash-5.0# tcpdump -n dst port 53


10:40:14.158155 IP 10.0.1.10.48385 > 10.4.0.10.53: 54428+ A? hoge-go.hoge.default.svc.cluster.local. (56)
10:40:14.159412 IP 10.0.1.10.41017 > 10.4.0.10.53: 35215+ A? hoge-go.hoge.svc.cluster.local. (48)
10:40:14.161212 IP 10.0.1.10.47116 > 10.4.0.10.53: 40310+ AAAA? hoge-go.hoge.svc.cluster.local. (48)
  1. nslookup hoge-go.hoge.
10:42:21.884964 IP 10.0.1.10.58678 > 10.4.0.10.53: 8623+ A? hoge-go.hoge. (30)

※この場合は解決できない

  1. nslookup hoge-go.hoge.svc.cluster.local

10:42:59.527023 IP 10.0.1.10.44223 > 10.4.0.10.53: 58441+ A? hoge-go.hoge.svc.cluster.local.default.svc.cluster.local. (74)
10:42:59.529878 IP 10.0.1.10.59164 > 10.4.0.10.53: 37994+ A? hoge-go.hoge.svc.cluster.local.svc.cluster.local. (66)
10:42:59.532175 IP 10.0.1.10.60062 > 10.4.0.10.53: 37258+ A? hoge-go.hoge.svc.cluster.local.cluster.local. (62)
10:42:59.533090 IP 10.0.1.10.40975 > 10.4.0.10.53: 57453+ A? hoge-go.hoge.svc.cluster.local.asia-northeast1-a.c.test.internal. (98)
10:42:59.536537 IP 10.0.1.10.45882 > 10.4.0.10.53: 20638+ A? hoge-go.hoge.svc.cluster.local.c.test.internal. (80)
10:42:59.539721 IP 10.0.1.10.57718 > 10.4.0.10.53: 39085+ A? hoge-go.hoge.svc.cluster.local.google.internal. (64)
10:42:59.542635 IP 10.0.1.10.48161 > 10.4.0.10.53: 49588+ A? hoge-go.hoge.svc.cluster.local. (48)
10:42:59.544235 IP 10.0.1.10.42390 > 10.4.0.10.53: 37242+ AAAA? hoge-go.hoge.svc.cluster.local. (48)
  1. nslookup hoge-go.hoge.svc.cluster.local.
10:45:12.725623 IP 10.0.1.10.51546 > 10.4.0.10.53: 49162+ A? hoge-go.hoge.svc.cluster.local. (48)
10:45:12.727908 IP 10.0.1.10.41173 > 10.4.0.10.53: 49705+ AAAA? hoge-go.hoge.svc.cluster.local. (48)

AAAAレコードをいったん抜きにしてみてみると最後の . まで加えたFQDNでの名前解決は発行数が少ない。

FQDNを使用していない場合は発行数が多い(1と3の発行数が異なる理由は後述)

クライアント用のpodのresolv.confを確認してみる

bash-5.0# cat /etc/resolv.conf

nameserver 10.4.0.10
search default.svc.cluster.local svc.cluster.local cluster.local asia-northeast1-a.c.test.internal c.test.internal google.internal
options ndots:5

nameserverはkube-dnsのservice

% kubectl get svc -n kube-system | grep kube-dns

kube-dns               ClusterIP   10.4.0.10    <none>        53/UDP,53/TCP   16d

ndotsは5に設定されている。

そのため前述の1~3までの問い合わせはドット数が5より少ないため searchで設定されている検索リストを順番に試すことになる。

1と3がクエリの発行数が違うのは1の場合、searchの左から2番目( svc.cluster.local )を試して成功したから少ないということだった。

3番目のクエリはsearch全て試して解決できなく、最後のFQDNで解決できるまでにたくさんのクエリが発行されてしまった。

もしかしたらDNSで障害が発生する可能性があるかもしれない

例えば、マイクロサービスでたくさんの内部通信が発生していて

気づかぬうちに大量のDNS解決のクエリが発行されていたらDNSによる障害が起きるかもしれない。

検証はしていないが、こちらの記事に障害が発生したことや、クエリ量を少なくする対応を行ったことによってトラフィック量が減少したことを示されている。

Kubernetes pods /etc/resolv.conf ndots:5 option and why it may negatively affect your application performances

クエリ量を減少させるための対応方法について考えてみる

これらがあると思う。

  1. 内部通信先のエンドポイント設定をFQDNにする
  2. dnsConfigを用いる

1. 内部通信先のエンドポイント設定をFQDNにする

これはあまりオススメはされていない。(たしかに「FQDNまで設定されているね。」と確認したくない。。。)

下記のコメントにはndotsがデフォルト5で設定されているの理由やsearchのリストについての背景などが記載されていたりしている。

Kube-dns add-on should accept option ndots for SkyDNS or document ConfigMap alternative subPath · Issue #33554 · kubernetes/kubernetes · GitHub

2. dnsConfigを用いる

dnsConfigを用いることでPodのresolv.confをカスタマイズすることができる。

この機能はkubernetesのversion 1.14からはstableになっている。

https://kubernetes.io/ja/docs/concepts/services-networking/dns-pod-service/#pod-dns-config

ドキュメントの例を見てもsearchやndotsなど様々な設定ができることがわかる。

最後に

2つの方法で対応できると記載したが、GKEにはNodeLocalDNSCacheというものが提供されていた。

これもkube-dnsの負荷を軽減してくれそうな仕組みなので、今度動かしてみてみたいと思う。

cloud.google.com

午前4時に障害対応をやらないために

注意

この記事はタイトルに対して汎用的に使用できるものではありません。

GCPのconfig connectorに関する記事です。

本文に多く「リソース」という言葉が出てきますが、これはGCPのconpute engine, gcs, bigquery, gke, memory store...など様々なサービスを想定して書いています。

config connectorはとても便利ではあるが、、

cloud.google.com

個人的にはそう信じています!

GKE上からyamlを定義してapplyするだけで、今までconsoleから作成していたリソースを簡単に作成することができます。

ということは、gitでソースコード同様に管理することができます。

これは嬉しいですよね?

これによって、GCPで使用しているサービスに依存してやりにくかったテストなど、開発のプロセスで少し難しく感じているプロセスを解消することができそうです。

ただ、注意も必要だということも私は、障害を起こしてしまったことで学びました。

先ほども記載したように「簡単に作成することができる」ということは、一方で簡単に削除もできてしまいます。

この特徴をちゃんと理解していないと本当に危険な目に遭います。

本番環境で運用しているBigQueryなどをデータごと吹っ飛ばしてしまうかもしれません。

何を起こしてしまったか

今の職場でconfig connectorを使用できないかということで、検証していました。

その中でプロジェクトのIAMを吹っ飛ばしてしまいました。

サービスアカウントを作成して、必要な権限を与えるためのyamlを定義してapplyしました。

コンソールを確認した時、目を疑いました。

全てのIAMがなくなっているのです。

目を疑いましたが、夢ではありませんでした。

既に運用しているアプリケーションに割り当てているサービスアカウントの権限だけでなく、

各サービスのデフォルトのサービスアカウントやサービスエージェントのIAMがなくなってしまっていました。

数分後には、使用しているサービスがアラートをあげ始める事態となりました。

原因

この2つを理解していなかったことが原因でした。

  • config connectorの適用スコープと適用時の動作
  • config connector独自の削除操作に関するアノテーション

config connectorの適用スコープと適用時の動作

cloud.google.com

全てのリソースではないですが、一部のリソースではプロジェクト全体で上書きされる形で適用されてしまいます。

私はIAM Policyを使用してIAMを全て吹っ飛ばしてしまいました。

IAMPolicy  |  Config Connector Documentation  |  Google Cloud

このexampleのコメントに以下のように記載されています。

    # **WARNING**: The bindings here represent the full declarative intent for the project.
    # It will fully overwrite the existing policy on the given project.
    #
    # For finer-grained control over the project's IAM policy, it is recommended
    # that the IAMPolicyMember resource be used instead.
    #
    # This sample assumes the following additional APIs are enabled:
    #   - compute.googleapis.com
    #   - container.googleapis.com
    #   - containerregistry.googleapis.com
    #   - redis.googleapis.com
    #
    # Replace ${PROJECT_ID?}, ${PROJECT_NUMBER?}, and ${PROJECT_NAME?} with your desired project ID,
    # that project's project number, and your Google Cloud account email respectively.

これを知らずに権限を与えたいサービスアカウントだけ権限を付与するようなyamlを定義して適用させたため、

上書きされてしまい、今まで存在していたサービスアカウント(各サービスのサービスアカウントやサービスジェントも含む)がすべてなくなってしまいました。

ただこのように動作するのはIAM Policyだけのようです。使用する際はドキュメントに記載されているexampleのyamlまでしっかり読んでから動かしてみることを強くお勧めします。

config connector独自の削除操作に関するアノテーション

これは、障害を起こしてしまった際に直接的に作用したものではないですが、その時ちゃんと理解しておらず、もしかしたら他のリソースを動かした時に

このアノテーションによる障害も起こしてしまっていたかもしれないと思い、記載しました。

cloud.google.com

config connectorは新しくリソースを作成することはもちろんですが、既に作成、運用されているリソースに対してconfig connectorの機能によって

インポートして紐づけることもできます。

既存の Google Cloud リソースのインポート  |  Config Connector のドキュメント

作成や紐付けたリソースを削除した場合、

もちろんk8sクラスタのリソースから削除されることはもちろんですが、GCP上のリソースからも削除されてしまいます。

本当に必要ないのであればGCP上のリソースから削除されてしまっても問題ないのですが、ケースによってはconfig connectorの紐付けだけ外して

リソース自体は残しておきたい場面があるかもしれません。(リソースに消えてはまずいデータが保管されているのであれば尚更です)

ドキュメントにも赤く警告文として記載されていますが

...
metadata:
  annotations:
    cnrm.cloud.google.com/deletion-policy: abandon
...

このアノテーションを付けることで、リソース自体を残すことができます。

障害を起こしてしまった時の対応

これは結構特例ですが、

サービスエージェントが失われてしまい、ほとんどのサービスのAPIを使用することができなくなってしまいました。

この消えてしまったサービスエージェントたちは、手作業で復旧しました。

手作業で同じようなIAMを復活させて本当に障害以前のように安定稼働するのか不安でしたが、問題なく動きました。 (後にサポートの方に聞き、状態など確認してもらいましたが問題なく動いているとの連絡をもらいました)

最後に

使用上の注意を正しく読んでお使いください。

以上夏の思い出でした。

今更RBAC

タイトルの通り、いまさらkubernetesのRBACをいじってみた。

Using RBAC Authorization - Kubernetes

ロールベースのアクセス制御  |  Kubernetes Engine ドキュメント  |  Google Cloud

KubernetesのRBACについて - Qiita

Kubernetes道場 20日目 - Role / RoleBinding / ClusterRole / ClusterRoleBindingについて - Toku's Blog

「RBACとは」的な説明は省略しているので、そのようなことを知りたい方は上記のリンクなどに記載している。

いじる前の準備(ベースの環境作成)

namespace

apiVersion: v1
kind: Namespace
metadata:
  name: wakashiyo

service account

apiVersion: v1
kind: ServiceAccount
metadata:
  name: sample
  namespace: wakashiyo

検証用Pod

pod

apiVersion: v1
kind: Pod
metadata:
  name: sample-kubectl
  namespace: wakashiyo
spec:
  serviceAccountName: sample
  containers:
    - name: kubectl-container
      image: lachlanevenson/k8s-kubectl:latest
      command: ["sleep", "86400"]

1. 検証用のPodからkubectlコマンドを使用してPodを作成してみる

この段階では何もRoleは設定されていない

作成してみる

kubectl exec -it sample-kubectl -n wakashiyo -- kubectl run nginx --image=nginx:latest

Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot create resource "pods" in API group "" in the namespace "wakashiyo"
command terminated with exit code 1

作ることができなかった

Podのリソースを作成することが禁じられていると言われてしまったので、Roleを付与してみる

role

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-role
  namespace: wakashiyo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create"]

role binding

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: sample-role-binding
  namespace: wakashiyo
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: sample-role
subjects:
  - kind: ServiceAccount
    name: sample
    namespace: wakashiyo

apply する

kubectl apply -f role.yml
kubectl apply -f rolebinding.yml

再度作成し直してみる

kubectl exec -it sample-kubectl -n wakashiyo -- kubectl run nginx --image=nginx:latest

pod/nginx created


kubectl get pods -n wakashiyo -o wide

NAME             READY   STATUS    RESTARTS   AGE     IP           NODE                                       NOMINATED NODE   READINESS GATES
nginx            1/1     Running   0          10s     10.48.1.12   gke-wakashiyo-default-pool-29ce2b10-b5gc   <none>           <none>
sample-kubectl   1/1     Running   0          9m55s   10.48.0.10   gke-wakashiyo-default-pool-29ce2b10-gx26   <none>           <none>

作成できた

2. 検証用のPodから get pods したりしてみる

kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods -n wakashiyo

Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot list resource "pods" in API group "" in the namespace "wakashiyo"
command terminated with exit code 1



kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods -n wakashiyo

Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot list resource "pods" in API group "" in the namespace "wakashiyo"
command terminated with exit code 1



kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods nginx -n wakashiyo

Error from server (Forbidden): pods "nginx" is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot get resource "pods" in API group "" in the namespace "wakashiyo"
command terminated with exit code 1



kubectl exec -it sample-kubectl -n wakashiyo -- kubectl describe pods nginx -n wakashiyo

Error from server (Forbidden): pods "nginx" is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot get resource "pods" in API group "" in the namespace "wakashiyo"
command terminated with exit code 1

できない。

特定のPodを指定してdescribeしたり、getしたりするためにはgetの操作権限、

pod一覧を出したりするためにはlistの操作権限が必要そう。

roleを変更してみる

role

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-role
  namespace: wakashiyo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "get", "list"] # get, listを追加

再度getしたりしてみる

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods -n wakashiyo

NAME             READY   STATUS    RESTARTS   AGE
nginx            1/1     Running   0          8m5s
sample-kubectl   1/1     Running   0          17m



% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods nginx -n wakashiyo

NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          8m13s



% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl describe pods nginx -n wakashiyo
Name:         nginx
Namespace:    wakashiyo
Priority:     0
... 以下省略

getできた。

ちなみに今まで設定していたのはClusterRoleではなく、Roleだったので、 --all-namespaces でのget操作はできない。

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods --all-namespaces

Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot list resource "pods" in API group "" at the cluster scope
command terminated with exit code 1

cluster roleを追加する

cluster role

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-cluster-role
  namespace: wakashiyo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list"]

cluster role binging

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: sample-cluster-role-binding
  namespace: wakashiyo
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: sample-cluster-role
subjects:
  - kind: ServiceAccount
    name: sample
    namespace: wakashiyo

applyして再度 --all-namespace でgetしてみる

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods --all-namespaces

NAMESPACE     NAME                                                        READY   STATUS    RESTARTS   AGE
kube-system   event-exporter-v0.2.5-599d65f456-jn2t4                      2/2     Running   0          4h
kube-system   fluentd-gcp-scaler-bfd6cf8dd-cj6m5                          1/1     Running   0          4h
kube-system   fluentd-gcp-v3.1.1-jml9r                                    2/2     Running   0          3h59m
kube-system   fluentd-gcp-v3.1.1-mvhhf                                    2/2     Running   0          3h59m
kube-system   heapster-gke-ddccfc6d-xtgkv                                 3/3     Running   0          3h59m
kube-system   kube-dns-5995c95f64-dclwz                                   4/4     Running   0          4h
kube-system   kube-dns-5995c95f64-qh26s                                   4/4     Running   0          4h
kube-system   kube-dns-autoscaler-8687c64fc-xsh7x                         1/1     Running   0          4h
kube-system   kube-proxy-gke-wakashiyo-default-pool-29ce2b10-b5gc         1/1     Running   0          4h
kube-system   kube-proxy-gke-wakashiyo-default-pool-29ce2b10-gx26         1/1     Running   0          4h
kube-system   l7-default-backend-8f479dd9-r2xgs                           1/1     Running   0          4h
kube-system   metrics-server-v0.3.1-5c6fbf777-26kst                       2/2     Running   0          3h59m
kube-system   prometheus-to-sd-4bkqh                                      2/2     Running   0          4h
kube-system   prometheus-to-sd-lhb4t                                      2/2     Running   0          4h
kube-system   stackdriver-metadata-agent-cluster-level-6f6775c594-qmcx7   2/2     Running   0          3h59m
wakashiyo     nginx                                                       1/1     Running   0          14m
wakashiyo     sample-kubectl                                              1/1     Running   0          24m

できた。

3. 検証用のPodから作成したnginxのPodを削除してみる

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl delete pods nginx -n wakashiyo

Error from server (Forbidden): pods "nginx" is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot delete resource "pods" in API group "" in the namespace "wakashiyo"
command terminated with exit code 1

当然deleteの権限がないのでできない。

roleを変更する

role

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-role
  namespace: wakashiyo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "get", "list", "delete"] # deleteを追加

applyして再度delete podを実行してみる

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl delete pods nginx -n wakashiyo

pod "nginx" deleted




% kubectl get pods -n wakashiyo

NAME             READY   STATUS    RESTARTS   AGE
sample-kubectl   1/1     Running   0          27m

削除できた

4. podをスケールさせてみる

% kubectl get pods -n wakashiyo

NAME                    READY   STATUS    RESTARTS   AGE
nginx-cd9dcdd6c-jwnvn   1/1     Running   0          78s
sample-kubectl          1/1     Running   0          55m




% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl scale deploy nginx --replicas 2

Error from server (Forbidden): deployments.extensions "nginx" is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot get resource "deployments" in API group "extensions" in the namespace "wakashiyo"
command terminated with exit code 1

できない。

roleに権限を追加する

role

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-role
  namespace: wakashiyo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "get", "list", "delete"]
  - apiGroups: ["apps", "extensions"]
    resources: ["deployments"]
    verbs: ["get"] # getを追加

再度スケールさせてみる

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl scale rs nginx --replicas 2

Error from server (Forbidden): deployments.extensions "nginx" is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot patch resource "deployments/scale" in API group "extensions" in the namespace "wakashiyo"
command terminated with exit code 1

まだできない

再度roleを変更する

role

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-role
  namespace: wakashiyo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "get", "list", "delete"]
  - apiGroups: ["apps", "extensions"]
    resources: ["deployments"]
    verbs: ["get"]
  # 以下を追加
  - apiGroups: ["apps", "extensions"]
    resources: ["deployments/scale"]
    verbs: ["patch"]

再度スケールさせてみる

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl scale deploy nginx --replicas 2

deployment.extensions/nginx scaled




% kubectl get pods -n wakashiyo
NAME                    READY   STATUS    RESTARTS   AGE
nginx-cd9dcdd6c-jwnvn   1/1     Running   0          4m27s
nginx-cd9dcdd6c-zs7f9   1/1     Running   0          7s
sample-kubectl          1/1     Running   0          58m

やっとできた。

スケールさせるには deploymentsではなく、 deployments/scale というリソースに対する権限が必要だった。

その他

RoleBindingはRoleを1つしか設定することができない。

role binding

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: sample-role-binding
  namespace: wakashiyo
roleRef:
  apiGroup: rbac.authorization.k8s.io
  - kind: Role
    name: sample-role
  - kind: Role # 2つ目のrole
    name: sample-role2
subjects:
  - kind: ServiceAccount
    name: sample
    namespace: wakashiyo
% kubectl apply -f rolebinding.yml
error: error parsing rolebinding.yml: error converting YAML to JSON: yaml: line 7: did not find expected key

※ エディタで書いている段階でエラー出る

RoleBindingで紐付けるservice accountは複数設定できる

role binding

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: sample-role-binding
  namespace: wakashiyo
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: sample-role
subjects: # 複数設定している
  - kind: ServiceAccount
    name: sample
    namespace: wakashiyo
  - kind: ServiceAccount
    name: sample2
    namespace: wakashiyo

1つのRoleで同じリソースに対してのルールを複数設定することができる

role

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-role
  namespace: wakashiyo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "delete"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list"]

上記で設定した操作ができる。

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl run nginx --image=nginx
pod/nginx created



% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods nginx -n wakashiyo
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          19s



% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl delete pods nginx -n wakashiyo
pod "nginx" deleted

cluster roleに限ってはaggregationできる。

cluster role 1

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-cluster-role
  labels:
    app: sample-role
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list"]

cluster role 2

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-cluster-role2
  labels:
    app: sample-role
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["delete"]

cluster role 3

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-cluster-role3
  labels:
    app: sample-role
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create"]

aggregated cluster role

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-aggregated-cluster-role
aggregationRule:
  clusterRoleSelectors:
    - matchLabels:
        app: sample-role

aggregateされている

% kubectl get clusterrole sample-aggregated-cluster-role -o yaml
aggregationRule:
  clusterRoleSelectors:
  - matchLabels:
      app: sample-role
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"aggregationRule":{"clusterRoleSelectors":[{"matchLabels":{"app":"sample-role"}}]},"apiVersion":"rbac.authorization.k8s.io/v1beta1","kind":"ClusterRole","metadata":{"annotations":{},"name":"sample-aggregated-cluster-role"}}
  creationTimestamp: "2020-05-05T12:24:26Z"
  name: sample-aggregated-cluster-role
  resourceVersion: "76217"
  selfLink: /apis/rbac.authorization.k8s.io/v1/clusterroles/sample-aggregated-cluster-role
  uid: 5c5491eb-8ecb-11ea-a4d9-42010a9200fb
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - get
  - list
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - delete
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - create

実際にcreate, get, deleteできる

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl run nginx --image=nginx
pod/nginx created



% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods -n wakashiyo
NAME             READY   STATUS    RESTARTS   AGE
nginx            1/1     Running   0          17s
sample-kubectl   1/1     Running   0          102m




% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods nginx -n wakashiyo
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          26s




% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl delete pods nginx -n wakashiyo
pod "nginx" deleted

おしまい。

GraphQLの認証と認可について

GraphQLの認証と認可についてどうなっているのか知りたくなったので、調べてみた。

基本的にgraphql-rubyをベースに書いてます。

認証(Authentification)について

GraphQL - Overview

ここに記載されていた。

In general, authentication is not addressed in GraphQL at all.

残念ながら、認証の機構については全く用意されていなかった。

そのため認証については個別で実装したり、他のサードパーティのライブラリやSaaSなどを使用して導入するしかないっぽい。

ドキュメントには以下のように記載されていた。

Instead, your controller should get the current user based on the HTTP request (eg, an HTTP header or a cookie) and provide that information to the GraphQL query.

代わりにHTTPリクエストのCookieなり、ヘッダーなりにユーザーの情報を含めてGraphQLのやり取りすることが必要だと言っている。

実際、graphql-rubyをインストールした際に生成されるcontrollerはこのようになっている。

class GraphqlController < ApplicationController
  # If accessing from outside this domain, nullify the session
  # This allows for outside API access while preventing CSRF attacks,
  # but you'll have to authenticate your user separately
  # protect_from_forgery with: :null_session

  def execute
    variables = ensure_hash(params[:variables])
    query = params[:query]
    operation_name = params[:operationName]
    context = {
      # Query context goes here, for example:
      # current_user: current_user <= ここがコメントアウトされている
    }
    result = AppSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
    render json: result
  rescue StandardError => e
    raise e unless Rails.env.development?

    handle_error_in_development e
  end

  private

  # Handle form data, JSON body, or a blank value
  def ensure_hash(ambiguous_param)
    case ambiguous_param
    when String
      if ambiguous_param.present?
        ensure_hash(JSON.parse(ambiguous_param))
      else
        {}
      end
    when Hash, ActionController::Parameters
      ambiguous_param
    when nil
      {}
    else
      raise ArgumentError, "Unexpected parameter: #{ambiguous_param}"
    end
  end

  def handle_error_in_development(e)
    logger.error e.message
    logger.error e.backtrace.join("\n")

    render json: { error: { message: e.message, backtrace: e.backtrace }, data: {} }, status: 500
  end
end

contextの部分にcurrent_userというものが用意されている。

実装者がこの部分を何らかの形でユーザー情報を取得し、current_userに含めれば、その後のQueryやMutationでcurrent_userにアクセスして 認可的な処理などを実装することができる。

それで認証は?

先ほどの説明から認証の機構は用意されていないので、自前で実装するしかない。

全てを1からやるのは面倒なので、railsの認証のライブラリで有名なDeviseを使用してサンプル実装をやってみた。

使用したgem

  • devise
  • devise-token_authenticatable

deviseの基本的な部分はをインストールすれば、モデルなど自動的に作成してくれるので特にやることはない。

先ほどの GraphqlController のcontextのcurrent_userのコメントアウトを外せば使用できるようになり、

デバッグしてみてもリクエストを送信したユーザーの情報が含まれていることがわかると思う。

(リクエストにはユーザー作成時に発行されたトークンをリクエストの Authorization: Bearer <発行されたトークン> にセットすることでユーザーが誰であるのかをサーバー側で把握することができる。)

認可について

graphql-rubyには3つの認可フレームワークが用意されている。

  • visibility
  • Accessibility
  • Authorization

このうち Accessibility については、非推奨となっているので今回は扱わない。

visibilityについて

これは、ユーザーの権限によってGraphQLスキーマの一部を隠すものだ。

実際にvisibilityを使用してみる

module Types
  class QueryType < Types::BaseObject

    field :todos, Types::TodoType.connection_type, null: false do
      def visible?(context)
        super && context[:current_user].role == 'admin'
      end
    end

    def todos
      Todo.all
    end
  end
end

上記以外の補足説明としては以下の2つのモデルが存在している

  • User
  • Todo
class User < ApplicationRecord
  has_many :todos, dependent: :destroy
end

class Todo < ApplicationRecord
  belongs_to :user
end

上記のような関係性である。

最初のQueryTypeの説明に戻すと、adminの場合はユーザー関係なく全てのTodoを見れるようにしたいとする。

上記を実現するためにvisibilityをfield: todosに実装した。

これの状態でadmin以外のユーザーでクエリを実行すると以下のように返ってくる。

"message": "Field 'todos' doesn't exist on type 'Query'"

そもそもそんなものないよと言われている。

確かに隠している形になっている。

ちなみにvisibilityはドキュメントに記載されているように4種類のメソッドを提供している

Type classes have a .visible?(context) class method

Fields and arguments have a #visible?(context) instance method

Enum values have #visible?(context) instance method

Mutation classes have a .visible?(context) class method

特定のモデルに紐付くTypeのfieldに使用したり、

mutationでもクラスメソッドして提供されているので、特定のmutationを隠蔽することができる。

module Mutations
  class CreateTodoMutation < BaseMutation
    argument :title, String, required: true
    argument :description, String, required: true

    field :todo_edge, Types::TodoType.edge_type, null: false

    def self.visible?(context)
      super && context[:current_user].role == 'admin'
    end

 # 以下省略
end

Authorizationについて

これは、ユーザーがアクセス中のオブジェクトに対してアクセスできる権限をチェックするものだ。

先ほどのvisibilityの例をauthorizationに変えてみた

module Types
  class QueryType < Types::BaseObject

    field :todos, Types::TodoType.connection_type, null: false do
      def authorized?(obj, args, ctx)
        super && context[:current_user].role == 'admin'
      end
    end

    def todos
      Todo.all
    end
  end
end

visibilityは受け渡される引数はcontextのみだったのに対して、 object, argument, contextが受け渡される。

objectはfieldから返されるアプリケーションオブジェクトをあらわす。

argumentはアクセスするfieldに必要なargumentをあらわす。

クエリを実行すると、結果としてはvisibilityと同じようにアクセスできずエラーが返却されるが、 メッセージ内容が異なることがわかる。

"message": "Cannot return null for non-nullable field Query.todos"

QueryTypeのtodosというfieldに対して許可を持ってないので、非表示にしたという内容の記述が返ってきた。

先ほどのvisibilityはtodosなんてないよと言ってきたのに対して、こちらは存在するが権限を持っていないよと言われている。

Authorizationは3種類のメソッドを提供している

Type classes have .authorized?(object, context) class methods

Fields have #authorized?(object, args, context) instance methods

Arguments have #authorized?(object, arg_value, context) instance methods

先ほどと同じように実装することができる。

Authorizationはデフォルトでアクセスしたオブジェクトに対して、許可しなかった場合、オブジェクトが存在しなかったかのようにnilを返すようになっている。

これは実装者自信でカスタマイズすることができる。

class AppSchema < GraphQL::Schema
  mutation(Types::MutationType)
  query(Types::QueryType)

  # Opt in to the new runtime (default in future graphql-ruby versions)
  use GraphQL::Execution::Interpreter
  use GraphQL::Analysis::AST

  # Add built-in connections for pagination
  use GraphQL::Pagination::Connections

  def self.unauthorized_object(error)
    raise GraphQL::ExecutionError, "An object of type #{error.type.graphql_name} was hidden due to permissions"
  end

  def self.unauthorized_field(error)
    raise GraphQL::ExecutionError, "The field #{error.field.graphql_name} on an object of type #{error.type.graphql_name} was hidden due to permissions"
  end
end

クラスメソッドとして提供されているunauthorized_fieldを実装することで、デフォルトではなく、カスタマイズしたものを返すことができる。

特定のfieldに対してはunauthorized_fieldが提供され、

特定の(クラス)オブジェクトに対してはunauthorized_objectが提供される。

module Types
  class QueryType < Types::BaseObject
    def self.authorized?(obj, ctx)
      super && ctx[:current_user].role == 'admin'
    end

  # 以下省略

  end
end

unauthorized_objectを実装していないと以下のようにnullが返ってくる

{
  "data": null
}

unauthorized_objectを実装すると以下のように変えることができる。

{
  "errors": [
    {
      "message": "An object of type Query was hidden due to permissions"
    }
  ]
}

descheduler再実践記録

前回の記事では、ほぼ和訳と実践してみたものの上手く動かないまま終わってしまった。

今回はちゃんと動いた結果を書いていく

環境

GKE

version : 1.13.11-gke.23

マシンタイプ : n1-standard-1

node数 : 3

リソースが欲しいので、stackdriver loggingを無効にしてfluentdがいない構成にしている

まずLowNodeUtilizationからやってみる

実行前Podの一覧である。

見てお分かりの通り、node3台にもかかわらず、2台のnodeで全てのPodが配置されている。

 kubectl get pods -n wakashiyo -o wide
NAME                                    READY   STATUS    RESTARTS   AGE   IP           NODE                                       NOMINATED NODE   READINESS GATES
wakashiyo-deployment-75b89bd54b-69bkl   1/1     Running   0          29m   10.48.1.16   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-75b89bd54b-hpdr7   1/1     Running   0          29m   10.48.1.19   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-75b89bd54b-khgvv   1/1     Running   0          29m   10.48.1.18   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-75b89bd54b-khvgc   1/1     Running   0          29m   10.48.1.17   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-75b89bd54b-vkn9h   1/1     Running   0          29m   10.48.0.15   gke-wakashiyo-default-pool-8b0ec5ba-2w40   <none>           <none>
wakashiyo-deployment-75b89bd54b-z2xfj   1/1     Running   0          29m   10.48.0.16   gke-wakashiyo-default-pool-8b0ec5ba-2w40   <none>           <none>

実行前にnodeの情報を見てみた。

LowNodeUtilizationはnodeのcpu、memory、podのリソースを設定して実行するものなので、事前に確認しておく。

本来の運用であれば、ある程度基準値を設けておいて設定しておくはずだが、今回はとりあえず動いているところを見たい+流れがわかりやすくなると思うので、事前に確認して設定することにする。

gke-wakashiyo-default-pool-8b0ec5ba-x29p

ProviderID:                  gce://wakashiyo-playground/asia-northeast1-a/gke-wakashiyo-default-pool-8b0ec5ba-x29p
Non-terminated Pods:         (8 in total)
  Namespace                  Name                                                   CPU Requests  CPU Limits  Memory Requests  Memory Limits  AGE
  ---------                  ----                                                   ------------  ----------  ---------------  -------------  ---
  kube-system                heapster-86f6474897-stbl9                              63m (6%)      63m (6%)    215840Ki (7%)    215840Ki (7%)  140m
  kube-system                kube-proxy-gke-wakashiyo-default-pool-8b0ec5ba-x29p    100m (10%)    0 (0%)      0 (0%)           0 (0%)         140m
  kube-system                metrics-server-v0.3.1-57c75779f-xg59s                  48m (5%)      143m (15%)  105Mi (3%)       355Mi (13%)    140m
  kube-system                prometheus-to-sd-wmcsp                                 1m (0%)       3m (0%)     20Mi (0%)        20Mi (0%)      140m
  wakashiyo                  wakashiyo-deployment-75b89bd54b-69bkl                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     31m
  wakashiyo                  wakashiyo-deployment-75b89bd54b-hpdr7                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     31m
  wakashiyo                  wakashiyo-deployment-75b89bd54b-khgvv                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     31m
  wakashiyo                  wakashiyo-deployment-75b89bd54b-khvgc                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     31m
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource                   Requests        Limits
  --------                   --------        ------
  cpu                        612m (65%)      609m (64%)
  memory                     651040Ki (24%)  1009440Ki (37%)
  ephemeral-storage          0 (0%)          0 (0%)
  attachable-volumes-gce-pd  0               0

gke-wakashiyo-default-pool-8b0ec5ba-2w40

ProviderID:                  gce://wakashiyo-playground/asia-northeast1-a/gke-wakashiyo-default-pool-8b0ec5ba-2w40
Non-terminated Pods:         (6 in total)
  Namespace                  Name                                                   CPU Requests  CPU Limits  Memory Requests  Memory Limits  AGE
  ---------                  ----                                                   ------------  ----------  ---------------  -------------  ---
  kube-system                kube-dns-79868f54c5-7cc5w                              260m (27%)    0 (0%)      110Mi (4%)       170Mi (6%)     141m
  kube-system                kube-dns-autoscaler-bb58c6784-8ms5s                    20m (2%)      0 (0%)      10Mi (0%)        0 (0%)         141m
  kube-system                kube-proxy-gke-wakashiyo-default-pool-8b0ec5ba-2w40    100m (10%)    0 (0%)      0 (0%)           0 (0%)         141m
  kube-system                prometheus-to-sd-qxg9g                                 1m (0%)       3m (0%)     20Mi (0%)        20Mi (0%)      141m
  wakashiyo                  wakashiyo-deployment-75b89bd54b-vkn9h                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     32m
  wakashiyo                  wakashiyo-deployment-75b89bd54b-z2xfj                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     32m
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource                   Requests     Limits
  --------                   --------     ------
  cpu                        581m (61%)   203m (21%)
  memory                     290Mi (10%)  390Mi (14%)
  ephemeral-storage          0 (0%)       0 (0%)
  attachable-volumes-gce-pd  0            0

gke-wakashiyo-default-pool-8b0ec5ba-654k

ProviderID:                  gce://wakashiyo-playground/asia-northeast1-a/gke-wakashiyo-default-pool-8b0ec5ba-654k
Non-terminated Pods:         (4 in total)
  Namespace                  Name                                                   CPU Requests  CPU Limits  Memory Requests  Memory Limits  AGE
  ---------                  ----                                                   ------------  ----------  ---------------  -------------  ---
  kube-system                kube-dns-79868f54c5-jvwg8                              260m (27%)    0 (0%)      110Mi (4%)       170Mi (6%)     142m
  kube-system                kube-proxy-gke-wakashiyo-default-pool-8b0ec5ba-654k    100m (10%)    0 (0%)      0 (0%)           0 (0%)         142m
  kube-system                l7-default-backend-fd59995cd-jfvwb                     10m (1%)      10m (1%)    20Mi (0%)        20Mi (0%)      142m
  kube-system                prometheus-to-sd-d6vmv                                 1m (0%)       3m (0%)     20Mi (0%)        20Mi (0%)      142m
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource                   Requests    Limits
  --------                   --------    ------
  cpu                        371m (39%)  13m (1%)
  memory                     150Mi (5%)  210Mi (7%)
  ephemeral-storage          0 (0%)      0 (0%)
  attachable-volumes-gce-pd  0           0

describe した結果を一覧表にまとめた。

node cpu memory pod数
gke-wakashiyo-default-pool-8b0ec5ba-x29p 612m (65%) 651040Ki (24%) 8
kube-system: 4
wakashiyo: 4
gke-wakashiyo-default-pool-8b0ec5ba-2w40 581m (61%) 290Mi (10%) 6
kube-system: 4
wakashiyo: 2
gke-wakashiyo-default-pool-8b0ec5ba-654k 371m (39%) 150Mi (5%) 4
kube-system: 4
wakashiyo: 0

今回はgke-wakashiyo-default-pool-8b0ec5ba-x29pにPodが偏っているので、このnodeを削除対象のnodeとしてスケジュールしたい。

そして、Podが全然配置されていないgke-wakashiyo-default-pool-8b0ec5ba-654kを再スケジュール対象のnodeとしてスケジュールしたい。

上記を考慮して以下のようなconfigMapの設定にした

apiVersion: v1
kind: ConfigMap
metadata:
  name: descheduler-policy-configmap
  namespace: kube-system
data:
  policy.yaml: |
    apiVersion: "descheduler/v1alpha1"
    kind: "DeschedulerPolicy"
    strategies:
      "RemoveDuplicates":
        enabled: true
      "RemovePodsViolatingInterPodAntiAffinity":
        enabled: true
      "LowNodeUtilization":
        enabled: true
        params:
          nodeResourceUtilizationThresholds:
            thresholds:
              "cpu" : 50
              "memory": 50
              "pods": 5
            targetThresholds:
              "cpu" : 60
              "memory": 30
              "pods": 7

注意しなければならないのは、kube-systemも含めた全リソースのcpu使用率、memory使用率、Pod数を考慮して設定しなければならないことだ。

そして、再スケジュール対象のnodeを決めるpolicy(thresholds)は全ての条件を満たさなければならないのに対し、

削除対象のnodeを決めるpolicy(targetThresholds)は3つの要素のいずれかを満たせばいい。

ということを考慮して上記のconfigMapのように設定した。

このconfigMapを適用して、deschedulerのジョブを実行させた。以下が実行後のPodの一覧である。

kubectl get pods -n wakashiyo -o wide
NAME                                    READY   STATUS    RESTARTS   AGE   IP           NODE                                       NOMINATED NODE   READINESS GATES
wakashiyo-deployment-75b89bd54b-69bkl   1/1     Running   0          41m   10.48.1.16   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-75b89bd54b-dts4h   1/1     Running   0          30s   10.48.2.18   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-75b89bd54b-f72ch   1/1     Running   0          30s   10.48.2.19   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-75b89bd54b-jg9vv   1/1     Running   0          30s   10.48.1.20   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-75b89bd54b-vkn9h   1/1     Running   0          41m   10.48.0.15   gke-wakashiyo-default-pool-8b0ec5ba-2w40   <none>           <none>
wakashiyo-deployment-75b89bd54b-xzffg   1/1     Running   0          30s   10.48.0.17   gke-wakashiyo-default-pool-8b0ec5ba-2w40   <none>           <none>

うまくいったようだ。2台ずつに配置されたので、いい感じになった。

RemovePodsViolatingNodeAffinity をやってみる

policyの説明は省略する。

nodeのラベルの一覧を確認する。

kubectl get nodes -o json | jq ".items[] | .metadata.labels"
{
  "beta.kubernetes.io/arch": "amd64",
  "beta.kubernetes.io/fluentd-ds-ready": "true",
  "beta.kubernetes.io/instance-type": "n1-standard-1",
  "beta.kubernetes.io/os": "linux",
  "cloud.google.com/gke-nodepool": "default-pool",
  "cloud.google.com/gke-os-distribution": "cos",
  "failure-domain.beta.kubernetes.io/region": "asia-northeast1",
  "failure-domain.beta.kubernetes.io/zone": "asia-northeast1-a",
  "kubernetes.io/hostname": "gke-wakashiyo-default-pool-8b0ec5ba-2w40"
}
{
  "beta.kubernetes.io/arch": "amd64",
  "beta.kubernetes.io/fluentd-ds-ready": "true",
  "beta.kubernetes.io/instance-type": "n1-standard-1",
  "beta.kubernetes.io/os": "linux",
  "cloud.google.com/gke-nodepool": "default-pool",
  "cloud.google.com/gke-os-distribution": "cos",
  "failure-domain.beta.kubernetes.io/region": "asia-northeast1",
  "failure-domain.beta.kubernetes.io/zone": "asia-northeast1-a",
  "kubernetes.io/hostname": "gke-wakashiyo-default-pool-8b0ec5ba-654k",
  "sample": "bias"
}
{
  "beta.kubernetes.io/arch": "amd64",
  "beta.kubernetes.io/fluentd-ds-ready": "true",
  "beta.kubernetes.io/instance-type": "n1-standard-1",
  "beta.kubernetes.io/os": "linux",
  "cloud.google.com/gke-nodepool": "default-pool",
  "cloud.google.com/gke-os-distribution": "cos",
  "failure-domain.beta.kubernetes.io/region": "asia-northeast1",
  "failure-domain.beta.kubernetes.io/zone": "asia-northeast1-a",
  "kubernetes.io/hostname": "gke-wakashiyo-default-pool-8b0ec5ba-x29p"
}

あらかじめ、gke-wakashiyo-default-pool-8b0ec5ba-654kに対して、 sample=bias というラベルを付与した。

そして、そのラベルが付いているnodeにPodをスケジューリングするようにした。

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  namespace: wakashiyo
  name: wakashiyo-deployment
spec:
  replicas: 5
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.12
          ports:
            - containerPort: 80
          resources:
            requests:
              memory: 75Mi
              cpu: 100m
            limits:
              memory: 100Mi
              cpu: 100m
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: sample
                    operator: In
                    values:
                      - bias
 kubectl get pods -n wakashiyo -o wide
NAME                                    READY   STATUS    RESTARTS   AGE   IP           NODE                                       NOMINATED NODE   READINESS GATES
wakashiyo-deployment-54664b6fc8-44qf2   1/1     Running   0          20s   10.48.2.20   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-54664b6fc8-5wlzm   1/1     Running   0          16s   10.48.2.24   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-54664b6fc8-nm5rh   1/1     Running   0          20s   10.48.2.22   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-54664b6fc8-qnc2l   1/1     Running   0          20s   10.48.2.21   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-54664b6fc8-wljgc   1/1     Running   0          16s   10.48.2.23   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
(base) takahiroyoshikawa@TakahironoMacBook-Pro descheduler %

全てのPodがgke-wakashiyo-default-pool-8b0ec5ba-654kに配置されている。

ここで、 sample=bias というラベルを他のnode に付与する。

(base) takahiroyoshikawa@TakahironoMacBook-Pro descheduler % kubectl label nodes gke-wakashiyo-default-pool-8b0ec5ba-654k sample-
node/gke-wakashiyo-default-pool-8b0ec5ba-654k labeled
(base) takahiroyoshikawa@TakahironoMacBook-Pro descheduler % kubectl label nodes gke-wakashiyo-default-pool-8b0ec5ba-x29p sample=bias
node/gke-wakashiyo-default-pool-8b0ec5ba-x29p labeled
kubectl get nodes -o json | jq ".items[] | .metadata.labels"
{
  "beta.kubernetes.io/arch": "amd64",
  "beta.kubernetes.io/fluentd-ds-ready": "true",
  "beta.kubernetes.io/instance-type": "n1-standard-1",
  "beta.kubernetes.io/os": "linux",
  "cloud.google.com/gke-nodepool": "default-pool",
  "cloud.google.com/gke-os-distribution": "cos",
  "failure-domain.beta.kubernetes.io/region": "asia-northeast1",
  "failure-domain.beta.kubernetes.io/zone": "asia-northeast1-a",
  "kubernetes.io/hostname": "gke-wakashiyo-default-pool-8b0ec5ba-2w40"
}
{
  "beta.kubernetes.io/arch": "amd64",
  "beta.kubernetes.io/fluentd-ds-ready": "true",
  "beta.kubernetes.io/instance-type": "n1-standard-1",
  "beta.kubernetes.io/os": "linux",
  "cloud.google.com/gke-nodepool": "default-pool",
  "cloud.google.com/gke-os-distribution": "cos",
  "failure-domain.beta.kubernetes.io/region": "asia-northeast1",
  "failure-domain.beta.kubernetes.io/zone": "asia-northeast1-a",
  "kubernetes.io/hostname": "gke-wakashiyo-default-pool-8b0ec5ba-654k"
}
{
  "beta.kubernetes.io/arch": "amd64",
  "beta.kubernetes.io/fluentd-ds-ready": "true",
  "beta.kubernetes.io/instance-type": "n1-standard-1",
  "beta.kubernetes.io/os": "linux",
  "cloud.google.com/gke-nodepool": "default-pool",
  "cloud.google.com/gke-os-distribution": "cos",
  "failure-domain.beta.kubernetes.io/region": "asia-northeast1",
  "failure-domain.beta.kubernetes.io/zone": "asia-northeast1-a",
  "kubernetes.io/hostname": "gke-wakashiyo-default-pool-8b0ec5ba-x29p",
  "sample": "bias"
}

kube-schedulerはPodの作成時にはルールに基づいたスケジューリングを行うが、それ以外の場合は再スケジュールなどはしない。

そのためラベルを付け変えたが、特に変化はない

kubectl get pods -n wakashiyo -o wide
NAME                                    READY   STATUS    RESTARTS   AGE     IP           NODE                                       NOMINATED NODE   READINESS GATES
wakashiyo-deployment-54664b6fc8-44qf2   1/1     Running   0          3m40s   10.48.2.20   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-54664b6fc8-5wlzm   1/1     Running   0          3m36s   10.48.2.24   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-54664b6fc8-nm5rh   1/1     Running   0          3m40s   10.48.2.22   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-54664b6fc8-qnc2l   1/1     Running   0          3m40s   10.48.2.21   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-54664b6fc8-wljgc   1/1     Running   0          3m36s   10.48.2.23   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>

再スケジュールするためにconfigMapを適用し、jobを実行した。

configMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: descheduler-policy-configmap
  namespace: kube-system
data:
  policy.yaml: |
    apiVersion: "descheduler/v1alpha1"
    kind: "DeschedulerPolicy"
    strategies:
      "RemoveDuplicates":
        enabled: true
      "RemovePodsViolatingInterPodAntiAffinity":
        enabled: true
      "RemovePodsViolatingNodeAffinity":
        enabled: true
        params:
          nodeAffinityType:
          - "requiredDuringSchedulingIgnoredDuringExecution"

結果

 kubectl get pods -n wakashiyo -o wide
NAME                                    READY   STATUS    RESTARTS   AGE   IP           NODE                                       NOMINATED NODE   READINESS GATES
wakashiyo-deployment-54664b6fc8-5hn99   1/1     Running   0          23s   10.48.1.22   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-54664b6fc8-6xp6k   1/1     Running   0          23s   10.48.1.25   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-54664b6fc8-7gvqt   1/1     Running   0          23s   10.48.1.26   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-54664b6fc8-jtqcd   1/1     Running   0          23s   10.48.1.23   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-54664b6fc8-x8tkg   1/1     Running   0          23s   10.48.1.24   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>

新たにラベルを付け替えたnodeにPodが再配置された。

RemoveDeplicates をやってみる

このポリシーを使用して、各nodeに1台ずつPodが配置されるようにしていく。

実行前のPodの一覧

 kubectl get pods -n wakashiyo -o wide
NAME                                    READY   STATUS    RESTARTS   AGE   IP           NODE                                       NOMINATED NODE   READINESS GATES
wakashiyo-deployment-75b89bd54b-77cvb   1/1     Running   0          32s   10.48.2.27   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-75b89bd54b-7sdxt   1/1     Running   0          33s   10.48.0.19   gke-wakashiyo-default-pool-8b0ec5ba-2w40   <none>           <none>
wakashiyo-deployment-75b89bd54b-8dspm   1/1     Running   0          35s   10.48.0.18   gke-wakashiyo-default-pool-8b0ec5ba-2w40   <none>           <none>
wakashiyo-deployment-75b89bd54b-qk5hp   1/1     Running   0          35s   10.48.2.26   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-75b89bd54b-rxqm8   1/1     Running   0          32s   10.48.0.20   gke-wakashiyo-default-pool-8b0ec5ba-2w40   <none>           <none>
wakashiyo-deployment-75b89bd54b-vgw69   1/1     Running   0          35s   10.48.2.25   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>

gke-wakashiyo-default-pool-8b0ec5ba-x29pには1台も配置されていない。。。

以下のようにconfigMapを設定し、適用した

configMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: descheduler-policy-configmap
  namespace: kube-system
data:
  policy.yaml: |
    apiVersion: "descheduler/v1alpha1"
    kind: "DeschedulerPolicy"
    strategies:
      "RemoveDuplicates":
        enabled: true

そして、jobを実行した結果、Podがどのnodeにも1台は配置されるようになった。

kubectl get pods -n wakashiyo -o wide
NAME                                    READY   STATUS    RESTARTS   AGE     IP           NODE                                       NOMINATED NODE   READINESS GATES
wakashiyo-deployment-75b89bd54b-6bs4z   1/1     Running   0          36s     10.48.1.31   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-75b89bd54b-77cvb   1/1     Running   0          3m15s   10.48.2.27   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-75b89bd54b-7bth7   1/1     Running   0          36s     10.48.1.30   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-75b89bd54b-7sdxt   1/1     Running   0          3m16s   10.48.0.19   gke-wakashiyo-default-pool-8b0ec5ba-2w40   <none>           <none>
wakashiyo-deployment-75b89bd54b-ddt28   1/1     Running   0          36s     10.48.1.28   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-75b89bd54b-jnpgz   1/1     Running   0          36s     10.48.1.29   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>

ドキュメントを読んだ感じではDeploymentなどに紐づくPodが各nodeに1台だけであるみたいに読み取ったから、Replica数が多い場合はどうなるのかと思ったが、今回動かした感じでは最低1台は配置するみたいな解釈でも良さそうな気がしている。。。

追記

最初にLowNodeUtilizationを確認した際のconfigMapは、RemoveDeplicatesとLowNodeUtilizationがpolicyに含まれていたため、正直どっちが動いていい感じにスケジューリングされたのかわからなかったと後から気づいた。

そのため、先ほどのRemoveDeplicates実行後の環境を使用してLowNodeUtilizationが動いていることをもう一度確認してみたい。

再度実行前のnodeの状況を確認してみた。

gke-wakashiyo-default-pool-8b0ec5ba-654k

ProviderID:                  gce://wakashiyo-playground/asia-northeast1-a/gke-wakashiyo-default-pool-8b0ec5ba-654k
Non-terminated Pods:         (5 in total)
  Namespace                  Name                                                   CPU Requests  CPU Limits  Memory Requests  Memory Limits  AGE
  ---------                  ----                                                   ------------  ----------  ---------------  -------------  ---
  kube-system                kube-dns-79868f54c5-jvwg8                              260m (27%)    0 (0%)      110Mi (4%)       170Mi (6%)     169m
  kube-system                kube-proxy-gke-wakashiyo-default-pool-8b0ec5ba-654k    100m (10%)    0 (0%)      0 (0%)           0 (0%)         169m
  kube-system                l7-default-backend-fd59995cd-jfvwb                     10m (1%)      10m (1%)    20Mi (0%)        20Mi (0%)      169m
  kube-system                prometheus-to-sd-d6vmv                                 1m (0%)       3m (0%)     20Mi (0%)        20Mi (0%)      169m
  wakashiyo                  wakashiyo-deployment-75b89bd54b-77cvb                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     5m52s
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource                   Requests    Limits
  --------                   --------    ------
  cpu                        471m (50%)  113m (12%)
  memory                     225Mi (8%)  310Mi (11%)
  ephemeral-storage          0 (0%)      0 (0%)
  attachable-volumes-gce-pd  0           0

gke-wakashiyo-default-pool-8b0ec5ba-2w40

ProviderID:                  gce://wakashiyo-playground/asia-northeast1-a/gke-wakashiyo-default-pool-8b0ec5ba-2w40
Non-terminated Pods:         (5 in total)
  Namespace                  Name                                                   CPU Requests  CPU Limits  Memory Requests  Memory Limits  AGE
  ---------                  ----                                                   ------------  ----------  ---------------  -------------  ---
  kube-system                kube-dns-79868f54c5-7cc5w                              260m (27%)    0 (0%)      110Mi (4%)       170Mi (6%)     170m
  kube-system                kube-dns-autoscaler-bb58c6784-8ms5s                    20m (2%)      0 (0%)      10Mi (0%)        0 (0%)         170m
  kube-system                kube-proxy-gke-wakashiyo-default-pool-8b0ec5ba-2w40    100m (10%)    0 (0%)      0 (0%)           0 (0%)         170m
  kube-system                prometheus-to-sd-qxg9g                                 1m (0%)       3m (0%)     20Mi (0%)        20Mi (0%)      170m
  wakashiyo                  wakashiyo-deployment-75b89bd54b-7sdxt                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     7m11s
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource                   Requests    Limits
  --------                   --------    ------
  cpu                        481m (51%)  103m (10%)
  memory                     215Mi (8%)  290Mi (10%)
  ephemeral-storage          0 (0%)      0 (0%)
  attachable-volumes-gce-pd  0           0

gke-wakashiyo-default-pool-8b0ec5ba-x29p

ProviderID:                  gce://wakashiyo-playground/asia-northeast1-a/gke-wakashiyo-default-pool-8b0ec5ba-x29p
Non-terminated Pods:         (8 in total)
  Namespace                  Name                                                   CPU Requests  CPU Limits  Memory Requests  Memory Limits  AGE
  ---------                  ----                                                   ------------  ----------  ---------------  -------------  ---
  kube-system                heapster-86f6474897-stbl9                              63m (6%)      63m (6%)    215840Ki (7%)    215840Ki (7%)  171m
  kube-system                kube-proxy-gke-wakashiyo-default-pool-8b0ec5ba-x29p    100m (10%)    0 (0%)      0 (0%)           0 (0%)         171m
  kube-system                metrics-server-v0.3.1-57c75779f-xg59s                  48m (5%)      143m (15%)  105Mi (3%)       355Mi (13%)    171m
  kube-system                prometheus-to-sd-wmcsp                                 1m (0%)       3m (0%)     20Mi (0%)        20Mi (0%)      171m
  wakashiyo                  wakashiyo-deployment-75b89bd54b-6bs4z                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     5m36s
  wakashiyo                  wakashiyo-deployment-75b89bd54b-7bth7                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     5m36s
  wakashiyo                  wakashiyo-deployment-75b89bd54b-ddt28                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     5m36s
  wakashiyo                  wakashiyo-deployment-75b89bd54b-jnpgz                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     5m36s
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource                   Requests        Limits
  --------                   --------        ------
  cpu                        612m (65%)      609m (64%)
  memory                     651040Ki (24%)  1009440Ki (37%)
  ephemeral-storage          0 (0%)          0 (0%)
  attachable-volumes-gce-pd  0               0
node cpu memory pod数
gke-wakashiyo-default-pool-8b0ec5ba-x29p 612m (65%) 651040Ki (24%) 8
kube-system: 4
wakashiyo: 4
gke-wakashiyo-default-pool-8b0ec5ba-2w40 481m (51%) 215Mi (8%) 5
kube-system: 4
wakashiyo: 1
gke-wakashiyo-default-pool-8b0ec5ba-654k 471m (50%) 225Mi (8%) 5
kube-system: 4
wakashiyo: 1

gke-wakashiyo-default-pool-8b0ec5ba-x29pに偏っているPodをgke-wakashiyo-default-pool-8b0ec5ba-2w40とgke-wakashiyo-default-pool-8b0ec5ba-654kにスケジューリングしたいので、以下のようなconfigMapを適用した

configMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: descheduler-policy-configmap
  namespace: kube-system
data:
  policy.yaml: |
    apiVersion: "descheduler/v1alpha1"
    kind: "DeschedulerPolicy"
    strategies:
      "LowNodeUtilization":
        enabled: true
        params:
          nodeResourceUtilizationThresholds:
            thresholds:
              "cpu" : 60
              "memory": 20
              "pods": 6
            targetThresholds:
              "cpu" : 60
              "memory": 30
              "pods": 7

jobを実行した結果以下のようになった。

 kubectl get pods -n wakashiyo -o wide
NAME                                    READY   STATUS    RESTARTS   AGE   IP           NODE                                       NOMINATED NODE   READINESS GATES
wakashiyo-deployment-75b89bd54b-77cvb   1/1     Running   0          14m   10.48.2.27   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-75b89bd54b-7bth7   1/1     Running   0          11m   10.48.1.30   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-75b89bd54b-7sdxt   1/1     Running   0          14m   10.48.0.19   gke-wakashiyo-default-pool-8b0ec5ba-2w40   <none>           <none>
wakashiyo-deployment-75b89bd54b-ddt28   1/1     Running   0          11m   10.48.1.28   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-75b89bd54b-jnpgz   1/1     Running   0          11m   10.48.1.29   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-75b89bd54b-zhxj9   1/1     Running   0          30s   10.48.2.28   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>

理想的には2台ずつ配置されて欲しかったが、うまくいかなかった。

なぜ上手くいかなかったのか、gke-wakashiyo-default-pool-8b0ec5ba-x29pの情報を確認してみた。

gke-wakashiyo-default-pool-8b0ec5ba-x29p

ProviderID:                  gce://wakashiyo-playground/asia-northeast1-a/gke-wakashiyo-default-pool-8b0ec5ba-x29p
Non-terminated Pods:         (7 in total)
  Namespace                  Name                                                   CPU Requests  CPU Limits  Memory Requests  Memory Limits  AGE
  ---------                  ----                                                   ------------  ----------  ---------------  -------------  ---
  kube-system                heapster-86f6474897-stbl9                              63m (6%)      63m (6%)    215840Ki (7%)    215840Ki (7%)  179m
  kube-system                kube-proxy-gke-wakashiyo-default-pool-8b0ec5ba-x29p    100m (10%)    0 (0%)      0 (0%)           0 (0%)         3h
  kube-system                metrics-server-v0.3.1-57c75779f-xg59s                  48m (5%)      143m (15%)  105Mi (3%)       355Mi (13%)    179m
  kube-system                prometheus-to-sd-wmcsp                                 1m (0%)       3m (0%)     20Mi (0%)        20Mi (0%)      3h
  wakashiyo                  wakashiyo-deployment-75b89bd54b-7bth7                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     14m
  wakashiyo                  wakashiyo-deployment-75b89bd54b-ddt28                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     14m
  wakashiyo                  wakashiyo-deployment-75b89bd54b-jnpgz                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     14m
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource                   Requests        Limits
  --------                   --------        ------
  cpu                        512m (54%)      509m (54%)
  memory                     574240Ki (21%)  907040Ki (33%)
  ephemeral-storage          0 (0%)          0 (0%)
  attachable-volumes-gce-pd  0               0

gke-wakashiyo-default-pool-8b0ec5ba-654k

ProviderID:                  gce://wakashiyo-playground/asia-northeast1-a/gke-wakashiyo-default-pool-8b0ec5ba-654k
Non-terminated Pods:         (6 in total)
  Namespace                  Name                                                   CPU Requests  CPU Limits  Memory Requests  Memory Limits  AGE
  ---------                  ----                                                   ------------  ----------  ---------------  -------------  ---
  kube-system                kube-dns-79868f54c5-jvwg8                              260m (27%)    0 (0%)      110Mi (4%)       170Mi (6%)     3h2m
  kube-system                kube-proxy-gke-wakashiyo-default-pool-8b0ec5ba-654k    100m (10%)    0 (0%)      0 (0%)           0 (0%)         3h2m
  kube-system                l7-default-backend-fd59995cd-jfvwb                     10m (1%)      10m (1%)    20Mi (0%)        20Mi (0%)      3h2m
  kube-system                prometheus-to-sd-d6vmv                                 1m (0%)       3m (0%)     20Mi (0%)        20Mi (0%)      3h2m
  wakashiyo                  wakashiyo-deployment-75b89bd54b-77cvb                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     19m
  wakashiyo                  wakashiyo-deployment-75b89bd54b-zhxj9                  100m (10%)    100m (10%)  75Mi (2%)        100Mi (3%)     5m10s
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource                   Requests     Limits
  --------                   --------     ------
  cpu                        571m (60%)   213m (22%)
  memory                     300Mi (11%)  410Mi (15%)
  ephemeral-storage          0 (0%)       0 (0%)
  attachable-volumes-gce-pd  0            0

1台Podが削除された時点で、thresholdsとtargetThresholdsのそれぞれに対象となるnodeがなくなってしまったのかもしれない。。。

(gke-wakashiyo-default-pool-8b0ec5ba-2w40の情報を貼り忘れてしまったので、なんとも言えない感じになってしまいました。ご了承ください。。)

改めてconfigMapを設定した。

configMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: descheduler-policy-configmap
  namespace: kube-system
data:
  policy.yaml: |
    apiVersion: "descheduler/v1alpha1"
    kind: "DeschedulerPolicy"
    strategies:
      "LowNodeUtilization":
        enabled: true
        params:
          nodeResourceUtilizationThresholds:
            thresholds:
              "cpu" : 60
              "memory": 20
              "pods": 5
            targetThresholds:
              "cpu" : 70
              "memory": 20
              "pods": 7

jobを回した結果、均等に配置することができた。

 kubectl get pods -n wakashiyo -o wide
NAME                                    READY   STATUS    RESTARTS   AGE   IP           NODE                                       NOMINATED NODE   READINESS GATES
wakashiyo-deployment-75b89bd54b-5cph9   1/1     Running   0          29s   10.48.0.26   gke-wakashiyo-default-pool-8b0ec5ba-2w40   <none>           <none>
wakashiyo-deployment-75b89bd54b-7sdxt   1/1     Running   0          27m   10.48.0.19   gke-wakashiyo-default-pool-8b0ec5ba-2w40   <none>           <none>
wakashiyo-deployment-75b89bd54b-gjmn9   1/1     Running   0          8m    10.48.2.29   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>
wakashiyo-deployment-75b89bd54b-jnpgz   1/1     Running   0          25m   10.48.1.29   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-75b89bd54b-sb5dn   1/1     Running   0          8m    10.48.1.32   gke-wakashiyo-default-pool-8b0ec5ba-x29p   <none>           <none>
wakashiyo-deployment-75b89bd54b-zhxj9   1/1     Running   0          13m   10.48.2.28   gke-wakashiyo-default-pool-8b0ec5ba-654k   <none>           <none>

kubernetesの個人的メモ(オートスケールとdescheduler)

色々といじってたら、繋がったので、書いてみる。

オートスケールの前に

リソースの制限

kubernetesにはコンテナ単位にリソースの制限を行うことができる。

コンテナ、ノードが適切にパフォーマンスを発揮するためにも必要なことである。

基本的にはCPUとメモリに制限をかけることができる。

CPUについては、1vCPUを1000millicores単位で指定する。 メモリについては、1Gi を 1024Mi単位で指定する。

構成ファイル例

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  namespace: wakashiyo
  name: wakashiyo-dev-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.12
          ports:
            - containerPort: 80
          resources:
            requests:
              memory: 1024Mi
              cpu: 500m
            limits:
              memory: 2048Mi
              cpu: 1000m

resources.requestsとresources.limitsの2種類が設定されている。

requestsは、リソース使用量の下限で、

limitsは、上限を指定している。

Podのスケジューリングは、ノードにrequestsで指定したリソースが残っているとスケジューリングされ、 残っていないとスケジューリングされない。

例えば、ノードAの割り当て可能なCPUが940mだった場合、

kube-systemの合計のrequestsが300mになっている。

これからデプロイしたいPodのrequestsのCPUリソースを100mにしているとする。(レプリカ数は、8にする)

この場合、6台までは割り当てられることができるが、残り割り当て可能なCPUリソースは40m しか残っていないため、

7台目以降はスケジューリングされない。

requestsの値を基準にリソースが残っているか判断しスケジューリングされるため、limitsの方で指定したリソースが残っていなくてもスケジューリングされる。

コンテナのリソース使用量がlimitsを超えた場合どうなってしまうのか?

limitに設定したメモリを超える構成ファイルを用意して、デプロイしてみた

apiVersion: v1
kind: Pod
metadata:
  name: memory-over-pod
  namespace: wakashiyo
spec:
  containers:
    - name: memory-over-pod
      image: polinux/stress
      resources:
        requests:
          memory: "50Mi"
        limits:
          memory: "100Mi"
      command: ["stress"]
      args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"]

limitsのメモリを100Miにして250のメモリを確保する用コマンドを実行している。

memory-over-pod   0/1     OOMKilled   1          10s
memory-over-pod   0/1     CrashLoopBackOff   1          11s

結果、上記のように強制終了し、再作成の繰り返しとなった。

limitsの値を実際に超えてしまうと、強制的に終了され、再作成のスケジューリングが実行されることがわかった。

CPUの場合、どうなるのかも試してみた。

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  namespace: wakashiyo
  name: cpu-over-pods
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.12
          ports:
            - containerPort: 80
          resources:
            requests:
              memory: 50Mi
              cpu: 5m
            limits:
              memory: 100Mi
              cpu: 10m

デプロイ後に、apach bench で負荷をかけてみた。(ingress , nodeportもデプロイしている)

NAME                             READY   STATUS    RESTARTS   AGE
cpu-over-pods-5579fd4fdd-v97zr   1/1     Running   0          3m16s

CPUのときも強制終了するかもしれないと期待したが、何も変化がなかった。。。(もしかしたらやり方が悪かった?)

あらかじめ、ノードより大きいリソースを割り当てられる設定を行うとスケジューリングされないとわかったが、

実際に使用している間にlimitsを超えたりするとどうなるのかがわかった。

オートスケールしてみる(hpaを使ってみる)

前述のリソースの制限にはLimitRangeやCluster Auto Scaler、Resource Quotaなどもあるが今回は割愛する。

hpa(Horizontal Pod AutoScaler)はCPU負荷に応じて、Deployment, ReplicaSet, Replication Controllerのレプリカ数を自動的にスケールしてくれるとても便利なリソースだ。

内部的には30秒に1回オートスケールするべきかのチェックが行われるらしい。

その際に実際に使用しているCPU使用率と

どれくらのCPU使用率に抑えたいか(targetAverageUtilization)

この2つの値を用いて必要なレプリカ数を算出し、スケールアウトしたり、スケールインしてくれる。

算出する計算式は、ドキュメントに記載されている。

Horizontal Pod Autoscaler - Kubernetes

実際にデプロイしてみた

apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  namespace: wakashiyo
  name: wakashiyo-dev-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: wakashiyo-dev-deployment
  minReplicas: 1
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        targetAverageUtilization: 30

hpaを適用するdeployment

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  namespace: wakashiyo
  name: wakashiyo-dev-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.12
          ports:
            - containerPort: 80
          resources:
            requests:
              memory: 50Mi
              cpu: 5m
            limits:
              memory: 75Mi
              cpu: 10m

今回はスケールするのを簡単にみたいため、cpuのresourcesを小さくした。

targetAverageUtilization: 30ということで目標のCPU使用率平均の値を30%にした

これをapach benchで負荷をかけてみた

pods(負荷前)

% kubectl get pods -n wakashiyo
NAME                                        READY   STATUS    RESTARTS   AGE
wakashiyo-dev-deployment-6bdb498876-89dk2   1/1     Running   0          17m

pods(負荷後)

% kubectl get pods -n wakashiyo
NAME                                        READY   STATUS    RESTARTS   AGE
wakashiyo-dev-deployment-6bdb498876-5zpxf   1/1     Running   0          2m4s
wakashiyo-dev-deployment-6bdb498876-774dv   1/1     Running   0          3m34s
wakashiyo-dev-deployment-6bdb498876-89dk2   1/1     Running   0          17m
wakashiyo-dev-deployment-6bdb498876-c9ps5   1/1     Running   0          3m4s
wakashiyo-dev-deployment-6bdb498876-f7rl2   1/1     Running   0          3m34s
wakashiyo-dev-deployment-6bdb498876-g95xg   1/1     Running   0          3m4s
wakashiyo-dev-deployment-6bdb498876-p64wc   1/1     Running   0          2m4s
wakashiyo-dev-deployment-6bdb498876-pp9sf   1/1     Running   0          3m4s
wakashiyo-dev-deployment-6bdb498876-qqdms   1/1     Running   0          3m34s
wakashiyo-dev-deployment-6bdb498876-tjxh9   1/1     Running   0          2m4s

hpa

 % kubectl get horizontalpodautoscalers.autoscaling -n wakashiyo --watch
NAME                REFERENCE                             TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
wakashiyo-dev-hpa   Deployment/wakashiyo-dev-deployment   20%/30%   1         10        1          10m
NAME                REFERENCE                             TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
wakashiyo-dev-hpa   Deployment/wakashiyo-dev-deployment   0%/30%    1         10        1          10m
wakashiyo-dev-hpa   Deployment/wakashiyo-dev-deployment   100%/30%   1         10        1          11m
wakashiyo-dev-hpa   Deployment/wakashiyo-dev-deployment   100%/30%   1         10        4          11m
wakashiyo-dev-hpa   Deployment/wakashiyo-dev-deployment   200%/30%   1         10        4          11m
wakashiyo-dev-hpa   Deployment/wakashiyo-dev-deployment   200%/30%   1         10        7          12m
wakashiyo-dev-hpa   Deployment/wakashiyo-dev-deployment   26%/30%    1         10        7          12m
wakashiyo-dev-hpa   Deployment/wakashiyo-dev-deployment   46%/30%    1         10        7          12m
wakashiyo-dev-hpa   Deployment/wakashiyo-dev-deployment   46%/30%    1         10        10         13m
wakashiyo-dev-hpa   Deployment/wakashiyo-dev-deployment   48%/30%    1         10        10         13m
wakashiyo-dev-hpa   Deployment/wakashiyo-dev-deployment   20%/30%    1         10        10         13m

上記のように、CPU使用率の目標値を上回ってから、 podがオートスケールされていき、

目標値に近づいてきた。実際に正確な目標値におさまることは確認できなかったが、CPU使用率に応じて自動でスケールされることがわかった。

一つ気になること

hpaを使用することでオートスケールするのが確認できたが、実際ノードに割り振られているpod数を確認すると以下のようになっていた

% kubectl get pods -o wide -n wakashiyo
NAME                                        READY   STATUS    RESTARTS   AGE     IP           NODE                                       NOMINATED NODE   READINESS GATES
wakashiyo-dev-deployment-6bdb498876-5zpxf   1/1     Running   0          5m35s   10.48.2.11   gke-wakashiyo-default-pool-8b127224-zx8q   <none>           <none>
wakashiyo-dev-deployment-6bdb498876-774dv   1/1     Running   0          7m5s    10.48.2.6    gke-wakashiyo-default-pool-8b127224-zx8q   <none>           <none>
wakashiyo-dev-deployment-6bdb498876-89dk2   1/1     Running   0          20m     10.48.2.4    gke-wakashiyo-default-pool-8b127224-zx8q   <none>           <none>
wakashiyo-dev-deployment-6bdb498876-c9ps5   1/1     Running   0          6m35s   10.48.2.7    gke-wakashiyo-default-pool-8b127224-zx8q   <none>           <none>
wakashiyo-dev-deployment-6bdb498876-f7rl2   1/1     Running   0          7m5s    10.48.1.4    gke-wakashiyo-default-pool-8b127224-4pbq   <none>           <none>
wakashiyo-dev-deployment-6bdb498876-g95xg   1/1     Running   0          6m35s   10.48.2.9    gke-wakashiyo-default-pool-8b127224-zx8q   <none>           <none>
wakashiyo-dev-deployment-6bdb498876-p64wc   1/1     Running   0          5m35s   10.48.2.10   gke-wakashiyo-default-pool-8b127224-zx8q   <none>           <none>
wakashiyo-dev-deployment-6bdb498876-pp9sf   1/1     Running   0          6m35s   10.48.2.8    gke-wakashiyo-default-pool-8b127224-zx8q   <none>           <none>
wakashiyo-dev-deployment-6bdb498876-qqdms   1/1     Running   0          7m5s    10.48.2.5    gke-wakashiyo-default-pool-8b127224-zx8q   <none>           <none>
wakashiyo-dev-deployment-6bdb498876-tjxh9   1/1     Running   0          5m35s   10.48.2.12   gke-wakashiyo-default-pool-8b127224-zx8q   <none>           <none>
ノード Pod数
A 9
B 1
C 0

1つのノードでは全くPodが割り当てられていないことがわかった。

これで9台割り当てられているリソースで枯渇が起きたら、効率的にクラスタを利用できていないし、パフォーマンスも発揮できない。

できるのならCのノードに何台かPodを割り当てたい。

deschedulerを使用する

deschedulerとは?

GitHub - kubernetes-sigs/descheduler: Descheduler for Kubernetes

一部のノードが使用されていなかったり、使用されすぎたりしてしまっているとき、

ノードが新たに追加されたとき、

ラベルがノードに追加されたり、削除されたりして、今まで適用されていたNode Affinityの条件を満たさなくなってしまった

ノードに障害が起こり、他のノードに移動した

など、クラスタの状態が動的に変化するにもかかわらず、kube-schedulerは配置後の見直しは行わないので、

このdeschedulerによって意図しないPodのスケジューリングを見つけ、再配置をしてくれる。というこれまた便利なものだ。

実際に、スケジューリング自体はkube-systemが行い、deschedulerが行うのは、移動可能なPodを見つけ排除するところまでを行う。

そのため、スケジューリング自体はデフォルトのkube-systemに依存している。

deschedulerは5つのポリシーと戦略から構成される。(RemovePodsViolatingNodeTaints こちらについては今回省略する。)

これらは全てデフォルトではenabledになっているとのこと。

RemoveDuplicates

これは、Deployment, ReplicaSet, ReplicationController, Jobによって動いているPodが同じノード内で1台だけであることを示す。

Podが重複していたら、クラスター内のPodの展開を改善するために排除される。

例としては、あるノードが障害が落ちてしまい、他のノードに移動したとする。 その後、障害していたノードが復活した際に、復活したノードに再配置されるように、障害時に移動して、重複状態のPodを削除する。

LowNodeUtilization

これは、使用されていないノードを見つけ、使用されすぎているノードからPodを削除する。

その上で、使用されていないノードに削除されたPodがスケジュールされるようにする。

nodeResourceUtilizationThresholds というパラメータを使用して、設定を行う。

nodeResourceUtilizationThresholds.thresholds は、

使用されていないノードの条件を設定する。cpu, memory, Pod数から決める。ここで設定した値を下回ると(3つ全て)使用されていないノードとみなされ、他のノードで削除されたPodがあった際に、スケジュール対象ノードになる。

nodeResourceUtilizationThresholds.targetThresholds は、

使用されすぎているノードの条件を設定する。cpu, memory, Pod数から決める。ここで設定した値を上回ると、そのノードに割り当てられているPodが削除される。

thresholdsとtargetThresholdsの間にあるノードはどちらの対象のノードにならない。

もう一つ numberOfNodes というパラメータも存在する。

これは、使用されていないノード数が numberOfNodes で設定した値を超えるとこの LowNodeUtilizationのポリシーが有効になるというもの。

これはデフォルトでは、0になっていて、

大規模なクラスタで運用していて、何台かのノードが、しばしば使用されなさすぎな状態になるとき、あるいは短期間使用されなさすぎな状態になるということがあると役立つらしい。

RemovePodsViolatingInterPodAntiAffinity

これは、anti affinityのルールに違反しているPodをそのノードから削除してくれるもの。

例えば、Pod A, B , C が同じノードで動いているとする。 後から、PodB, PodC にPodAとは同じノードで動いてはいけないというanti affinityルールを付与したとする。 このとき、PodAが削除される。

RemovePodsViolatingNodeAffinity

これは、node affinityのルールに違反しているPodをそのノードから削除してくれるもの。

例えば、ノードAではPodAがスケジュールされるために必要なnode affinityを満たしていて、PodAがノードAで動いていたとする。 時が経って、ノードAではその条件を満たさなくなり、ノードBで条件を満たす状態になったとする。 この時、PodAが削除される。

使ってみる

使用方法は、

GitHub - kubernetes-sigs/descheduler: Descheduler for Kubernetes

README にも記載している。

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: descheduler-cluster-role
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "watch", "list"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "watch", "list", "delete"]
  - apiGroups: [""]
    resources: ["pods/eviction"]
    verbs: ["create"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: descheduler-sa
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: descheduler-cluster-role-binding
subjects:
  - kind: ServiceAccount
    name: descheduler-sa
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: descheduler-cluster-role
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
data:
  policy.yaml: |
    apiVersion: "descheduler/v1alpha1"
    kind: "DeschedulerPolicy"
    strategies:
      "RemoveDuplicates":
         enabled: true
      "RemovePodsViolatingInterPodAntiAffinity":
         enabled: true
      "LowNodeUtilization":
        enabled: true
          params:
            nodeResourceUtilizationThresholds:
              thresholds:
                "cpu" : 10
                "memory": 10
                "pods": 0
              targetThresholds:
                "cpu" : 25
                "memory": 25
                "pods": 5
kind: ConfigMap
metadata:
  name: descheduler-policy-configmap
  namespace: kube-system

job

apiVersion: batch/v1
kind: Job
metadata:
  name: descheduler-job
  namespace: kube-system
spec:
  parallelism: 1
  completions: 1
  template:
    metadata:
      name: descheduler-pod
      annotations:
        scheduler.alpha.kubernetes.io/critical-pod: ""
    spec:
      containers:
        - name: descheduler
          image: gcr.io/wakashiyo-playground/descheduler:0.0.1
          volumeMounts:
            - mountPath: /policy-dir
              name: policy-volume
          command:
            [
              "/bin/descheduler",
              "--policy-config-file",
              "/policy-dir/policy.yaml",
              "-v",
              "1",
            ]
      restartPolicy: "Never"
      serviceAccountName: descheduler-sa
      volumes:
        - name: policy-volume
          configMap:
            name: descheduler-policy-configmap

これを適用したが、Podがうまく割り振られたかというと上手くいかなかった。

nodeのリソースをそれぞれ確認すると、

1台も割り当てられていないPodというのは、既にkube-systemで結構リソースが使用されていた。

だから、 LowNodeUtilization.nodeResourceUtilizationThresholds の設定がよくなかったのかもしれない。

元々、そのリソース使用量だったから、偏ったPodの配置をkube-schedulerは行ったのかもしれない。。。(それだったら、kube-systemは適切な配置を行ったとも言うことができる。。。)

この辺よくわからなくなってきたので、今日はここまでにして、もっと調査したりして、理解を深めていきたいと思う。