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で仕切り直し中とのこと。