kubeletが使うコンテナランタイムをdockerからcontainerdに変更する

kubeletが使うコンテナランタイムはこれまでdockerを使っていたのだが、別のものを試してみようということでcontainerdに変更してみた。

containerdをインストールする

containerdはaptでインストールできる(パッケージ: containerd)。また、containerdが使うLow Level Runtimeであるruncも同時にインストールされる。インストールした後、以下のコマンドを実行して設定のデフォルト値を /etc/containerd/config.toml に書き込み、systemctl start containerd で起動する。

$ containerd config default

kubeletからcontainerdを使う

kubeletの引数に以下を追加して再起動することで、containerdを使うようになる。

 --container-runtime=remote
 --container-runtime-endpoint=/run/containerd/containerd.sock

CRIのおかげで、これだけでランタイムを切り替えることができてとても便利。

ノード上で動いているコンテナ一覧を見るには

これまではdocker psを実行してそのノード上で動いているコンテナの一覧を表示していた。containerdの場合はどうすればいいのかというと、2つ方法がある。

まずはcontainerdに付属しているctrコマンド。namespaceフラグでk8s.ioを指定することで、kubeletによって作成されたコンテナの一覧を見ることができる。

$ sudo ctr --namespace k8s.io containers list
CONTAINER                                                           IMAGE                                                                                                    RUNTIME
24ff8b4bb5d6cdfdc5311aa44716a53fa7cd578da76b125e063660e8459ea96f    k8s.gcr.io/metrics-server-amd64:v0.3.1                                                                   io.containerd.runtime.v1.linux

しかし、コンテナのIDとイメージ、ランタイムしか見えずトラブルシュートに使うには物足りない。そこで、もう1つの方法としてcrictlを使うことができる。

https://github.com/kubernetes-sigs/cri-tools/blob/master/docs/crictl.md

crictlはCRIに互換性のあるコンテナランタイム向けのCLIツールである。crictl psでコンテナ一覧を見ることができる。表示項目はdocker psに近く、使いやすいだろう。また、docker psコマンドでコンテナ一覧を見るとコンテナ名が長くなり読みにくいが、crictlだと素朴に名前が表示されるので視認性も高い。しかし、pauseコンテナは表示されないのでその点は注意(見えないことで困ることがあるのかはよく分からない)。

$ sudo crictl ps
CONTAINER ID        IMAGE               CREATED             STATE               NAME                       ATTEMPT             POD ID
24ff8b4bb5d6c       61a0c90da56e7       22 hours ago        Running             metrics-server             0                   68dfed1214a0a

crictlはexecやlogsなど、ノード内でコンテナを見るときに使うコマンドも備えているので基本的にはこれを使い、必要に応じてctrコマンドを使うのが良さそう。なお、runcにもlistコマンドがありコンテナ一覧を表示できそうだったが何も見えなかった。

containerdをkubernetesから使ってみたい方、参考になれば幸いです。

reference

ClusterAPI v0.3.0の変更点

2020年3月11日(JST)にClusterAPI v0.3.0がリリースされました。本記事では、v0.3.0でどのような変更があったのかについて、v0.3.0のリリースノートを眺めてみます。

v0.3.0で実装されたプロポーザル

Kubeadm based control plane

Proposal: https://github.com/kubernetes-sigs/cluster-api/blob/master/docs/proposals/20191017-kubeadm-based-control-plane.md

このプロポーザルは、ClusterAPI がコントロールプレーンマシンを単一概念として管理するための新しいプロセスの概要です。アップグレード、スケールアップ、コントロールプレーンマシンが使うイメージの変更(例:AMI)を含んでいます。

2019年の時点では、各InfrastructureProviderでコントロールプレーン管理が実装されていました。ブートストラップの処理がClusterAPI Bootsrap Provider Kubeadm(CABPK)として共通化されたのと同様、各プロバイダーでコントロールプレーンの管理について実装しなくても良いように共通化するもののようです。BootstrapProviderとあわせて、InfrastructureProvider側で実装すべきコードが減るのは良さそうですね。

Clusterctl v2

Proposal: https://github.com/kubernetes-sigs/cluster-api/blob/master/docs/proposals/20191016-clusterctl-redesign.md

これまでclusterctlコマンドは各プロバイダーで微妙に異なる物が提供されており、混乱の元になっていました。また、clusterctlのスコープが広く、複雑になっていたようです。v2ではDay1におけるエクスペリエンスをスコープとし、最適化されたCLIを提供するように変更されています。

Machine Health Checking and Remediation

Proposal: https://github.com/kubernetes-sigs/cluster-api/blob/master/docs/proposals/20191030-machine-health-checking.md

マシンのヘルスチェック自動化と、異常なノードの修復を行う提案です。これにより、クラスタ管理のオーバーヘッド削減、マシン障害に対する回復力を高めることができます。

実装としてはMachineHealthChecker(MHC)があり、これは異常なノードを提供するマシンを削除することでクラスターノードを正常に保とうとします。

MachinePool (experimental)

Proposal: https://github.com/kubernetes-sigs/cluster-api/blob/master/docs/proposals/20190919-machinepool-api.md

AWSのAuto Scale GroupやGCPのManaged Instance Group、AzureのVirtual Machine Scale Setなど、クラウドサービスのマシンプール機能を使えるようにする提案です。全てのインフラストラクチャプロバイダーがマシンプール機能を持つわけではないので、オプションとして提供されています。AWSなどを使っている場合は使えるというわけですね。

v0.3.0-rc.3からの変更点

新機能

  • KubeadmControlPlaneのReconcile時にCoreDNSイメージ情報を更新するようになりました (#2574)
  • KubeadmControlPlaneの更新時にkubeproxyデーモンセットも更新するようになりました (#2559)
  • KubeadmControlPlaneの更新時にetcdのアップグレードも行われるようになりました (#2579)
  • clusterctl: 隔離された環境の全てのイメージを上書きできるようになりました (#2586)
  • clusterctl: cert-managerイメージの上書きが許可されました (#2558)
  • MachineHealthCheck の修正ロジックが追加されました (#2494)

バグフィックス

  • KubeadmControlPlaneにおいて、etcdの設定をバリデーションする際のN P(nil pointer exception)を修正しました (#2616)

その他

  • upgradeAfter フィールドが変更可能になりました (#2604)
  • KubeadmControlPlaneのspec.versionをミュータブルにできるようになりました (#2582)
  • コントロールプレーンのEtcdがローカルにもエクスターナルにもない際のバリデーションを追加しました (#2598)
  • KubeadmControlPlane内でバージョニングを一致させるための修正 (#2624)
  • Kubeadm v1beta1 typesからプライベートAPIが削除されました (#2605)
  • etcdの変更を行うため、コントロールプレーンのwebhookバリデーションが緩和されました (#2553)
  • 削除されたオブジェクトをcontroller.Watchに渡すようになりました (#2576)
  • リポジトリ全体でGoモジュールのクリーンアップ (#2552)
  • KubeadmControlPlane nodesにNoExecute taintのチェックが追加されました (#2550)
  • Metal3プロバイダーのリポジトリが変更されました (#2540)
  • cmd-clusterctl-api/tests: gomegaのインポート方法が標準化されました (#2615)
  • tests: gomegaのインポート方法が標準化されました(フォローアップ) (#2589)
  • tests: gomega/ginkgoのインポート方法が標準化されました (#2571)

所感

v0.2.0から引き続き、統合できる物は統合して個々のInfrastructureProviderを開発しやすくしたり、CLIツールの責務を整理して扱いやすくしたりという変更が目立ちます。また、MachineHealthCheckやMachinePoolのような新しい仕組みも追加されており、まだまだ進化してるな〜と感じました。v0.3.0も一度触ってみようと思います。

Go製ソフトウェアの開発中にインポートしているパッケージを修正して手元で動作確認する方法

Goで書かれたソフトウェアを触っていると、そのソフトウェアがインポートしているパッケージを修正して手元で動作確認したいことがある。Go Modulesのreplaceを使うと手軽に実現できるので、その方法について記録しておく。

例としてhashicorp/vaultを挙げる。vaultは機能ごとにプラグインとして外部パッケージに切り出していて、例えばシークレットのKVはhashicorp/vault-plugin-secrets-kv だったりする。この時、vault-plugin-secrets-kv側を変更して、その変更をVaultとして動作確認したいというわけである。

ではどうするかというと、Go Modulesにあるreplaceを使う。

replace github.com/hashicorp/vault-plugin-secrets-kv => ../vault-plugin-secrets-kv

このようにgithub.com/hashicorp/vault-plugin-secrets-kvを相対パスで指定することで、修正内容が含まれるパッケージでビルドすることができる。

consul-templateでVaultのシークレットをソースにしている場合、更新してもすぐにはレンダリングされない場合がある

consul-templateでVaultのKVSからデータを取得してファイルに書き出したいことがある。Consulとconsul-templateを組み合わせた時と同様、Vault上のシークレットを更新するとすぐに反映されると思っていた。しかし、そうではなく、KVSの場合そのシークレットのリース期間(設定されていない場合は5分)の85%~95%が過ぎたあたりで更新を試みるようだった。

検証に使ったconsul-templateのコンフィグとテンプレートはこんな感じ。

config.hcl:

log_level = "debug"

vault {
  address = "http://localhost:8200"
  token = "roottoken"
  renew_token = false

  ssl {
    enabled = false
  }
}

template {
  source      = "./template.ctmpl"
  destination = "./test.txt"
  command     = "echo rendered"
}

template.ctmpl:

{{- with secret "kv-v2/foo" }}
a: {{ .Data.data.a }}
b: {{ .Data.data.b }}
{{- end }}

kv-v2/fooにデータを書き込んでおく。

$ vault kv put kv-v2/foo a=111 b=222
Key              Value
---              -----
created_time     2020-03-13T00:55:12.045597Z
deletion_time    n/a
destroyed        false
version          2

$ vault kv get kv-v2/foo
====== Metadata ======
Key              Value
---              -----
created_time     2020-03-13T00:55:12.045597Z
deletion_time    n/a
destroyed        false
version          2

== Data ==
Key    Value
---    -----
a      111
b      222

consul-templateを起動すると、test.txtに書き込まれる。

2020/03/13 00:55:24.545585 [INFO] (runner) creating watcher
2020/03/13 00:55:24.546436 [INFO] (runner) starting
2020/03/13 00:55:24.546455 [DEBUG] (runner) running initial templates
2020/03/13 00:55:24.546462 [DEBUG] (runner) initiating run
2020/03/13 00:55:24.546520 [DEBUG] (runner) checking template dc39c8fc3e10d93709e0aabdf4a1b566
2020/03/13 00:55:24.546827 [DEBUG] (runner) missing data for 1 dependencies
2020/03/13 00:55:24.546846 [DEBUG] (runner) missing dependency: vault.read(kv-v2/foo)
2020/03/13 00:55:24.546853 [DEBUG] (runner) add used dependency vault.read(kv-v2/foo) to missing since isLeader but do not have a watcher
2020/03/13 00:55:24.546858 [DEBUG] (runner) was not watching 1 dependencies
2020/03/13 00:55:24.546860 [DEBUG] (watcher) adding vault.read(kv-v2/foo)
2020/03/13 00:55:24.546870 [DEBUG] (runner) diffing and updating dependencies
2020/03/13 00:55:24.546874 [DEBUG] (runner) watching 1 dependencies
2020/03/13 00:55:24.657740 [DEBUG] (runner) receiving dependency vault.read(kv-v2/foo)
2020/03/13 00:55:24.657783 [DEBUG] (runner) initiating run
2020/03/13 00:55:24.657791 [DEBUG] (runner) checking template dc39c8fc3e10d93709e0aabdf4a1b566
2020/03/13 00:55:24.658390 [DEBUG] (runner) rendering "./template.ctmpl" => "./test.txt"
2020/03/13 00:55:24.672384 [INFO] (runner) rendered "./template.ctmpl" => "./test.txt"
2020/03/13 00:55:24.672419 [DEBUG] (runner) appending command "echo rendered" from "./template.ctmpl" => "./test.txt"
2020/03/13 00:55:24.672428 [DEBUG] (runner) diffing and updating dependencies
2020/03/13 00:55:24.672439 [DEBUG] (runner) vault.read(kv-v2/foo) is still needed
2020/03/13 00:55:24.672445 [INFO] (runner) executing command "echo rendered" from "./template.ctmpl" => "./test.txt"
2020/03/13 00:55:24.672552 [INFO] (child) spawning: echo rendered
rendered
2020/03/13 00:55:24.678989 [DEBUG] (runner) watching 1 dependencies
2020/03/13 00:55:24.679010 [DEBUG] (runner) all templates rendered
2020/03/13 00:55:24.679049 [DEBUG] (cli) receiving signal "child exited"

ここで、Vault側を更新するとconsul-templateがtest.txtを更新してくれるのかと思ったが、そうではなかった。

Consul-template not update file when data in Vault is updated によると、Vaultはシークレットの更新をウォッチするブロッキングクエリをサポートしていないらしい。ではどうやって更新するかというと、シークレットのリース期間の半分が過ぎると更新を試みるらしい。なるほど、ではリース期間から待ち時間を算出するロジックを探してみよう。

https://github.com/hashicorp/consul-template/blob/v0.24.1/dependency/vault_common.go#L126-L167 のleaseCheckWaitが待ち時間を算出する関数のようだ。

// leaseCheckWait accepts a secret and returns the recommended amount of
// time to sleep.
func leaseCheckWait(s *Secret) time.Duration {
	// Handle whether this is an auth or a regular secret.
	base := s.LeaseDuration
	if s.Auth != nil && s.Auth.LeaseDuration > 0 {
		base = s.Auth.LeaseDuration
	}

	// Handle if this is a certificate with no lease
	if certInterface, ok := s.Data["certificate"]; ok && s.LeaseID == "" {
		if certData, ok := certInterface.(string); ok {
			newDuration := durationFromCert(certData)
			if newDuration > 0 {
				log.Printf("[DEBUG] Found certificate and set lease duration to %d seconds", newDuration)
				base = newDuration
			}
		}
	}

	// Ensure we have a lease duration, since sometimes this can be zero.
	if base <= 0 {
		base = int(VaultDefaultLeaseDuration.Seconds())
	}

	// Convert to float seconds.
	sleep := float64(time.Duration(base) * time.Second)

	if vaultSecretRenewable(s) {
		// Renew at 1/3 the remaining lease. This will give us an opportunity to retry
		// at least one more time should the first renewal fail.
		sleep = sleep / 3.0

		// Use some randomness so many clients do not hit Vault simultaneously.
		sleep = sleep * (rand.Float64() + 1) / 2.0
	} else {
		// For non-renewable leases set the renew duration to use much of the secret
		// lease as possible. Use a stagger over 85%-95% of the lease duration so that
		// many clients do not hit Vault simultaneously.
		sleep = sleep * (.85 + rand.Float64()*0.1)
	}

	return time.Duration(sleep)
}

標準的なシークレットの場合、LeaseDurationがあればそれを、なければVaultDefaultLeaseDurationをリース期間として使っている(VaultDefaultLeaseDurationは5分)。そして、シークレットがリニュー可能かどうかでロジックは異なるが、待ち時間を計算して返却する。

kv-v2/fooをみると、lease_durationは0なのでVaultDefaultLeaseDurationが使われる。

{
  "request_id": "c8ffdb88-1b66-10d1-b417-3061e843914f",
  "lease_id": "",
  "lease_duration": 0,
  "renewable": false,
  "data": {
    "data": {
      "a": "111",
      "b": "222",
      "ttl": "60s"
    },
    "metadata": {
      "created_time": "2020-03-13T01:02:53.685381Z",
      "deletion_time": "",
      "destroyed": false,
      "version": 4
    }
  },
  "warnings": null
}

なお、証明書の場合は証明書の発行から失効までの時間をリース期間としている。

consul-templateでVaultと連携してあれこれできて便利だが、更新のタイミングには注意しないといけないということがわかった。

Ubuntuでユーザーディレクトリを再作成するには mkhomedir_helper を使うと良い

Ubuntuでユーザーディエレクトリを消した後に/etc/skelをベースに再作成したい時があると思うのだけど、そういう時は mkhomedir_helper が使える。

Usage: /sbin/mkhomedir_helper <username> [<umask> [<skeldir>]]

こういう感じでユーザー名を指定するとその名前でユーザーディレクトリが作られる。