りんごとバナナとエンジニア

エンジニア修行の記録

【Kustomize】patchesJson6902の使い方と利用シーン

Kustomizeを使うとき、 patches でファイルを指定してbaseファイルを部分的に変更することが多いと思う。しかし先日、 patches ではなく patchesJson6902 という指定をしているファイルを見かけたので、どう使うのか調べてみた。

patchesJson6902とは

patchesJson6902は、名前の通りRFC6902に沿った方法でPatch処理を行うことができる。RFC6902ではJSON Patchについて定義されている。

tools.ietf.org

Kustomizeは公式ドキュメントをたどりづらいが、Kubernetesのドキュメントの中に詳しい使い方が記載されている。 kubernetes.io

patchesStrategicMergepatchesJson6902 の違い

Kustomizeでは、patchを行う際に patchesStrategicMergepatchesJson6902 の2つの方式に対応している。 patch フィールドは渡したファイルまたはPatchをもとに、どちらの方式かを自動で判定して適用してくれる。
この2つの方式がどう違うのかというと、 patchesStrategicMerge はマージの挙動をKubernetesの設定ファイルに最適化されるように変更している。例えば、以下のようなdeployment.yamlがあるとする。

# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.1
        ports:
        - containerPort: 80
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
      - name: nginx
        image: nginx:1.14.3
        ports:
        - containerPort: 80

# base/kustomization.yaml
resources:
  - deployment.yaml

overlaysを以下のようにする。

# overlays/test/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  template:
    spec:
      containers:
        - name: busybox
          image: busybox:1.33
          ports:
            - containerPort: 80

# overlays/test/kustomization.yaml
bases:
  - ../../base

patchesStrategicMerge:
  - deployment.yaml

このとき、buildした結果は以下のようになる。overlaysで指定したコンテナが、 containers に追加されていることがわかる。

$ kustomize build . 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: busybox:1.33
        name: busybox
        ports:
        - containerPort: 80
      - image: nginx:1.14.1
        name: nginx
        ports:
        - containerPort: 80
      - image: nginx:1.14.2
        name: nginx
        ports:
        - containerPort: 80
      - image: nginx:1.14.3
        name: nginx
        ports:
        - containerPort: 80

一方で、これと同じことを patchesJson6902 で行おうとすると、overlaysを以下のようにしなければならない。

# overlays/test/kustomization.yaml
bases:
  - ../../base

patchesJson6902:
  - target:
      group: apps
      version: v1
      kind: Deployment
      name: nginx-deployment
    patch: |- # patchの内容は別ファイルに指定しても良い
      - op: add
        path: /spec/template/spec/containers/0
        value: {"image": "busibox:1.33", "name": "busybox", "ports": [{"containerPort": 80}]}

patch 内の pathJSON pathを指定する必要があり、ここで配列のインデックス番号を指定したうえで、配列に入れる新たな要素を定義する必要がある。 patchesStrategicMerge の場合と同じようにしてしまうと、 containers が丸々入れ替わって busybox のみになってしまう。

patchesJson6902 を使うべきシーン

逆に「配列の中の特定の一要素を入れ替えたい場合」は patchesJson6902 の方を使うべき。

# overlays/test/kustomization.yaml
bases:
  - ../../base

patchesJson6902:
  - target:
      group: apps
      version: v1
      kind: Deployment
      name: nginx-deployment
    patch: |-
      - op: replace
        path: /spec/template/spec/containers/0
        value: {"image": "busibox:1.33", "name": "busybox", "ports": [{"containerPort": 80}]}

opreplace に変更したことで、 containers 配列の0番目の要素のみを入れ替えることができる。結果は以下の通り。

$ kustomize build .  
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: busibox:1.33
        name: busybox
        ports:
        - containerPort: 80
      - image: nginx:1.14.2
        name: nginx
        ports:
        - containerPort: 80
      - image: nginx:1.14.3
        name: nginx
        ports:
        - containerPort: 80

これに対して patchesStrategicMerge の場合、 $patch: replace を指定することで containers 配列全体を入れ替えることはできるが、特定の要素を指定してそこだけ入れ替える機能はない。baseの containers 配列全体をoverlaysに書いてそこに新しい要素を加えるという方法はあるが、配列の要素が多い場合は非効率である。

各Patch方式の詳細な挙動は、以下が参考になる。

community/strategic-merge-patch.md at master · kubernetes/community · GitHub kustomize/inlinePatch.md at master · kubernetes-sigs/kustomize · GitHub

「レガシーコードからの脱却」を読んだ感想

昨年の10-12月まで、社内の輪読会で「レガシーコードからの脱却」を読んだ。(最初間違えて「レガシーコード改善ガイド」を買おうとしたのは内緒だ)

www.oreilly.co.jp

感想など

タイトルの通り、レガシーコードが増えていく企業ソフトウェアの開発ノウハウについて書かれた本。前半はアジャイル開発の方法論がメインで、正直この辺はより詳しいアジャイル本を読んだ方が良いと思う。しかし後半になると、単なるリファクタリングにとどまらない本質的な内容がたくさんあった。

ペアプログラミングの恐怖を乗り越える

過去に何度かペアプログラミングを行ったことがあるが、正直あまり良い印象を持っていなかった。会議室に一日中際限なく拘束されるうえに一挙手一投足を監視され、場の空気が非常に重くなりチームとしての生産性も非常に低かったからだ。
しかし8章を読んで、ペアプログラミングに対する誤解に気付かされた。良いペアプログラミングを行う前提として、「全員が問題を理解していること」そして「全員がメンターでありメンティーであること」が重要となる。ジュニアであっても時には問題解決のためにシニアに助言や意見を言うべきであり、全員が同時に問題解決にあたりディスカッションが自然と起こるような場作りが重要になる。ただ長く集まったり、交代でキーボードを打てば良いというものではない。
それでもペアプログラミングの気が進まないという人のために、本書では代替案も提示している。例えばある小さな問題を調査するために時間を区切って集まるスパイクや、1時間だけ2人が集まりお互いのその日のコードをレビューするバディプログラミングなどがある。大事なのは孤独な作業に徹することではなく、どのような形式であれチームメンバーと協力しあうこと・助けを求めることだ。

CLEANコードの意義

CLEAN原則についてはいろいろな本で目にしており頭では知っていたが、本書の9章を読むことで心で理解できた気がする。5つの原則(凝集性・疎結合カプセル化・断定的・非冗長)は根元では全てつながり合っており、その意義は「結合すべきものは結合し、分離すべきものは分離している状態」を作り出すことにある。こうすることで、レガシーコードを最小限に抑え、ソフトウェアの保守・拡張を容易にすることができる。
また、むやみやたらに責務の分離をさせるのも良くない。12章に書かれているが、「銀行に行く」というTODOを分解して「電車の切符を買う」というような細かすぎるタスクを作る人はいない。個々のクラスの責務が過不足なく意味が通るレベルにすることで、コード全体の抽象度が揃い、責務がわかりやすくなる。

創発設計

テストファーストで実装し、常にレガシーコードにならないように気を配りながら書いていくと、責務の分割や名前などに課題が見つかっていく。これを繰り返していくと、書いている途中でより良い設計が浮かぶ。最初にすべての設計を決めてからそのとおりに実装するよりはるかにましになる。この流れを本書では「創発設計」と呼んでいる。
このやり方は初心者には厳しいものがあるかもしれないが、アジャイル開発を行っている際に仕様や実装が大きくひっくり返る経験は何度もしたことがあるので、それが言語化されてとても共感できた。

既存のアーキテクチャに無慈悲になる

最終章に少しだけ書かれていた言葉だが、非常に刺さった。自分はどうしても「既存の設計を学んで理解する」ことに意識を向けがちだが、そこで終わらず「既存の設計の問題点を理解する」ところまで行かなければ、そもそも改善しようという発想に至らない。レガシーコードを改善するためには、まず目の前にあるものがレガシーコードではないかと認識する必要がある。
改善案も既存の設計をベースにした小さなものばかりではなく、時には全く新しい設計にする方が優れたコードになるかもしれない。そのような改善には新しい知識や経験が必要になることもあり、そのために自分たちは業務内だけでなく業務外でもより多くの技術を身につける必要がある。ここは自分の大きな弱点なので、少しずつ考え方を変えていきたい。

まとめ

真新しい概念は出てこないが、今まで知っていた概念とその意義を腹落ちさせてくれる良書だった。輪読会という形式で1章ずつ精読していったことが功を奏したのかもしれない。時間がかかってもじっくり読みたい本だと思う。

【Terraform】Autoscaling GroupのLaunch Configurationを変更するのに失敗した話

先日、新しいECS ClusterをTerraformで作った。EC2インスタンスは1台のみだが、Launch Configurationを用意してAutoscaling Groupを作成した。

resource "aws_launch_configuration" "ecs_cluster" {
  name                   = "ecs_cluster"
  image_id             = data.aws_ami.aws_optimized_ecs.id
  iam_instance_profile = aws_iam_instance_profile.ecs-cluster.name
  security_groups      = [aws_security_group.ecs-cluster.id]
  user_data            = file("${path.module}/user_data.sh")
  instance_type        = "t3.medium"

  root_block_device {
    delete_on_termination = true
    encrypted             = false
    volume_size           = "30"
    volume_type           = "gp2"
  }
}

しかしその後、key_nameの登録を忘れておりEC2インスタンスssh接続できないことに気づいたため、Launch Configurationを修正した。

 resource "aws_launch_configuration" "ecs_cluster" {
   name                   = "ecs_cluster"
   image_id               = data.aws_ami.aws_optimized_ecs.id
   iam_instance_profile = aws_iam_instance_profile.ecs-cluster.name
   security_groups      = [aws_security_group.ecs-cluster.id]
   user_data            = file("${path.module}/user_data.sh")
   instance_type        = "t3.medium"
+ key_name           = "ecs_cluster"

   root_block_device {
     delete_on_termination = true
     encrypted             = false
     volume_size           = "30"
     volume_type           = "gp2"
   }
 }

ところが、この状態で terraform apply しようとしても失敗してしまった。

Error: error deleting Autoscaling Launch Configuration (terraform-20201203072824459100000004): ResourceInUse: Cannot delete launch configuration terraform-20201203093824531400000001 because it is attached to AutoScalingGroup ecs_cluster

これは、TerraformがLaunch Configurationを一旦削除し、同名のものを新しく作って追加しようとしているため。 terraform plan の結果は Plan: 1 to add, 1 to change, 1 to destroy. となっており、 changeはAutoscaling Groupを、addとdestroyはLaunch Configurationを指している。

これを避けるためには2つの処理が必要となる。まず lifecycle の中で create_before_destroy = true を追加すること。通常であれば、resourceに対するupdateができない場合Terraformは古い方をdestroyしてから新しい方をcreateするが、この指定をすると先に新しいresourceのcreateが行われ、その後古い方をdestroyするようになる。これにより、Autoscaling Groupが起動しているのにLaunch Configurationがなくなってしまうということが起こらない。
もう一つは、 name の指定をやめるか、あるいは name_prefix の指定に変えること。これにより、新旧のLaunch Configurationが異なる名前を持つようになる。この指定を行わないと、同じ名前を持った新旧のresourceが一時的に併存することになってしまい、applyに失敗する可能性がある。

 resource "aws_launch_configuration" "ecs_cluster" {
- name                   = "ecs_cluster"
+ name_prefix            = "ecs_cluster_"   
  image_id             = data.aws_ami.aws_optimized_ecs.id
   iam_instance_profile = aws_iam_instance_profile.ecs-cluster.name
   security_groups      = [aws_security_group.ecs-cluster.id]
   user_data            = file("${path.module}/user_data.sh")
   instance_type        = "t3.medium"
   key_name           = "ecs_cluster"
    
+  lifecycle {
+    create_before_destroy = true
+  }

   root_block_device {
     delete_on_termination = true
     encrypted             = false
     volume_size           = "30"
     volume_type           = "gp2"
   }
 }

このようにすると terraform apply が成功した。

xargsの各オプションにおける引数の渡され方

普段xargsをよく使っているが、いつも -I オプションで引数をループさせる用途であることがほとんどなので、xargsはループ用のコマンドだと思いこんでいた。そのため先日、別の人のシェルスクリプトを読んだときに混乱してしまったのでまとめておく。

オプションなし

そもそもxargsは、標準入力として受け取ったデータをパラメータとして別のコマンドに渡すためのもの。ここでいうパラメータとは、標準入力を空白文字・改行の両方で区切ったものを指す。
デフォルトでは、xargsは受け取った全てのパラメータを1つのコマンドに対する引数として渡す。そのため、xargs以降に指定したコマンドは1回のみ実行される。

echo hoge fuga\\npiyo | xargs echo
hoge fuga piyo # echo hoge fuga piyoが実行された

-I オプション

-I オプションを使うと、標準入力が改行で分割され、コマンド1回の実行につき1行ずつ使用される。そのため、xargs以降に指定したコマンドは、標準入力の行の数だけ実行されるようになる。使い所が多く便利。
for ループとの違いは、引数が1つ渡されるたびにxargs以降のコマンドが実行されること。 for ループの場合、for i in... の部分のコマンドがすべて終了し、繰り返される要素がすべて揃って始めて for ループ内部のコマンドが実行される。

echo hoge fuga\\npiyo | xargs -I {} echo {}
hoge fuga # echo hoge fugaが実行された
piyo # echo piyoが実行された

-n オプション

-n コマンドは、xargs以降に指定したコマンドが、最大いくつのパラメータを使えるかを指定するもの。例えば -n 1 と指定すると、パラメータが1つずつ渡され、xargs以降に指定したコマンドはパラメータの数だけ実行される。 -n 1 の場合、 -I の場合よりもさらに細かい単位で分割されることに注意。

echo hoge fuga\\npiyo | xargs -n 1 echo    
hoge # echo hogeが実行された
fuga # echo fugaが実行された
piyo # echo piyoが実行された

【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