Kubernetes勉強会第6回 〜Kubernetesの実行と管理、CustomResourceDefinitions(CRD)、Container Runtime Interface(CRI)〜

Kubernetes in Action

Kubernetes in Action

「Kubernetes in Action」を読んで学んだ結果を社内で共有しました。 その第6回の内容です。

Kubernetesの実行と管理をしやすくするには

コンテナのイメージを管理しやすくする

imageのサイズは小さくし、不要なファイルやツールは含めないように。

イメージに適切にタグをうち、imagePullPolicyを賢く使う

docker imageのtagにはlatestが使えるが、podのyamlファイルにはlatestを指定しないようにしよう。 latestを指定しているともしimageを更新しても、新しくscheduleするpodにはその新しいimageが使用されるが、既存のpodのコンテナのimageは古いバージョンのまま。 またlatestタグを使うと1つ前のバージョンへrollbackができない。

もしあなたがimageのタグにmutable tag(同じタグを使い続けること)を使うならpodのspecのimagePullPolicyはAlwaysにしなければならない。 しかしそれはproductionでは大きな落とし穴になる。 もしAlwaysを設定したら、新しいpodがscheduleするたびにcontainer runtimeは毎回container registryに問い合わせることになる。 これはpodが起動するのが少し遅くなる。なぜならnodeは毎回imageが更新されているかチェックする必要がでてくるため。またcontainer registryが応答がない場合、podが起動するのを妨げてしまう。

複数の情報をLabelに含める

podだけではなく他のリソースにもlabelをつけることを忘れないようにしよう。 各リソースに複数のlabelをつけるようにすれば、リソースの取得が簡単になる。 labelには例えば以下のものがよいだろう。

  • リソースが属しているアプリケーション(もしくはマイクロサービス)の名前
  • Enviroment(development, QA, staging, production 等)
  • バージョン
  • リリースのタイプ(stable, canary, green or blue for green/blue deployments等)
  • テナント(あなたがもしpodをnamespaceを使用する代わりに各テナントで動かしている場合)
  • シャードシステムならそのシャード

annotationを通して各リソースを表現する

他の追加情報はannotationに含めましょう。 たとえばそのアプリケーションの責任を持っている人の情報とか。 もしマイクロサービスであれば、そのpodが使用している他のサービスの名前とか。 バージョン情報とか、ツールやGUIツールが使用するmetadataとか。

なぜプロセスが終了したのかの情報を提供する

Kubernetesにはなぜコンテナが終了したのかの情報を含めることができます。 コンテナの /dev/termination-log に終了した理由を書き込んでおけば、 kubectl describe podしたときにContainersのLast StateのMessageに表示されます。

アプリケーションログをハンドリングする

アプリケーションログは標準に出すべき。 そうすればkubectl logsで見れる。 一つ前のpodのlogはkubectl logs --previousでみれる。

特定のログファイルにはいていたら、 $ kubectl exec <pod> cat <logfile> でみれる。 または、pod内にコンテナが1つなら $ kubectl cp foo-pod:/var/log/foo.log foo.log でログをローカルへコピーすることができる。 複数のコンテナが実行されていたら -c containerName オプションでコンテナを指定しよう。

Kubernetesはログ集約システムは提供していない。 しかしそれをログ集約システムをデプロイするのは簡単。 Google Kubernetes EngineならEnable Stackdriver Logging checkbox にチェックを入れればいい。

EFK(ElacsticSeach, Fluentd, kibana)のようなスタックをデプロイすれば使うこともできる。 fluentdのようなログ収集ミドルウェアは複数行ログ(たとえばJavaスタックトレースのような)は1行1行別のログになってしまうので、jsonとして出力するのがよい。 ただjsonログは人間には見にくいので(kubectl logsした場合)、 標準出力にはplain textにして、特定のログファイルにはjsonで出力するのがよい。

カスタムリソースを作成する

独自のカスタムリソースを定義し、Kubernetesを拡張することができる

CustomResourceDefinitions

新しいリソースを作成するためには、CustomResourceDefinition objectを定義する必要がある。 CustomResourceDefinitionを定義すれば、他のリソースと同様に、yamljsonを通してそのinstanceを作成することができるようになる。 以下が定義例。以下ではWebsiteという名前のリソースを新たに定義している。

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: websites.extensions.example.com
spec:
  scope: Namespaced
group: extensions.example.com
version: v1
names:
  kind: Website
  singular: website
  plural: websites

このリソースのinstanceを作成する場合は以下。

apiVersion: extensions.example.com/v1
kind: Website
metadata:
  name: kubia
spec:
  gitRepo: https://github.com/luksa/kubia-website-example.git

そして新しく定義したこのリソースに対するcontrollerを実装する必要がある。

実装例 https://github.com/luksa/k8s-website-controller

controllerもpodとして動かすことができる。

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: website-controller
spec:
  replicas: 1
  template:
metadata:
  name: website-controller
  labels:
    app: website-controller
spec:
  serviceAccountName: website-controller
  containers:
  - name: main
    image: luksa/website-controller
  - name: proxy
    image: luksa/kubectl-proxy:1.6.2

custom objectのバリデートはKubernetes1.8でアルファ機能として導入された。 API serverで バリデートするためにはCustomResourceValidation機能を有効にする必要がある。

custom objectのためのcustom API serverを提供する

Kubernetesでcustom objectのサポートを提供する良い方法は、自分のAPI serverを実装し、clientがそのAPI serverとやり取りさせること。 Kubernetes 1.7から、KubernetesのデフォルトのmainのAPI Serverと自分で作成したAPI serverを統合することができるようになった。 複数のAPI Serverがあっても1つのAPIServerとして扱うことができる。

custom api serverを登録するには、custom api serverをpodとしてdeployしserverを通して公開すればよい。

Service Catalogを使ってKubernetesを拡張する

Service Catalogとは名前の通りserviceのカタログで、Service Catalog機能を使えば、カタログの中からサービス(アプリケーション)を選んで、pod,service,configmap等を直接扱うこと無く簡単にデプロイすることができるようになる。

Service Catalogは以下のリソースを提供している。

  • ClusterServiceBroker
    • サービスを提供する(外部)システムについて定義したもの
  • ClusterServiceClass
    • 提供されるサービスについて定義したもの
  • ServiceInstance
    • 提供されたサービスのinstance
  • ServiceBinding
    • はServiceInstanceとclients(pods)のセットを結びつけるもの

Kubernetesは、提供されるサービスのリストをbrokerへ問い合わせ、ClusterServiceClassリソースを作成する ユーザが提供されるサービスが必要な場合、KubernetesはServiceInstanceを作成し、ServiceInstanceをpodsに結びつける(bindingする) それらのpodsに必要な認証情報や接続に必要な情報をsecretを通して渡す。

Service Catalog API server と Controller Manager とは

Service Catalogは以下の3つのコンポーネントからなる分散システムである。

  • Service Catalog API Server
  • etcd as the storage
  • Controller Manager, where all the controllers run

Service Brokers と the OpenServiceBroker API とは

クラスタの管理者は1つ以上の外部ServiceBrokerをService Catalogに登録することができる。 各brokerはOpenServiceBroker APIを実装する必要がある。

Service Catalogがサービスの一覧を取得したあと、Service Catalog は各ClusterServiceClassリソースを生成する。各ClusterServiceClassリソースは、サービスの1つのタイプが定義されている(例えばあるClusterServiceClassは“PostgreSQL database”)。 各ClusterServiceClassは、1つ以上のservice planを持っている。ユーザは必要とするそのサービスレベルを選択することができる。たとえばdatabase ClusterServiceClassはHDDのfreeプランや、SSDのPremiumプランを提供したりするだろう。

Serviceの提供と使用

実際にserviceが提供されるためには、service instanceを作成する必要がある。 例:

apiVersion: servicecatalog.k8s.io/v1alpha1
kind: ServiceInstance
metadata:
  name: my-postgres-db
spec:
  clusterServiceClassName: postgres-database
  clusterServicePlanName: free
  parameters:
    init-db-args: --data-checksums

重要なのは、このservice(これだとpostgres-database)は、Kubernetesクラスタ外に作成されてもいいということ。なので例えばクラウド上のDBでもよい。 serviceへの接続情報はServiceBindingリソースを使ってsecretを通して与えられる。

例:

apiVersion: servicecatalog.k8s.io/v1alpha1
kind: ServiceBinding
metadata:
  name: my-postgres-db-binding
spec:
  instanceRef:
    name: my-postgres-db
  secretName: postgres-secret #ここに好きなsecret名を指定

将来的にはPodPresetsというKubernetesの機能が導入される予定。

Service CatalogはServiceBindingで指定した名前で新しいSecretを生成し、必要な情報をsecret内に保存する。 あとはpodからそのsecretをmountして使えば、podが接続情報を得ることができる。

Summary

  • CustomResourceDefinition objectを作成することで、Custom resourcesをAPI Serverに登録することができる
  • API Serverのコードを変更すること無く、CustomObjectのインスタンスを保存、取得、更新、削除することができる
  • custom objectsを実際にKubernetes上で動くようにCustom Controllerを実装することができる
  • API aggregation機能を通し、Custom API Serverを使うことでKubernetesを拡張することができる
  • Kubernetes上に構築されたPasSはコンテナを使ったアプリケーションを構築するのが楽になる
  • パッケージマネージャーのhelmは既存のアプリケーションをresource manifestsを必要とせずにデプロイすることができる

複数クラスターをkubectlで扱う

以下のように環境変数KUBECONFIGにコロン区切りでクラスタファイルを指定すると複数のファイルを読み込むことができ、つまり複数クラスタを扱うことができる。

KUBECONFIG=~/.kube/config1:~/.kube/config2

kubeconfigの構成を理解する

apiVersion: v1
clusters:
- cluster:
    certificate-authority: /home/luksa/.minikube/ca.crt
    server: https://192.168.99.100:8443
  name: minikube
contexts:
- context:
    cluster: minikube
    user: minikube
    namespace: default
  name: minikube
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
  user:
    client-certificate: /home/luksa/.minikube/apiserver.crt
    client-key: /home/luksa/.minikube/apiserver.key

上記のようにKubeconfig fileは以下で構成されている。

  • clustersのリスト
  • contextsのリスト
  • current contextの名前
  • usersのリスト

それぞれの詳細

cluster

  • Kubernetes clusterを表している。API ServerのURL、certificate authority(CA)ファイル、他には必須ではないがAPI serverと通信するためのいくつかのオプション。 CA certificateは分割して保存できそれをkubeconfigファイル内から指定できる。また直接certificate-authority-dataフィールドに含めることも出来る。

user

  • どのuserもAPI Serverと通信する時に使うcredentialsを定義する。credentialsには以下が使える
    • usernameとpassowrd
    • authentication token
    • client key and certificate
  • client key and certificateはkubeconfig fileに含めることができる。もしくはファイルを分割してconfig file内で指定することができる。以下のように
users:
- name: minikube
  user:
    client-certificate: /home/luksa/.minikube/apiserver.crt
    client-key: /home/luksa/.minikube/apiserver.key

context

  • contextはclusterとuserを紐付けるもの。またkubectlで使われるデフォルトのnamespaceを指定できる。複数のcontextで、同じuserや同じclusterを指定することができる。

他のcontainer runtimesを使用する

Dockerの代わりにrktを使う

container runtimeとしてdockerのほかにrktがある。 rktはデフォルトでPod(複数の関連するコンテナを実行する)の概念をサポートしている。 rktはDocker-formatted container imageをそのまま実行することができるのでリパッケージは必要がない。 Kubernetesでrktを使うためにはkubeletに--container-runtime=rktオプションを渡してあげればよい。 なおkubeletはContainer Runtimeとやりとりする唯一のコンポーネント

他のcontainer runtimesをCRIを通して使う

Kubernetes 1.5 から Container Runtime Interface(CRI) が導入された。 CRIはplugin APIで他のcontainer runtimeを統一的に操作することができる。 kubeletはDockerやrktをCRIを通して操作する。

新しいCRI実装として、CRI-Oというものもでてきた。 参考: kubernetes用コンテナランタイムcri-oを試す

CRI-Oの開発の背景には、dockerの開発速度が早い中、それに依存するkubernetesが不安定になっており、より小さく単純な、専用のランタイムを用意することでkubernetesの安定化に繋げたいという意図があったようです。

また、Kubernetesをコンテナの代わりにVM上でアプリケーションを動かせる新しいCRI実装が開発中。 そのうちの一つはFrakti(The hypervisor-based container runtime for Kubernetes.)というもの。

ほかには Mirantis VirtletというCRI実装があり、これは、docker imageの代わりに実際にVM imagesを動かすというもの。

Cluster Federation

Kubernetesは複数のKubernetesを1つのKubernetesとして扱うための Cluster Federationという機能がある。 これにより、Kubernetes Clusterを複数のdata center、複数のアベイラビリティゾーン、もしくは複数のクラウド冗長化することができる。

※ただ、Federation(の最初のversion)はあまりいけていないらしく、SIG-Multiclusterで仕切り直し中とのこと。

Kubernetes勉強会第5回 〜node affinity, pod affinity、Init containers、lifecycle hooks〜

Kubernetes in Action

Kubernetes in Action

「Kubernetes in Action」を読んで学んだ結果を社内で共有しました。 その第5回の内容です。

node affinity, pod affinity

Kubernetesのnode affinity, pod affinityについて

Summry

  • もしnodeにtaintsをセットすると、podがそのtaintsを許容できるtolerationsをセットしていない限りそのnodeにそのpodはscheduleされない。
  • taintsは3タイプあり、NoScheduleは完璧にscheduleされない。 PreferNoScheduleはそこまで厳密ではない。NoExecuteはnode上の実行中のpodであっても追い出す。
  • NoExecute taintは、nodeがunreachableやunready状態になった場合podがどのくらいの時間schedulingされるのを待つか指定できる。
  • Node affinityはどのnodeにpodがscheduleされるべきか指定できる。
  • Pod affinityは同じaffinity ruleをもったpodが実行されているnodeにpodをscheduleすることができる
  • Pod anti-affinity はお互いのpodを避けさせることができる

Init containers

Init containersを使うと、Pod内のコンテナを起動する前に特定の順番で特定の処理を行うことができる。

https://qiita.com/tkusumi/items/f64fff37a724b86d9819#init-containers-alpha

Pod内で初期化処理をするコンテナを指定をできる機能です。Podの定義にinitContainersという項目で、従来のcontainersで指定したコンテナの前に起動するコンテナを指定できるようになるようです。複数のコンテナを指定することができますが、containersがパラレルに起動されるのに対してinitContainersは指定した順番に1つずつ起動していくようです。

使用用途としては例えば以下のようなものが挙げられます。

  • データを動的にダウンロードしてくる
  • データベーススキーマの反映
  • アプリケーションが何らかの事前条件を満たすまでウェイトする

Init containersを使えば事前条件が満たされるまでpodのmainコンテナが起動するのを遅らせることができます。 readiness probesを設定するのを忘れてはいけません。 アプリケーションがまだ準備段階ではない場合にserviceにpodが追加されるのを防ぐことができますし、 Deployment controllerが、bad versionをrolloutするのを防ぐためにも使用されます。

もしInit Containerの実行がエラーになった場合、KubernetesはInit Containerの実行が成功するまでpodを再起動します。 しかしもしrestartPolicyNeverに設定していたなら再起動しません。

例:

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox
    command: ['sh', '-c', 'echo The app is running! && sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox
    command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
  - name: init-mydb
    image: busybox
    command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']

参考: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/

lifecycle hooks

podのlifecycleに以下の2つのhookを定義することができる。これらをlifecycle hooksと呼ぶ。

  • Post-start hooks
  • Pre-stop hooks

pod全体に設定するinit containersと違って、lifecycle hooksはpodのコンテナごとに設定できる。 lifecycle hooksはcontainerがstartするときとstopするときに実行される。

lifecycle hooksは、以下の点でlivenes、 readiness probesと似ている。

  • containerの内部で実行されるコマンド
  • URLに対してHTTP GET Requestとして実行される

Post-start hooks

Post-start hookはコンテナのmain processが実行された後すぐに実行される。 もしコンテナのアプリケーションとは別のアプリケーションを動かしたい場合、あなたがそのアプリケーションの開発者ならアプリケーション内で別のアプリケーションを動かすことができるが、アプリケーションの開発者ではない場合、post-start hookは役に立つ。 Post-start hookはmain processと平行して実行されるが、以下の点でcontainerに影響を与える まずPost-start hookの実行が完了するまで、containerのstateWaitingのままでありreasonContainerCreatingのまま。そのためpodのstatusはRunningではなくPendingのまま。 もしPost-start hookが実行に失敗したりnon-zero exit statusを返した場合、main containerはkillされる。

以下が定義例。

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-poststart-hook
spec:
  containers:
  - image: luksa/kubia
    name: kubia
    lifecycle:
      postStart:
        exec
          command:
          - sh
          - -c
          - "echo 'hook will fail with exit code 15'; sleep 5; exit 15"

hookによって開始されたprocessが標準出力に出力してもそれをみるすべはない。そのため永続的volumeに書き込むこと。

Pre-stop hooks

Pre-stop hooksはコンテナが終了する前にすぐに呼び出される。つまりコンテナが終了するのをブロッキングする。 なのでコンテナを削除する呼び出しが送られる前にPre-stop hooksは終了しなければならない。 またPre-stop hooksには何のパラメーターも渡されない。 Podの終了時の振る舞いの詳細はTermination of Podsを参照すること。

Pre-stop hooksの実行タイミング等は以下の記事がとても詳しいです。 Kubernetes: 詳解 Pods の終了

以下が定義例。コンテナが終了する前に http://POD_IP:8080/shutdown へリクエストが送信される。

apiVersion: v1
kind: Pod
metadata:
  name: pre-stop-hook-httpget-pod
spec:
  containers:
  - image: luksa/kubia
    name: kubia
    lifecycle:
       preStop:
         httpGet:
            port: 8080
            path: shutdown

また以下のように定義すると、shellコマンドが実行される。

apiVersion: v1
kind: Pod
metadata:
  name: pre-stop-hook-shell-pod
spec:
  containers:
  - image: luksa/kubia
    name: kubia
    lifecycle:
       preStop:
         exec:
           command: ["sh", "-c", "sleep 1; nginx -s quit; sleep 5"]

参考: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/

システムリニューアルで重要視していること

自分は今の会社に入社してから、PHPで書かれたシステムをRubyで書かれたシステムへリニューアルするプロジェクトをやっています。 PHPで書かれたシステムが122万行くらいあって、DBのテーブルも300以上あるのでそう簡単にはリニューアルはできません。 またただ書き直すだけでは意味がないので、アーキテクチャから見直したりDB設計を見直したりしてます。 で、やっと主要機能のリニューアルが終わってきて、だいたい全体の半分くらいリニューアルが終わりました。 リニューアルの仕方は画面or機能ごとにRubyに書き換えて移行をしています。なのでPHPのシステムと並行稼動しているわけです。 あとRubyへリニューアル作業をしているのは初期は数人でしたが、今では大半のエンジニアがPHPよりもRubyで書くようになりました。

ということで自分がPHPからRubyへのシステムリニューアルで重要視していることをまとめてみました。(正確には社内で発表する機会があったのでそれをもとにこの記事を書いている) 書いてあるのは当たり前のことなので主に自分への備忘録目的です。

アーキテクチャから見直す

  • アーキテクチャが適切でなければリニューアルしても不適切なシステムが出来上がるだけ
  • アーキテクチャにはDB設計も含まれる
  • DB設計が間違っていればアプリケーションは正しいものにはなりえない

技術的負債に対して明確な殺意をもつ

  • 技術的負債はもちろん返却していく
  • その際に絶対に保守的にならない
  • 保守的というのは、「ここを大きく変えると他も直さないといけなくて大変だからそっとしておこう」とか「この仕様がよくわからないけど消したら怖いから残しておこう」とか
  • 保守的になったらこのプロジェクトは終わり
  • 常にアグレッシブでいること

メンテナンス性

メンテナンス性はいくつかの要素があるが以下の2つを大切にしている。

  • 一貫性を大切に
    • 一貫性があるシステムはそれだけで生産性が
高い
    • 「一貫性のあるスタイルは”正しい”スタイルよりも大切」By リーダブルコード
    • 例えばエンジニアがそれぞれ考える”正しさ”を持ち寄ると、バラバラのプログラムが出来上がってしまう
  • シンプルに
    • もし何か判断に迷ったらシンプルな方にたおす

仕様の整理

  • 正しい仕様をもう一度考える
  • 複雑性のわりにユーザへの貢献度が低い機能や仕様を削る
  • 何のためにあるのかよく分からない仕様も思い切って捨てる
  • もちろんディレクターやCSの人と相談したり合意を取ることは必要

継続的な開発

  • リニューアルして終わりではなく継続的な開発ができるようにしていく
  • 継続的にリファクタリングしたり
  • 継続的に不要となった機能を消したり
  • 継続的にバージョンアップしたり
  • 継続的にモダンな技術を取り入れたり

おまけ: レガシーシステムを憎みすぎない

古いプログラムや技術的負債と向き合っていると、 ついつい憎んでしまいます。 しかし憎みすぎると既存の仕様や仕組みをガラッと変えるような判断をしがちです(坊主憎けりゃ袈裟まで憎い)。 レガシーシステムの機能や仕様の中にも不適切な部分と適切な部分があります。その適切な部分は活かすべきです。 慈悲の心を持って、きちんと何が不適切な部分なのか冷静&客観的になって見極めるようにしていきましょう。

Kubernetes勉強会第4回 〜cAdvisor、Heapster、podとnodeのauto scaling、taintsとtolerations〜

Kubernetes in Action

Kubernetes in Action

「Kubernetes in Action」を読んで学んだ結果を社内で共有しました。 その第4回の内容です。

cAdvisor

kubeletには内部にcAdvisorというものを持っている。 cAdvisorはnode自体のリソース使用量及びnode上で実行されているコンテナのリソース消費量のデータを収集する役割を担っている。 それらのデータをクラスタ全体で集約するためのHeapsterとよばれるコンポーネントがある。 Heapsterは全nodeのうち1つのnode上でpodとして動作する。 Heapsterはserviceを通して公開される。

minikubeの場合は以下のコマンドでheapsterを有効にできる。 minikube addons enable heapster

heapsterが実行されていれば、 kubectl top node でその集約データ結果からnodeのリソース使用量がみれる。 (minikube addons enable heapsterを実行した直後であれば少し待つ必要がある)

$ kubectl top node
NAME       CPU(cores)   CPU%      MEMORY(bytes)   MEMORY%
minikube   170m         8%        556Mi           27%

# またpodごとのリソース消費量もみれる
$ kubectl top pod
NAME           CPU(cores)    MEMORY(bytes)
requests-pod   21m           19Mi

$ kubectl top pod --all-namespaces
NAMESPACE     NAME                                    CPU(cores)   MEMORY(bytes)
default       requests-pod-2                          21m          19Mi
kube-system   kube-scheduler-minikube                 11m          13Mi
kube-system   kube-controller-manager-minikube        40m          34Mi
kube-system   heapster-bmk4j                          0m           16Mi
kube-system   kubernetes-dashboard-5498ccf677-5gjhz   0m           14Mi
kube-system   kube-addon-manager-minikube             10m          19Mi
kube-system   kube-apiserver-minikube                 33m          232Mi
kube-system   etcd-minikube                           18m          32Mi
kube-system   kube-dns-86f4d74b45-hkn2d               1m           25Mi
kube-system   kube-proxy-xnfjc                        2m           15Mi
kube-system   influxdb-grafana-tnbzw                  1m           24Mi
kube-system   storage-provisioner                     0m           33Mi

グラフィカルにリソースをモニタリングしたい場合、GKEならGoogle Cloud Monitoringが使える。 minikubeやオンプレの場合は、データの保存にInfluxDB, ビジュアリングにGrafanaが使える。

Heapster, InfluxDB, Grafanaを試したい方は以下をご参考下さい。 KubernetesをHeapster + InfluxDB + Grafanaでモニタリングする

podのリソースを管理するためのまとめ

  • Resource Requestsを指定することはpodをscheduleするときに役に立つ
  • Resource Limitsを指定することはpodが他のpodのリソースを使い過ぎることを防ぐ
  • 使われていないCPU時間はコンテナのRequestsに基づいて割り当てられる
  • コンテナはCPUを使い過ぎても決してkillされないが、メモリを使い過ぎようとしたらkillされる
  • QoSと実際のメモリ使用量に基づいて、より重要なpodのために他のpodが消されることもある
  • LimitRangeリソースを定義することによって、それぞれのpodに対して、Resource RequestsとResource Limitsの最小値,最大値, デフォルト値を設定することができる
  • ResourceQuotaリソースを定義することによって、namespace内のpodのResource Requests, Resource Limitsの総量に対して最小, 最大を設定することができる
  • podに適切なResource Requests, Resource Limitsを指定するためには、長い間モニタリングをする必要がある

podとnodeのauto scaling

Kubernetesのauto scalingについて

taintsとtolerations

Kubernetesのtaintsとtolerationsについて

Kubernetes勉強会第3回 〜Resource RequestとResource Limit、pod QoSクラス、LimitRangeリソース、ResourceQuotaリソース〜

Kubernetes in Action

Kubernetes in Action

「Kubernetes in Action」を読んで学んだ結果を社内で共有しました。 その第3回の内容です。

podの使用リソースを管理する

Resource RequestとResource Limit

KubernetesのResource RequestとResource Limit

pod QoSクラス

KubernetesのQuality of Service(QoS)クラスについて

LimitRangeリソース

Resource RequestsResource Limitsを設定していないと、QoSクラスがBestEffortになってしまいkillされやすくなってしまう。それを避けたいならそれぞれのコンテナにResource RequestsResource Limitsを設定する必要がある。

LimitRangeリソースを定義すると、そのLimitRangeリソースが属しているnamespace内にpodを作成するときに、pod内でResource RequestsResource Limitsを設定していなくても、 LimitRangeリソースで指定したResource RequestsResource Limitsを設定することができる(つまりデフォルト値を定義することができる)。 またLimitRangeリソースで指定したResource RequestsResource Limitsの最大値、最小値を超えるPodを作成できないようにすることができる。

つまりLimitRangeリソースには主に以下が設定できる。

  • podで指定できるResource RequestsResource Limitsの最大値、最小値
  • podでResource RequestsResource Limitsが設定されなかった場合のデフォルト値

(ちなみにLimitRangeリソースはLimitRanger Admission Control pluginによって動く)

例:

apiVersion: v1
kind: LimitRange
metadata:
  name: example
spec:
  limits:
  - type: Pod # Pod全体に対する制限
    min: # Pod全体で要求される最小のCPUとメモリ
      cpu: 50m
      memory: 5Mi
    max: cpu: 1 # Pod全体で要求される最大のCPUとメモリ
      memory: 1Gi
  - type: Container # コンテナに対する制限
    defaultRequest: # Resource Requestsを設定していないコンテナはこのデフォルト値が設定される
      cpu: 100m
      memory: 10Mi
    default: # Resource Limitsが設定されていないコンテナはこのデフォルト値が設定される。
      cpu: 200m
      memory: 100Mi
    min: # コンテナが設定できる最小のRequests/Limits値
      cpu: 50m
      memory: 5Mi
    max: # コンテナが設定できる最大のRequests/Limits値
      cpu: 1
      memory: 1Gi
    maxLimitRequestRatio: # Maximum ratio between the limit and request for each resource(※)
      cpu: 4
      memory: 10
  - type: PersistentVolumeClaim # PVCに対しても制限がかけられる
    min:
      storage: 1Gi
    max:
      storage: 10Gi

※ CPU maxLimitRequestRatioが4の場合、CPU LimitsはCPU Requestsの4倍以上設定できないことを指す。つまりCPU Requestsが200millicoresだったら、CPU Limitは801millicores以上は設定できない。メモリも同様。

既存のLimitRangeリソースの定義を変更しても既存のpodには影響を与えない。なぜならLimitRangeリソースはpodが作成されるときのみ働く(validateする)からだ。

ちなみに、たとえLimitRangeリソースを設定してpodが使用可能なResource量を制限しても、podを何個も作成したらClusterのリソースを食い尽くしてしまう。それを防ぐにはResourceQuotaリソースを使う必要がある。

ResourceQuotaリソース

ResourceQuotaリソースを使用することでnamespaceに対して使用可能リソース量を指定することができる。 LimitRangeリソースはpodごとに適用される。それに対して、ResourceQuotaリソースはnamespaceに対して適用される。 制限できるのは、CPU, メモリ、PersistentVolumeClaimのディスク使用量, podの数, (ユーザがそのnamespace内に作成することを許した)他のAPI objects。 podを作成するとき、ResourceQuotaリソースで定義された制限を超えていないかチェックされる。超えていたらpodは作成できない。以下のようなエラーメッセージが表示される。

$ kubectl apply -f resource-request-and-limit.yaml
Error from server (Forbidden): error when creating "resource-request-and-limit.yaml": pods "limited-pod" is forbidden: exceeded quota: cpu-and-mem, requested: limits.cpu=1,requests.cpu=1, used: limits.cpu=0,requests.cpu=0, limited: limits.cpu=600m,requests.cpu=400m

既存のResourceQuotaリソースの設定を変更しても、既存のpodには影響がない。

(ちなみにResourceQuotaリソースはResourceQuota Admission Control pluginによって働く)

apiVersion: v1
kind: ResourceQuota
metadata:
  name: cpu-and-mem
spec:
  hard:
   requests.cpu: 400m
    requests.memory: 200Mi
    limits.cpu: 600m
    limits.memory: 500Mi

ResourceQuotaリソースはそのnamespace内のpodのResource Requests、Resource Limitsのトータル値に対して制限がかかる。

kubectl describe quota で詳細がみれる。

% kubectl describe resourcequotas
Name:            cpu-and-mem
Namespace:       default
Resource         Used  Hard
--------         ----  ----
limits.cpu       200m  600m
limits.memory    20Mi  500Mi
requests.cpu     200m  400m
requests.memory  20Mi  200Mi

Kubernetes勉強会第2回 〜RBAC、セキュリティを高める方法、PodSecurityPolicy、NetworkPolicy〜

Kubernetes in Action

Kubernetes in Action

「Kubernetes in Action」を読んで学んだ結果を社内で共有しました。 その第2回の内容です。

RBAC

KubernetesのRBACについて

13. Securing cluster nodes and the network

nodeのネットワークを使用する方法

  • podが自身のvirtualネットワークではなく、nodeのネットワークを使いたい場合、 hostNetworkプロパティを設定すればOK。
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-host-network
spec:
  hostNetwork: true #これ
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
  • hostNetworkプロパティをtrueにすれば、nodeあてのネットワークアクセスはそのpodに届くことになる

  • Kubernetes Control Plane components がpodとしてdeployされるとき、hostNetworkがONでデプロイされる。

    • なのでまるでpodの中で動いていないように振る舞う
  • 同じようにnodeのportを使いたい場合は、hostPortを設定すればOK。
    • しかしその場合、そのpodはnodeに1つしか配置できない。なぜなら同じportを複数のpodで使えないから。
  • 同じように hostPID: true、 hostIPC: true もできる
  • もしGCP上でこれらを試したい場合は、 gcloud compute firewall-rulesコマンドを使って設定する必要がある。参考

コンテナのプロセス実行ユーザを制限する

  • podのコンテナのプロセスの実行ユーザを限定する方法として、以下のようにユーザidを指定できる
apiVersion: v1
kind: Pod
metadata:
  name: pod-as-user-guest
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      runAsUser: 405 # これ

またrootユーザとしてコンテナのプロセスを実行することを防ぐことができる 例えばhostディレクトリをマウントするコンテナが会った場合、rootユーザであればそのマウントしたディレクトリにフルアクセスできてしまう。しかしrootユーザ以外で動かせばそうではない。

apiVersion: v1
kind: Pod
metadata:
  name: pod-run-as-non-root
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      runAsNonRoot: true #これ

セキュリティをあげるための方法

  • podのコンテナのプロセスの実行ユーザを限定する(user idを指定)
  • コンテナのプロセスをrootで動かさない
  • コンテナをprivilegedで動かすと、nodeのカーネルにフルアクセスできてしまうので避ける
  • SELinuxを設定する
  • コンテナがfileシステムに書き込めないようにする(=read only)に設定する。
    • コンテナのreadOnlyRootFilesystemをtrueに
  • readonlyにしつつ、特定のvolumeは書き込めるようにもできる

またlinuxcapabilitiesをコンテナに指定することができる

SecurityContextをもっと詳しく知るためには以下を参照

13.3 Restricting the use of security-related features in pods

PodSecurityPolicyリソース

PodSecurityPolicyリソースとは

参考: Pod Security Policies

PodSecurityPolicy とはクラスタ全体のセキュリティ上のポリシーを定義する機能です。ホストに影響を与える可能性がある 特権 (privileged) や HostIPC などの機能を制限し Pod に脆弱性があった場合にクラスタを守ることができます。

引用元: Kubernetes: Pod Security Policy によるセキュリティの設定 PodSecurityPolicyに関しては上記の記事を読めば十分。(Z Labのエンジニアは本当に強い…)

PodSecurityPolicyはクラスタレベルのリソースです。 pod内でどんな機能が使えるのかもしくは使えないのかを定義することができます。 PodSecurityPolicyは働くためにはPodSecurityPolicy admission control pluginが API serverで動いている必要があります。

もし誰かがPodリソースを作成(API Serverへpost)しようとした場合、PodSecurityPolicy admission control pluginはPodの定義を定義されたPodSecurityPolicyに反していないかバリデートします。 もしクラスタのPolicyに従っていれば受け入れられetcdに保存されます。従っていなければ即時に却下されます。 PodSecurityPolicy admission control pluginはPodリソースをpolicyの設定に沿って変更するかもしれません(例えばコンテナ実行ユーザをPodSecurityPolicyで指定したユーザにoverrideすることができる)。 ちなみにpolicyを新しく作成しても既存のpodには影響を与えません。なぜならPodSecurityPoliciesはpodが作成/更新されるとき適用されるため。

PodSecurityPolicyで制限できること

  • どのpodがホストのIPC, PID or Network namespaceを使用できるか
  • どのホストポートをpodが使えるか
  • どのユーザでコンテナのプロセスを動かすか
  • privileged containeを持ったpodを作れるかどうか
  • どの kernel capabilitieを許可するか、またデフォルトでどのkernel capabilitieを追加or削除するか
  • どんな SELinuxLabelをコンテナが使うか
  • コンテナがrootファイルシステムを書き込み許可するかどうか
  • コンテナがどのfile system groupを実行できるか
  • podがどのvolumeタイプを使用できるか

apiVersion: extensions/v1beta1
  kind: PodSecurityPolicy
  metadata:
    name: restricted-psp
  spec:
    hostIPC: false   
    hostPID: false
    hostNetwork: false
    hostPorts:    # 使用できるport rangeを指定
    - min: 10000
      max: 11000
    - min: 13000
      max: 14000
    privileged: false
    readOnlyRootFilesystem: true
    runAsUser:
      rule: RunAsAny
    fsGroup:
      rule: RunAsAny
    supplementalGroups:
      rule: RunAsAny
    seLinux:
      rule: RunAsAny # コンテナが必要とするどんなSELinux groupも使用することができる
    volumes: 
    - '*'            # 全てのvolume typeを使用することができる

PodSecurityPolicyをRoleとして定義でき、RBACによって、ServiceAccountに紐付ける(bindingする)ことができる。

# 先のPodSecurityPolicyリソースを作成
& kubectl create -f restricted-psp.yaml

# PodSecurityPolicyをClusterRoleとして定義
$ kubectl create clusterrole psp-cluster-role --verb=use --resource=podsecuritypolicies --resource-name=restricted-psp

# ServiceAccountを作成
$ kubectl create serviceaccount psp-service-account

# ClusterRoleをServiceAccountに紐付ける(binding)
$ kubectl create clusterrolebinding psp-restricted --clusterrole=psp-cluster-role \
    --serviceaccount=default:psp-service-account

# あとはpodにServiceAccount:psp-service-accountを指定して、例えばprivileged: trueにするとpodは作成できなくなる。

13.4 Isolating the pod network

Network Policies

もしnetworking pluginがpod間のネットワーク通信制限機能をサポートしていれば、NetworkPolicyリソースを作成することによって、 ネットワークの隔離をすることができる。 NetworkPolicyリソースはlabel selectorにマッチしたpodに対して適用される。そしてpodへの外への通信とpodの中への通信を制限することができる。 ingress rule, egress ruleと呼ばれる。Ingressリソースとは関係がない。 ingressはネットワークの流入、egressはネットワークの流出のルールを定義できる。

このpodはこのpodへしかリクエストを送れないor受け取れないとかこのportを使ってしかあのpodへリクエストを送れないor受け取れないとかが設定できる。 また特定のlabelがついたnamespace内のpodからリクエストを受け取らないということができる。 もしくはcidrを使ってリクエストを制限することもできる。

例:

以下のingress/egressルールにマッチするものだけ通信が許可される。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
spec:
  podSelector:
    matchLabels:
      role: db # このlabelを持ったpodがこのNetworkPolicyルールの対象になる
  ingress:
  - from:
    - ipBlock:
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24
    - namespaceSelector:
        matchLabels:
          project: myproject
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 6379
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 5978

13.5 Summary

  • podは自分のではなくnodeの Linux namespacesを使うことができる
  • コンテナはコンテナイメージで指定されたユーザ/グループとは別のユーザ/グループで実行するように設定することができる
  • コンテナはprivileged modeで動かすことができ、privileged modeだとpodに公開されていないnodeのdeviceにアクセスすることができる。
  • コンテナはプロセスがファイル書き込みをしないようにread onlyで動くように設定することができる(また特定のvolumeのみ書き込みが可能に設定できる)
  • Cluster-levelリソースであるPodSecurityPolicyは、nodeの安全を脅かすpodの生成を防ぐことができる。
  • PodSecurityPolicyはRBACのClusterRoleとClusterRoleBindingを使って特定のuserに紐付けることができる。
  • NetworkPolicyリソースはpodのinbound/outbound trafficを制限することができる。

Podのセキュリティを高めるためには以下を参照

Kubernetesのセキュリティを高めるためには以下を参照

Kubernetes勉強会第1回 〜Secrets、StatefulSet、DaemonSet、API server への接続方法〜

Kubernetes in Action

Kubernetes in Action

「Kubernetes in Action」を読んで学んだ結果を社内で共有しました。 その第1回の内容です。

Secrets

  • ConfigMapと同様、Key-Value のリストだが、パスワードや認証情報等の秘密情報を格納するためのリソース。
  • Container から Secret を使うには、"環境変数"、"Volume としてマウント"の二種類がある。この使い方はConfigMapと変わらない。
  • 3つ種類がある。使い分けは以下
    • docker-registry: 認証が必要なDocker registryを使うときに使用する
    • generic: secretをローカルファイル、ディレクトリ、literal valueから生成する場合
    • tls: ingressSSL certificateに使用する場合

環境変数から使う場合

apiVersion: v1
kind: Pod
metadata:
  name: fortune-env
spec:
  containers:
  - image: luksa/fortune:env
    name: html-generator
    env:
    - name: MY_PASSWORD_SECRET
      valueFrom:
        secretKeyRef: # configMapを環境変数に使う場合はここが`configMapKeyRef`になる
          name: my-secret
          key: password

volumeとしてmountする

apiVersion: v1
kind: Pod
metadata:
  name: fortune-https
spec:
  containers:
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: certs
      mountPath: /etc/nginx/certs/
  volumes:
  - name: certs
    secret:
      secretName: fortune-https
$ kc exec git-sync-demo-b9cb448f7-972rh ls /var/run/secrets/kubernetes.io/serviceaccount/
ca.crt
namespace
token
  • Secretがbase64なのは、バイナリデータも含めることができるように。
  • Secretのサイズ上限は1M
  • private dockerレジストリからimageをpullする時の認証をsecretに登録するためには
$ kubectl create secret docker-registry mydockerhubsecret \
  --docker-username=myusername --docker-password=mypassword \
  --docker-email=my.email@provider.com

のようにしてsecret genericではなくsecret docker-registryを指定する。 podには以下のように imagePullSecrets を指定

apiVersion: v1
kind: Pod
metadata:
  name: private-pod
spec:
  imagePullSecrets:
  - name: mydockerhubsecret
  containers:
  - image: username/private:tag
    name: main

API server への接続方法

まずkubectlは中でKubernetes API ServerへREST APIリクエストを送って、Kubernetesの操作をしています。 そのため、kubectl --v=8 get podsのように--v=8オプションをつけて実行すると、どのようなREST APIが送信されているかみることができます。

$ kubectl --v=8 get node
省略...
I0321 19:31:59.047070    9111 round_trippers.go:414] GET https://192.168.99.100:8443/api/v1/nodes
I0321 19:31:59.047079    9111 round_trippers.go:421] Request Headers:
I0321 19:31:59.047086    9111 round_trippers.go:424]     Accept: application/json
I0321 19:31:59.047093    9111 round_trippers.go:424]     User-Agent: kubectl/v1.8.4 (darwin/amd64) kubernetes/9befc2b
I0321 19:31:59.060898    9111 round_trippers.go:439] Response Status: 200 OK in 13 milliseconds
I0321 19:31:59.060920    9111 round_trippers.go:442] Response Headers:
I0321 19:31:59.060926    9111 round_trippers.go:445]     Content-Type: application/json
I0321 19:31:59.060932    9111 round_trippers.go:445]     Content-Length: 3171
I0321 19:31:59.060937    9111 round_trippers.go:445]     Date: Wed, 21 Mar 2018 10:31:58 GMT
I0321 19:31:59.061025    9111 request.go:836] Response Body: {"kind":"NodeList","apiVersion":"v1","metadata": 長いので省略

Kubernetesの各pod内のコンテナからもAPI Serverを利用することができます。 参考: https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-api/#accessing-the-api-from-a-pod

kubectl run curl --image=tutum/curl -- tail -f /dev/null kc exec -it pod名 /bin/bash でpodにログインし、

# 以下でkubernetes APIのIPとPORTがわかる。
$ env | grep KUBERNETES_SERVICE
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT_HTTPS=443

# SSL証明書を指定
export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# token(認証情報)を設定
export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)

curl -H "Authorization: Bearer $TOKEN" https://10.96.0.1:443/api/v1/namespaces/default/pods

# kubernetesというドメインでもアクセスできる
curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/default/pods
curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/default/services/git-sync-demo

token(認証情報)はpod外から以下でも取得できる

$ kubectl describe secret $(kubectl get secrets | grep default | cut -f1 -d ' ') | grep -E '^token' | cut -f2 -d':' | tr -d '\t'

認証方式はいくつかある kubernetesの認証とアクセス制御を動かしてみる

認可に関してはRBAC(Roles-Based Access Control)という機能が担う。

Rubyのkubernetes API クライアントは https://github.com/abonas/kubeclient がある。

StatefulSets

https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/

ステートフルなアプリケーション(例えばDB等)をサポートするためのリソース。

  • 再作成されてもpod nameやhostnameは同じ名前で生成される。
    • pod名は {name}-NというformatでNは0から始まる。つまり{name}-0, {name}-1, {name}-2 ...というように生成される。
  • 作成されるときは、次の使われていないindexを使って作成される。
  • podが起動するときは小さいindexから順に起動される。
  • またpodが削除されるときは大きいindexから順に削除される。
  • 同じPersistentVolumeClaimsを使おうとする。
  • 削除されるときは使っていたPersistentVolumeClaimsは削除されない。

例:

apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: kubia
spec:
  serviceName: kubia
  replicas: 2
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia-pet
        ports:
        - name: http
          containerPort: 8080
        volumeMounts:
        - name: data
          mountPath: /var/data
  volumeClaimTemplates: # このテンプレートを使ってPersistentVolumeClaimsが作成される
  - metadata:
      name: data
    spec:
      resources:
        requests:
          storage: 1Mi
      accessModes:
      - ReadWriteOnce

参考: https://scrapbox.io/blackawa/StatefulSet%E3%81%A3%E3%81%A6%E3%81%84%E3%81%A4%E4%BD%BF%E3%81%86%E3%81%AE%EF%BC%9FPersistentVolumes%E3%81%A7%E3%81%84%E3%81%84%E3%82%93%E3%81%98%E3%82%83%E3%81%AA%E3%81%84%E3%81%AE%EF%BC%9F

DaemonSet

https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/

全てのnode、もしくは特定のnodeに必ず1つのpodを動かしたい場合にはDaemonSetを使うとよい。 たとえば、以下のケースに使える。

  • Cluster storage daemon(例えばglusterdやceph)を動かす場合
  • nodeのメトリックス収集用のpodを動かしたい場合(例えば Prometheus Node Exporter, collectd, Datadog agent, New Relic agent, or Ganglia gmond.)
  • nodeのログを収集するpodを動かしたい場合(fluentd や logstash)
  • nodeのネットワークを操作するためのpodを動かしたい場合

例:

apiVersion: apps/v1beta2
kind: DaemonSet
metadata:
  name: ssd-monitor
spec:
  selector:
    matchLabels:
      app: ssd-monitor
  template:
    metadata:
      labels:
        app: ssd-monitor
    spec:
      nodeSelector:
        disk: ssd
      containers:
      - name: main
        image: luksa/ssd-monitor

11. Understanding Kubernetes internals

  • Kubernetes SystemコンポーネントだけがKubernetes API Serverと通信をする
  • Kubernetes API serverのみがetcdと通信をする。他のコンポーネントは直接etcdと通信しない。 クラスタの状態もAPI Serverと通して行われる
  • ectdには楽観ロックで操作するので一貫性がとれる。

11.4.3 Introducing the Container Network Interface

  • コンテナ間で簡単にネットワーク通信できるようにContainer Network Interface (CNI)というプロジェクトがスタートした。 KubernetesはいくつかのCNI pluginを使うことができる。
    • Calico
    • Flannel
    • Romana
    • Weave Net
    • And others

CNIはDaemonSetとして各Nodeにデプロイされる。

http://tech.uzabase.com/entry/2017/09/12/164756