【Kubernetes】CKADに合格しました

10月末にCKAD (Certified Kubernetes Application Developer) を受けて合格した。試験に受かるのは久しぶり。

受験の経緯

仕事で扱っているシステムがKubernetes上で動いており、自身のKubernetesの知識不足に危機感を持っていたのがきっかけ。今まではその都度調べてなんとかなっていた状態だったが、同じチームの先輩がCKADとCKA (Certified Kubernetes Administrator) に合格したことを社内でシェアしていてこの試験のことを知った。選択問題ではなく実際にクラスタ内でコマンドを打って問題を解く形式なのが気に入り、まずはCKADを受験してみることにした。

学習法

学習開始は7月末。

  • KodeCloud
    自分の場合付け焼き刃の知識が多く、Kubernetesの基礎が一部怪しかったので、有料コースを取った。

kodekloud.com

Udemy上にも同じ講師の同様のコースがあるので、セール期間中ならそちらでも良い。自分が申し込もうとしたときはUdemyのセール期間が終わっていて2万くらいしていたので、講師の方が運営するこちらのサービスを利用した。
このコースはKubernetesのresourceの種類ごとに教材が豊富で、Katacodaを使った演習や模擬試験も多く揃っていた。説明は全編英語だが聞き取りやすい。ただし教材の一部には日本語字幕がついていないので英語が全くわからない人には厳しいかもしれない。ちなみにコースの途中で受験料が15%オフになるクーポンをもらうことができた。(((KodeCloudでの話なので、Udemyだともらえるのかはわからない。))
当日の問題形式に慣れるために、最後についているmock exam(1時間x2回分)はそれぞれ2回ずつ行った。

  • CKAD-Exercise
    コースを一通り終えた後は、CKAD-Exerciseを2周ほどしてコマンドの練習をした。このレポジトリはよく試験対策に使われているが、全てkubectlコマンドの一問一答であるため、知識に不安がある人はこれ以外の教材も併用すべき。

github.com

  • Kubernetes完全ガイド
    レファレンスとしてお世話になった。PCの横に置いておき、副読本として使うのがおすすめ。

book.impress.co.jp

試験当日

申し込みは以下のページから行う。日本語版もあるらしいが、ローカライズの質がわからなかったので英語にした。 training.linuxfoundation.org

当日は本人確認としてパスポートの提示が必要なことに注意。期限が切れている人はあらかじめ再発行しなければいけない。

試験監督とは全てチャットで会話するので、英語のスピーキングは必要ない。Web試験での不正防止のためか、事前確認はとても厳重。試験前に他のアプリケーションは全て閉じる必要があり、またWebカメラを通じて部屋全体360度の様子と、机の上に一切何も置いていないのを見せる必要がある。自分の場合、机のそばの壁に貼ってある掲示物も剥がせと言われた。さらに、試験中の私語や、部屋に他の人が入ってくるのもNGとなる。これから受験する人は、試験時間になる前にPCのプロセスと机まわりを徹底的に片付けておき、家族や同僚などがいるなら誰も部屋に入って来ないよう事前に調整しておく方がいい。事前確認で15-30分はかかるので、部屋を押さえる時間帯にも余裕を取ろう。
試験中も時々チャットが飛んできて、口に手を当てるのをやめろ・両方の手のひらをWebカメラに向けろ等と言われた。慣れていないととても緊張するが、言われた通り落ち着いて対応するのが重要。

試験の心構え

  • 時間が足りないので、極力YAMLを書かないこと。kubectlでできることは全てそれで済ませるべき。YAMLを書かなければいけない場合でも、 -o yaml --dry-run=client をつけてファイルに出力すれば、YAMLのひな形が手に入る。
  • 学習中にKubernetesドキュメントを調べたときは、何の例がどのページに載っていたか覚えておくと良い。試験中はKubernetesドキュメントを開けるので、マニフェストファイルの内容をコピペしたいときに時間を節約できる。
  • 試験が始まったら .vimrc を編集して、タブをスペースに変更すると良い。これをやらないと、Kubernetesドキュメントからコピペしたときに、タブとスペースの混在によって kubectl apply がエラーになってしまう。他にも、kubectlコマンドにエイリアスを設定したという人もいるようだ。

udomomo.hatenablog.com

kubectl port-forwardでクラスタ外からPodにアクセスする

Cluster内にIngressを立てたがうまく動作しないことがあり、デバッグのためPod内のアプリケーションにアクセスしたいと思った。今までは、テスト用のPodを立ててその中からcurl等でリクエストを送っていた。

kubectl run test-pod --image nginx -- /bin/sh -c 'curl <target_pod's_ip_address>'

しかし、 kubectl port-forward コマンドを使うことで、テスト用Podなしでデバッグができることを教えてもらった。

kubernetes.io

kubectl port-forward <target_pod's_name> 8080:80

このようにすることで、ホストの8080番ポートがPodの80番ポートにPort-forwardingされるため、 localhost:8080 でアクセスが可能になる。ブラウザで見られるのがありがたい。

kubectl port-forward のオプションにはServiceも指定できるが、内部的にはPodを指定するのと同じであり、ClusterIPを通さず直接Podにアクセスしていることに注意したい。ClusterIPにアクセスしたい場合は以下の記事が参考になる。

qiita.com

sh -cでコマンドを渡すときはシングルクォートを使う

KubernetesのPod内の環境変数が正しく設定されているかを確認しようとして、以下のようにしたところ、何も出力されなかった。

$ kubectl exec -it pod_name -- /bin/sh -c "echo $VAR_NAME"

Podの中に入って確認すると正しく出る。

$ kubectl exec -it pod_name -- /bin/sh
# echo $VAR_NAME
test

これは、 /bin/sh -c に渡したコマンドがダブルクォートだったことが原因。 sh -c を使うと、 -c 以下で渡したコマンドを sh で実行させることができるが、ダブルクォートにしてしまうと kubectl を実行しているシェルで変数が解釈されてしまう。 $VAR_NAME という環境変数はPodの中にはあるがhostでは設定されていないので、単なる echo を渡していることと同じになり、何も出力されずに終わる。

正しく確認するには、シングルクォートで渡せば良い。シングルクォートで囲われた内容は展開されないため、 $VAR_NAME がPodに渡る。

$ kubectl exec -it pod_name -- /bin/sh -c 'echo $VAR_NAME'
test

netcatのzero I/Oモードで疎通確認する

疎通確認をする際はcurlwgetを使うことが多かったが、CKADの講座で講師がnetcatを使っているのを見て調べてみた。

netcatはサービスとの通信を確認できるコマンドであり、たいていのLinuxディストリビューションに同梱されている。KubernetesのPodに入ってコマンドを打つ場合、imageにcurlが入っていないというようなことも多いので、使えるようにしておくと便利かもしれない。

netcatでの通信は、基本的に相手のホスト名とポートを指定すればよい。

nc google.com 80

これに加え、netcatではzero I/Oモードを使ったポートスキャンを行うことができる。これはパケットを飛ばさず、相手のポートに接続できるかのみを確認するモードで、接続に成功すればすぐに接続が閉じられる。実際にデータのリクエスト・レスポンスをすることがないので、疎通確認に適している。

CKAD講座では、Podの中からService名を指定して疎通できるか確認していた。

nc -z -v -w 1 service-name 80

-z でzero I/Oモードを使える。 -v は詳細な出力をするオプション。 -wタイムアウト判定までの秒数を指定するもので、これを指定しないと接続に失敗した場合コマンドが終了しない。

【GitHub Actions】AWS Lambdaの環境変数を自動で更新する

最近システムのリリースフローの自動化を進めることが多い。先日はLambda関数の更新の自動化を行った。この関数は環境変数を持っており、これを頻繁に更新する必要があった。

新しい環境変数をどのように渡すかが最も難しいポイントだったが、この環境変数AWS CLIコマンドにおいてJSONファイル形式で渡すことができる。

docs.aws.amazon.com

aws lambda update-function-configuration --function-name "${function_name}" --environment fileb://env.json

--environment オプションの後にファイルパスを指定する。 AWS CLIでは fileb:// とつけることで、ファイルの中身をバイナリコンテンツとして渡すことができる。面倒なファイル読み込みの処理を書かずに済むため、この記法はとても気に入っている。また、このJSONファイルをGitHub上に置けるため、環境変数のバージョン管理がしやすくなるという利点もある。

JSONファイルのフォーマットは以下の通り。 SECRET_ENV は機密性の高い環境変数であり、値はGitHub ActionsのSecretsに登録してある。JSONファイル上では値を適当なものにしておく。

# env.json
{
  "Variables": {
    "NORMAL_ENV": "example",
    "SECRET_ENV": "to_be_converted"
  }
}

workflowファイルには以下のように記載する。 jq コマンドでJSONファイルの SECRET_ENV の値を置換しているのがポイント。 --arg オプションとして secret_env として ${{ secrets.SECRET_ENV }} の値を渡し、その値をJSONファイル内の to_be_converted と入れ替えている。

- name: Update lambda envvar
      env:
        LAMBDA_FUNCTION_NAME: dev_function
        ENV_JSON_FILE: env.json
        SECRET_ENV: ${{ secrets.SECRET_ENV }}
      run: |
        jq --arg secret_env "${SECRET_ENV}" '.Variables.SECRET_ENV = $secret_env' "${ENV_JSON_FILE}" > env_converted.json
        aws lambda update-function-configuration --function-name "${LAMBDA_FUNCTION_NAME}" --environment fileb://env_converted.json

【Kubernetes】--dry-runでmanifestファイルの雛形を手に入れる

CKAD対策で学んだtips。
Kubernetes上のリソースを作る際、複雑な設定の場合はmanifestファイルを書かなければいけない。しかしYAML形式で記述を行うのはとても時間がかかるうえ、それぞれのリソース種別ごとの記法を全部は覚えられない。公式ドキュメントからコピペしてくることもできるが、それも探すのが面倒なうえタイムロスになる。
そこで、manifestファイルの雛形を手に入れたいときは --dry-run が役立つ。

--dry-run はkubectlコマンドのオプションで、 applycreate を実行せず、クライアントサイドでできるバリデーションを行ってくれる。この出力をYAML形式でファイルに書き出せば、それがmanifestファイルの雛形になる。

$ kubectl run nginx --image=nginx --dry-run=client -o yaml > nginx.yaml
$ cat nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    run: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      run: nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}

これをもとに各項目の値を変えることができる。ゼロからmanifestファイルを作るよりはるかに楽。
なお、設定すべき項目のうちコマンド上で設定できるオプションをあらかじめ設定してファイルに出力すると、作業がより効率的になる。

【GitHub Actions】ファイルパスのワイルドカード指定には「*」では不十分

先日いつものようにプルリクエストを作ったら、GitHub Actionsのworkflowが全く動いてくれなかった。GitHub ActionsのSpending limitやGitHub自体の障害も疑ったが、原因はもっと単純なものだった。

yamlファイルでは、 on 節を以下のように記述していた。

on:
  pull_request:
    branches:
      - master
    paths:
      - '*'
      - '!README.md'
      - '!.gitignore'
      - '!.github/workflows/dev-workflow.yaml'
      - '!.github/workflows/release-workflow.yaml'
...

これでコードのファイルは全て検知すると思っていたが、 * を指定していたのが問題だった。GitHub Actionsでは、 * ではスラッシュにマッチしないため、これではトップディレクトリにあるファイルしか検知してくれない。

docs.github.com

workflowが動かなかったプルリクエストでは、 src/main.py のみに差分があったため、上記の条件では検知されなかった。それ以前のプルリクエストでは、READMEやworkflowファイルを合わせて更新していたので、たまたま問題なかっただけだった。

サブディレクトリ以下にあるファイルにもマッチさせたい場合、 ** と指定すればよい。

on:
  pull_request:
    branches:
      - master
    paths:
      - '**'
      - '!README.md'
      - '!.gitignore'
      - '!.github/workflows/dev-workflow.yaml'
      - '!.github/workflows/release-workflow.yaml'
...

ただ、このように「少数のファイル以外全てにマッチさせたい」という場合、 paths-ignore を使う方がより単純に書ける。

docs.github.com

on:
  pull_request:
    branches:
      - master
    paths-ignore:
      - 'README.md'
      - '.gitignore'
      - '.github/workflows/dev-workflow.yaml'
      - '.github/workflows/release-workflow.yaml'
...