【AWS Lambda】dead_letter_queueからPagerDutyにアラートは飛ばない

本番システムで使われているAWS Lambda functionが失敗した際にPagerDutyでアラート通知を受けたいと思い、開発用のLambda functionにdead_letter_queueを設定し、そのSNS queueにPagerDutyのエンドポイントをsubscribeさせた。
しかし、Lambda functionを失敗させてからいくら待ってもアラートが来ない。確認のため、Slackに通知を投げる別のLambda functionも合わせてsubscribeさせたが、そちらには通知が飛ぶ。

PagerDuty側の原因ではないかと思い調べてみたところ、PagerDutyとAWSのIntegrationで表示されるのはAWS CloudWatchのみだった。PagerDutyは、CloudWatchからSNS queueに送られたメッセージを受信し、そこからAlarm状態を検知することでAlertを飛ばす。しかし、今回は aws lambda invoke コマンドでLambda functionを実行させていたため、dead_letter_queueから送られたメッセージは空だった。これではPagerDutyがAlarm状態だとみなさない。

メッセージの中身を解析して人為的に同じ内容を飛ばすようにしても良いが、そうするくらいであればCloudWatchのAlarmを設定した方が早い。結局dead_letter_queueの使用をやめ、CloudWatchからSNS queue経由でPagerDutyにメッセージを飛ばすように変更した。dead_letter_queueは失敗検知には便利だが、あくまで「実行に失敗したイベント」を拾うためのものであり、アラートの完全な代用にはならない。

ArgoCDを使ってみる

ArgoCDをローカルで動かしてみたいと思い、以前使ったKubernetes講座のmanifestファイルでやってみた。

github.com

まず、以下のチュートリアルのステップ4まで行い、 argocd namespaceでArgoCDのサーバを起動させる。

argoproj.github.io

その後、 argocd namespaceにAppProjectとApplicationのmanifestファイルを作れば、宣言的にArgoCDの設定を定義できる。ローカルのKubernetesクラスタで動かしたので、namespaceはdefaultに設定した。

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: default
  namespace: argocd
spec:
  description: argocd project
  clusterResourceWhitelist:
  - group: '*'
    kind: '*'
  namespaceResourceBlacklist:
  - group: ''
    kind: ResourceQuota
  - group: ''
    kind: LimitRange
  - group: ''
    kind: NetworkPolicy
  destinations:
  - namespace: 'default'
    server: 'https://kubernetes.default.svc'
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: wordpress-challenge
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default

  source:
    repoURL: https://github.com/Udomomo/kubernetes-challenge-1-wordpress
    targetRevision: argocd
    path: app

  destination:
    server: https://kubernetes.default.svc
    namespace: default

これらのファイルをapplyするだけで設定は完了する。レポジトリの argocd ブランチをトラックするように設定したので、後は app ディレクトリ下にあるアプリケーションコードを更新し、syncさせれば設定が反映される。
AppProjectやApplicationの設定を更新したい場合は、当該manifestファイルを更新してapplyし直せば良い。

f:id:Udomomo:20201115235229p:plain

このようにArgoCDを使ってアプリケーションを管理する場合、ArgoCDの管理下のレポジトリにmanifestファイルをプッシュすれば、自分が権限を持っていない種類のresourceでもArgoCDを通してapplyできてしまうことが考えられる。そこで、resourceの種類ごとにブラックリスト/ホワイトリストを設定することも可能。 clusterResourceWhitelist clusterResourceBlacklist namespaceResourceWhitelist namespaceResourceBlacklist の4種類が設定できる。
上記のmanifestファイルでは、 namespaceResourceBlacklist にいくつか指定してみている。試しにLimitRangeのmanifestファイルをプッシュすると、ArgoCDがSyncを拒否して失敗した。

f:id:Udomomo:20201115234923p:plain

【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