CKA(Certificate Kubernetes Administrator)を取得した

認定された。普段からKubernetesを触ってきて、それなりに知識と経験は備えているとは思っているが、念の為Udemyの Certified Kubernetes Administrator (CKA) with Practice Tests コースをざっくりこなしてから受けた。結構知らないこともあったので、知識の補完ができてよかった。

パスポートはもともと持っていたし、受験は自宅で受けたのでそこまで大変ではなかった。部屋から本を出して物がない状態にするのが手間だったくらい。なんか、試験を受けるのが久しぶりで合格したのが結構嬉しい。

AirPodsProを買った

発売日に同僚が買ったものを試させてもらったところ、気づいたら注文していた。オンライン注文、店舗受け取りで当日購入。

結論としては久々に当たりガジェットである。つけ心地が非常に気持ちいいし、ノイズキャンセリングも快適。iPhoneとのインテグレーションもよくできている。iPhoneユーザーなら基本的に買うといいんじゃないかな、という位には気に入っている。

なにがいいのかというといろいろあるけど、つけていて耳が痛くならないのがよい。インナーイヤータイプは色々試したけどどれも1時間ほど使うと痛くなっていた。AirPodsProはそれがない。ノイズキャンセリングは完全に音を消すというよりは作業に集注できる程度にカットしている印象。飛行機で使う場合はわからないが、普段使いでは十分かなあ。音質はそんなにこだわりがないので割愛。

外音取り込みモードもよくて、イヤホンを外さなくても人の話し声を聞き取れる。左右分離タイプは外した時に落とすリスクがあったけど、外音取り込みモードにより外す回数は減らせそう。

充電用の端子がLightningなのだけど、普段は非接触充電すればよいしケーブルはiPhoneと共用できるので現段階では問題ないかなあ。

新宿駅で乗り換えたり、池袋でウロウロしても全く途切れなかったのでもうこれでいいやと思っている。聴くのもストリーミングばかりだしね。

docker/distributionで使われていないレイヤーやタグがついていないマニフェストをGCする

docker/distoributionを長く使っていると、どのイメージからも参照されていないレイヤーが出てきたり、タグがついていなくてほぼ使われないイメージが増えたりする。バックエンドにクラウドストレージを使っている場合は不要なデータを消せると嬉しいのだが、どうやって消せばいいのか?という疑問がある。

docker/distributionには garbage-collect コマンドがあり、これを使うことで不要なデータをGCすることができる。

`garbage-collect` deletes layers not referenced by any manifests

Usage:
  registry garbage-collect <config> [flags]
Flags:
  -m, --delete-untagged=false: delete manifests that are not currently referenced via tag
  -d, --dry-run=false: do everything except remove the blobs
  -h, --help=false: help for garbage-collect


Additional help topics:

デフォルトではマニフェストから参照されていないレイヤーを削除する。また、フラグを指定することでタグがついていないマニフェストも消すことができる。

ServiceAccountのトークンで使われているJWTの処理はどこに書かれているのか

KubernetesのServiceAccountと、そのトークンで使われているJWTのSign/Verifyのコードについて追った。ServiceAccountとJWTについての説明はしません。

ServiceAccountのトークンはServiceAccountと紐づくSecretに含まれている。このSecretはkube-controller-manager内のtoken-controllerが作成している。

https://github.com/kubernetes/kubernetes/blob/v1.16.2/pkg/controller/serviceaccount/tokens_controller.go#L383-L400

トークンの署名にはkube-controller-managerの引数である --service-account-private-key-file で渡した秘密鍵を使う。

トークンはKubernetesのAPIを利用する際の認証・認可に使われる。トークンのベリファイにはkube-apiserverの引数である --service-account-key-file で渡した秘密鍵もしくは公開鍵を使う。指定していない場合は --tls-private-key-file で渡した鍵が使われる。--service-account-key-file を渡している場合、jwtTokenAuthenticatorがauthenticatorsとして使えるようになる。

https://github.com/kubernetes/kubernetes/blob/v1.16.2/pkg/kubeapiserver/authenticator/config.go#L139-L145

jwtTokenAuthenticatorはトークンのベリファイをAuthenticateTokenメソッドで行う。

最後に、Sign/ParseSignの練習コードを乗せておく。RS256で、秘密鍵と証明書を使っている。https://github.com/takaishi/hello2019/blob/master/jwt/rs256/main.go にもある。

package main

import (
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"github.com/k0kubun/pp"
	"gopkg.in/square/go-jose.v2"
	"gopkg.in/square/go-jose.v2/jwt"
	"io/ioutil"
	"time"
)

func readPrivateKey(path string) (*rsa.PrivateKey, error) {
	privateKeyData, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}
	privateKeyBlock, _ := pem.Decode(privateKeyData)
	if privateKeyBlock == nil {
		panic(errors.New("invalid private key data"))
	}
	if privateKeyBlock.Type != "RSA PRIVATE KEY" {
		panic(errors.New(fmt.Sprintf("invalid private key type : %s", privateKeyBlock.Type)))
	}

	return x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes)
}

func readCertificate(path string) (*x509.Certificate, error) {
	certificateData, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}
	certificateBlock, _ := pem.Decode(certificateData)
	if certificateBlock == nil {
		panic(errors.New("invalid private key data"))
	}
	return x509.ParseCertificate(certificateBlock.Bytes)
}

func main() {
	privateKey, err := readPrivateKey("./service-account-key.pem")
	if err != nil {
		panic(err)
	}

	privateJWK := &jose.JSONWebKey{
		Algorithm: string(jose.RS256),
		Key:       privateKey,
		Use:       "sig",
	}

	signer, err := jose.NewSigner(
		jose.SigningKey{
			Key:       privateJWK,
			Algorithm: jose.RS256,
		},
		nil,
	)

	cl := jwt.Claims{
		Subject:   "subject",
		Issuer:    "issuer",
		NotBefore: jwt.NewNumericDate(time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)),
		Audience:  jwt.Audience{"leela", "fry"},
		ID:        "ryo",
	}
	raw, err := jwt.Signed(signer).Claims(cl).CompactSerialize()
	if err != nil {
		panic(err)
	}

	fmt.Println(raw)

	tok, err := jwt.ParseSigned(raw)
	if err != nil {
		panic(err)
	}

	certificate, err := readCertificate("./service-account.pem")
	if err != nil {
		panic(err)
	}

	out := jwt.Claims{}
	err = tok.Claims(certificate.PublicKey, &out)
	if err != nil {
		panic(err)
	}
	pp.Print(out)

}

kelm v0.0.5をリリースした

https://github.com/takaishi/kelm/releases/tag/v0.0.5

アクションに対してカスタム変数を渡せるようになったのと、フラグにnamespaceとkindを追加した。これにより、選択したNodeのInternalIPをjsonpathで取得して変数に格納、アクションに渡すということが実現できるようになり、ノードへ楽にSSHできるようになった。

以下、設定サンプル。

ノードを選択してSSHする

~/.kelm に以下のように設定する。

---
actions:
  nodes:
    - name: "ssh"
      variables:
        - name: address
          jsonpath: '{.status.addresses[?(@.type=="InternalIP")].address}'
      command: 'ssh {{ .address }}'

そして、以下のコマンドを実行。

$ kelm -c ./config.yaml --namespace default --kind nodes --action ssh 

デプロイメントを選択してsternする

~/.kelmに以下のように設定し、deploymentのアクションでsternを選択すればOK。

---
actions:
  deployments:
    - name: "stern"
      command: "stern -n {{ .Namespace }} {{ .Obj.metadata.name }}"