shibatch's journey

日々考えていることをつらつら書くだけです

skaffoldでのデプロイに時間がかかっていたのでやったこと2つ

skaffoldというKubernetesでbuild〜push〜deployを一気通貫にやってくれるツールがあります。 GitHub Workflowでskaffoldを叩いてdeployする運用をやっているのですが、これが最近とても遅く(1時間とかかかってた…)改善するべく2つのことをやったのでメモ。

BuildKitを有効化する

これを有効化するとリモートキャッシュが有効化されてビルド時間が短くなりました。例えば、staging環境でbuild〜deployしたものをそのままproduction環境でdeployする、といった場合はリモートキャッシュのデータをそのまま使ったりしてbuild自体スキップしたりします。

skaffold.yamlに以下の設定を入れるだけです。

     artifacts:
     - image: containers.repo.com/example/package
       context: ../
+    local: 
+      useBuildkit: true 

↓のようなログが出てくれば、リモートキャッシュが使われている証拠。

 - containers.repo.com/example/package: Found Remotely

deploy完了後のステータスチェックをやめる

skaffold run コマンドでbuild〜push〜deployまで一気通貫にできるのですが、デフォルトだとdeployした後Podがすべてupdateされるまで待ち、ステータスチェックをします。

この場合、たとえば100Pod立っていてローリングアップデートを少しずつ(5%とか…)するような運用にしている場合、延々と完了を待つことになります。

これがむちゃくちゃに時間がかかっていたのでやめました。

skaffold run -f ./skaffold.yaml -p $SKAFFOLD_ENV --status-check=false

↑このstatus-check=false の部分

これでCIで延々とdeploy終わらん〜〜という状況は無くなりました。(1時間→5分くらいになった)

CI完了=ローリングアップデート完了 ではなくなる点は留意が必要。でもやってよかったです。

Kubernetesでcert-managerを利用したときにつまづいたこと2点

昨日からKubernetesのcert-managerと戦っている。Let's Encryptをプライベートクラウド上で動かしたい。

cert-manager.io

実施するにあたり以下のQiitaの記事がとても役にたった…ありがとうございます。 Ingress Controllerはnginx controllerを使ったのでそこは読み替えたが、おおむね書いてある通りにやりました。

Kubernetes cert-managerでRoute53で作成したドメインの証明書を作成し、Ingressで利用する。 - Qiita

さてcert-managerではDNSとHTTPの2種類のチャレンジを使うことができるのだが、自分はDNSの形式で、Route53を使って認証する方法を使った。

Route53 - cert-manager Documentation

ほぼ↑に書いてある通りだったのだが、2点つまづいたことを書く。

まずAWSでIAMポリシーを作る必要があるのだが、手順では以下のように書いてある

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "route53:GetChange",
      "Resource": "arn:aws:route53:::change/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "route53:ChangeResourceRecordSets",
        "route53:ListResourceRecordSets"
      ],
      "Resource": "arn:aws:route53:::hostedzone/*"
    },
    {
      "Effect": "Allow",
      "Action": "route53:ListHostedZonesByName",
      "Resource": "*"
    }
  ]
}

Note: The route53:ListHostedZonesByName statement can be removed if you specify the (optional) hostedZoneID. You can further tighten the policy by limiting the hosted zone that cert-manager has access to (e.g. arn:aws:route53:::hostedzone/DIKER8JEXAMPLE).

つまりIAMポリシーの以下の部分は無くしても、zoneをちゃんと指定したら動くとのことだ。

    {
      "Effect": "Allow",
      "Action": "route53:ListHostedZonesByName",
      "Resource": "*"
    }

まぁポリシーなんて動くなら狭いに越したことはないので、ドキュメント通りにこの段落は消した。

そしてIssuerをデプロイしたらchallengeリソースで権限周りでこけてしまった

% k describe challenge hogehoge-jp-tls-z8w44-2448885068-2094531131
<snip>
Events:
  Type     Reason        Age                  From                     Message
  ----     ------        ----                 ----                     -------
  Normal   Started       3m12s                cert-manager-challenges  Challenge scheduled for processing
  Warning  PresentError  44s (x6 over 3m11s)  cert-manager-challenges  Error presenting challenge: failed to determine Route 53 hosted zone ID: AccessDenied: User: arn:aws:iam::XXXXXXX:user/hogehoge is not authorized to perform: route53:ListHostedZonesByName because no identity-based policy allows the route53:ListHostedZonesByName action

これは単に私のIssuerの設定漏れで、hostdZoneIDにRoute53のZoneIDを付与したら先に進んだ。

     - dns01:
         route53:
           region: ap-northeast-1
+          hostedZoneID: AAAAAAAAAAAA
           accessKeyID: XXXXXXXXXXXXXXXXXXX
           secretAccessKeySecretRef:
             name: secret

ところが次はchalengeがpending状態で止まってしまった。

% k describe challenge hogehoge-jp-tls-z8w44-2448885068-2094531131
<snip>
  Reason:      Waiting for DNS-01 challenge propagation: DNS record for "example.com" not yet propagated

これは以下の事象に当てはまったようだった。 https://cert-manager.io/docs/configuration/acme/dns01/#setting-nameservers-for-dns01-self-check

つまり、cert-managerはRoute53へTXTレコードを埋め込んだ後それが引けるかを確認する際、/etc/resolf.confの内容をみて権威ネームサーバを参照するようだが、これがうまくいっていないらしい。その原因までは掴めなかったのだが、cert-managerの環境変数でパブリックDNSを指定したらうまくいった。

 spec:
   containers:
   - args:
     - --v=2
     - --cluster-resource-namespace=$(POD_NAMESPACE)
     - --leader-election-namespace=kube-system
+    - --dns01-recursive-nameservers-only 
+    - --dns01-recursive-nameservers=8.8.8.8:53,1.1.1.1:53 

とりあえずこれで参照できるようになったよというメモ。

RSA暗号のキーペアはどの程度の頻度でローテーションするべきか

CloudFrontには署名付きURLを発行する機能がある。これはRSA暗号のキーペアを用いて期限付きのURLを発行する機能で、手元に秘密鍵、CloudFrontに共通鍵を置いて、たとえば以下のようなコマンドで発行することができる。

aws cloudfront sign --url https://XXXXXXXX.cloudfront.net/backet_name/dummy.data \
--key-pair-id ********* \
--private-key file://private_key.pem \
--date-less-than 2023-01-31T20:30:00+09:00

さてこのようなキーペアをつかった認証方式はAWSのドキュメントをみると定期的に更新 (変更) することをお勧めします書いてある。定期的って何よ、どの程度なの…??という疑問があがったので調べてみた。

日本語の文献ではこれ!!というベストプラクティスはなかったけれども、海外の文献だと2パターン見つかったのでここに紹介する。どちらもSSHの鍵に関してだけれども、RSA暗号のキーペアと広げてみてもよさそう。

12ヶ月ローテーション説

これは米NIST(米国立標準技術研究所 - セキュリティの勉強をすると1度は聞く名前)が2013年に発行したドラフト版の勧告に書いてある、SSHの鍵に関するもの。

datatracker.ietf.org

2013年なので古い上にExpires: October 06, 2013 となっていてどこまで参考にするかはなかなか難しいところはあるけれど、以下のように書いてある。

Authentication credentials for all trust relationships leading to moderate-impact and high-impact systems MUST be rotated every 12 months, and it is RECOMMENDED that trust relationships leading to low-impact systems be rotated every 12 months.

ざっくり言うとシステムの重要度によって差はあるけど12ヶ月に1度は更新しなさいよ、ということ。

45日〜30日ローテーション説

これはトレンドマイクロが出しているAWS IAM Best Practicesに書かれているもの。 3パターンが書いてあり、90日だと「許容できないリスク(not acceptable risk)」であり、45日は「達成されなければならない(should be achieved)」、30日は「許容範囲内(generally tolerable level of risk)」となっている。

こちらはIAMのアクセスキーの管理に関するベストプラクティスで、割と新しめで、パブリッククラウドで使う鍵の管理として参考にできそうな値である。

結局どうすればいいの

2つの説を紹介したけれど、12ヶ月or45日〜30日 とかなり幅がある。これはこの10年の間でプラットフォームを取り巻く環境が変わり、所有しているサーバへのログイン→パブリッククラウドを用いた構成 に変遷していることは無関係ではなさそう。 結局扱っているサーバの重要度次第なところはあるけれど、トレンドマイクロの基準はIAMだけではなくて割と汎用的に使える考え方ではないかなぁと思ったのでパブリッククラウドで扱う場合は45日〜30日に一度の更新はひとつの目安として良さそう

今回調査するにあたり以下の記事を大いに参考にさせていただいた。

www.thesslstore.com

このブログ記事はThe SSL Store という海外にあるドメイン価格.comみたいなところの記事で信憑性は高そう。また、他にもSSHキーのベストプラクティスがいろいろ載っているので興味のある方はどうぞ。

reference:

S3からダウンロードする場合と間にCloudFrontを挟んだ場合の時間の比較

S3からデータをダウンロードする際、直接ダウンロードする場合と、間にCloudFrontを挟んだ場合でダウンロード時間を比較してみた。

比較方法は簡易的なもので、

  • S3に2種類のサイズ(50MB / 1GB)のダミーファイルを置いて
  • S3 / CloudFrontそれぞれで署名付きURLを発行して
  • wgetでダウンロードするのにかかった時間をtimeコマンドでラップしてみる
  • S3は東京リージョンで
  • 距離による差をみるために東京 / 沖縄でそれぞれ取得する

というもの。 (沖縄でのダウンロード計測は @r_nakamineさんにご協力いただきました!感謝!)

前提は置いておいて結論はこの表です。

  S3: 50MB CF: 50MB S3/CF(倍) S3: 1GB CF: 1GB S3/CF(倍)
東京(単位はsec) 9.947 2.627 3.78644842 177.305 43.5465 4.071624585
沖縄(単位はsec) 4.985 3.24 1.538580247 103.375 56.125 1.841870824
  • S3直接ダウンロードよりもCloudFrontを挟む方が速い(実績値:2倍〜4倍くらい)
  • ダウンロードする場所は日本国内であればさほど影響を与えるほどではないといえる
    • おそらく…
    • 距離よりは環境による影響が強い

実際にやってみるとちゃんと環境を揃える必要性を感じた が、それを差っ引いてもS3直接よりCloudFrontを挟んだ方がずっと早くなることがわかった。

コンテンツをダウンロードするようなシステムを構築するときはWAFの機能を期待するほかに、ダウンロード時間が早くなる期待をもって良さそう。

2022年のふりかえりを手短に

今年は苦しいこともあったけれども全体的には良い年だった。

仕事ではチーム替え後本格的にサービスキャッチアップと改善に取り組んだ。K8sクラスタの改善とAWSを用いた構成の改善をどちらも進めることができ、割と今まで書いてこなかったコードも(必要に迫られて)書いてったし、仕事ではいちばん飛躍した年だと思う。

あと自分の組織内での立ち位置というか、役割のようなものがなんとなく見えてきたというか、自分に何が期待されているかがちょっとわかってきた気がする。おそらく。

10月からは初めて新加入した人のオンボーディングバディも経験し、「自分なら新しく入社したら何に困るだろう?」と考えてフォローすることを心がけた。うまくオンボーディングできたかはむしろ今後評価されるのだろう。

悪いことは割と体調に無理がきかないというか、締め切り、約束の期日を守るために無理をしがちだけど無理して成果を出すことの限界が見えてきたようなところがある。来年は無理せず成果を出すようにしたい。

プライベートではディズニーランド・シーに1回ずつ行ったり浜松や関西、鹿児島へ旅行行ったり楽しいことをたくさん経験できた。。良い年だった。

総じて良い年だったと思う。ほんとうに。いろんな人に感謝です。来年もよろしくお願いします。

2022年に私が行ったK8sクラスタの改善まとめ

この記事はGMOペパボエンジニアカレンダーの12/20の記事です。7日遅れです。ほんとごめんなさい。

こんにちは。技術部プラットフォームグループのshibatchです。私は2022年に入ってからSUZURIのプラットフォーム改善─とりわけK8sAWS周りの改善に取り組んできました。そういえば自分の成果について一年を通した振り返りはしてないなぁ、と気づいたので、この機会にSUZURIのK8s環境の何をどう改善していったかをアウトプットする試みをします。

結論だけを言うと

  • K8s環境でコスト周りの改善、認知負荷を減らす改善を続けていました

かんたんに環境を説明

振り返りの前に、まずSUZURIにおけるK8s環境についてかんたんにご説明します。SUZURIはユーザーさんがアップロードした画像をTシャツなどに商品化することができるサービスです。そのために、アップロードした画像とTシャツ画像の合成処理が必要になります。その画像合成をK8s環境で行なっています。

K8s環境はProduction(本番)環境はNKE(ペパボのプライベートK8s環境) / GKE(Google Kubernetes Engine)という2つのK8s環境にまたがっています。GKEではStaging環境、Sandbox(テスト)環境、Production環境を構築しており、NKEではProduction環境のみを構築しています。

構成図

また、以下の3つのdeploymentが動いています。

  • lens1 … 画像合成アプリケーション。Ruby製。現在いちばん多く使用している。CoffeeScript製。
  • lens2 … lensの後継となる画像合成アプリケーション。徐々にlens1から移行してきている。Rust製。
  • lens2-a(ここでの便宜上の呼び方です) … lens2で特定の商品用に動いている画像合成アプリケーション。Rust製。

※lens2(Rust製)については日経XTECHに記事がありますので併せてご参照ください!

課題は何か?

SUZURIのK8s環境を触ってみると、複数課題がありそうなことがわかってきました。

  • lens1でEvictedになっているPodが多い
    • アプリケーションのメモリの使用量がPodやノードのリソース設定と合っていない可能性がある
  • lens2 / lens2-aが環境ごとにノードのリソース設定が揃っていない
    • productionではCPUリソースを6000m(!)Requestしている
    • staging/sandboxではリソースのRequest / Limitは設定されていない
    • stagingとproductionで設定が揃っていないので検証が正しくProductionのリハーサルにならない恐れがある
    • 環境でリソースが揃っていないのは認知負荷が高いように思う
  • lens2 / lens2-aではメモリのRequest / Limitが設定されていない
    • どの程度メモリリソースを使うのが正常なのかよくわかっていない
  • GKEではlens1 / lens2 / lens2-aがProductionでそれぞれ専用ノード化していて、柔軟性がない
    • ノードプールが分かれている(=専用ノード化している)
    • lens2 / lens2-aでは上記の通りCPUリソースが6000m要求している。1ノードに1Pod動く構成になっている
    • K8sの長所である柔軟性が生かされていない
    • そもそもCPUリソースが6000m必要なのかがわからない

こうしてみると以下の2点に問題が集約されます。

  • Podのリソース設定が適正かがわかっていない
  • ノードのリソース設定がアプリケーションと合っているかわかっていない

これらの問題の解決に取り組みました。

実施したこと

Podのリソース使用量を計測し、調整をする

まずはlens / lens2 / lens2-a すべてに垂直オートスケール(VPA)を導入しました。VPAはPodのCPU / メモリリソースのリクエスト量を自動でスケールアップ / スケールダウンする機能ですが、スケールアップ / スケールダウンの自動化まではさせず、適正なリソース量をサジェストしてもらう目的で使いました。

なぜVPAで自動化しないかというと、水平オートスケール(HPA)を利用しているからです。HPAはリソース使用量に応じて自動でPod数を増減(スケールイン/スケールアウト)してくれる機能ですが、VPAでスケールアップ/スケールダウンされると何の値を評価してスケールイン/スケールアウトすればよいかがわからなくなってしまいます。

さてVPAによって、適正なリソース量が把握できたため、以下のようにリソース使用量を調整しました。(lens2-aはlens2と同様の調整になったため省きます)

Request

Pod 変更前-CPU 変更前-Memory 変更後-CPU 変更後-Memory
lens1-Production 1000m 2Gi 1150m 6Gi
lens2-Production 6000m 設定なし 600m 6Gi
lens2-Staging/Sandbox 設定なし 設定なし 600m 6Gi

Limit

Pod 変更前-CPU 変更前-Memory 変更後-CPU 変更後-Memory
lens1-Production 3500m 12Gi 1500m 10Gi
lens2-Production 設定なし 設定なし 1000m 8Gi
lens2-Staging/Sandbox 設定なし 設定なし 1000m 8Gi

これにより、以下の効果がありました。

  • lens1のメモリリクエストが適正化されたことにより、OOMでEvictedになってPodが再作成されることがなくなった
  • lens2でSandbox / Staging / Productionで同じリソース量になったので、認知負荷が減り、性能差がなくなった

NKE(lens1)のノードをスケールアップする

NKEでは8vCPU / 16GBメモリのノードを50台用意し、lens1を乗せて運用していました。 前述したリソース調整により1Podにつきメモリを6Giリクエストするため、1ノードにつき2Podしか載らない状況であることがわかりました。 そのかわりCPUは8vCPUなのに2300mのリクエストに留まっており、ノードのリソースを使いきれていません。

そこで8vCPU / 16GB -> 8vCPU / 50GB のノードにメモリのみスケールアップさせ、台数を50台→30台にしました。

これはスケールアップと台数削減を同時に行うことでコストは変わらないのですが搭載できるPod数が100Pod->180Podに1.8倍になりました。

GKEでlens1 / lens2 / lens2-aを同じノードで動くようにする

さて今度はGKE側の改善です。実施したのは以下の2つです。

  • 今までlens2 / lens2-aが専用のノードプールで、1Podにつき1ノードで運用されていたものを、同じノードで動くように統一する
  • ノードのリソースをNKEのものと同じにする

GKE改善

これによって

  • リソースの無駄がへり、コストが削減されました。
  • NKEと同じ認知負荷を減らし、わかりやすい構成になりました。
  • Datadogを搭載しているのですがノード数で課金されていたので、ノード数が減ったことでコスト削減しました。

NKEのノードを大幅に増やし、lens1をほぼNKEのみでの構成に変更

私が担当に入った頃はlensのトラフィックはRoute53の重み付きラウンドロビンでNKE:GKEが100:30で運用されていました。

NKEはまだノードを増やしても大丈夫ということがわかったので、30台あったノードを50台までスケールアウトし、NKE:GKEを99:1にしました。緊急時のみGKEを使うようにし、普段はほぼNKEを使う運用に変えました。これによりGKEのノード数が減り、コストの大幅な削減になりました。また、Datadogのコストも削減されました。

トラフィックが増えるとRoute53でGKE側のweightを増やす仕組みを作った

表題通り、トラフィックが増え、NKE側で受けきれなくなった場合はRoute53の重みを自動で調整する仕組みを作りました。詳しくはペパボテックポータルにアクセス数に連動してDNSの重み付けを自動制御する仕組みをAWSで作った話という記事を書いたのでご覧ください。

そのほか

  • Kubernetesのバージョンアップメンテナンス。1.21〜1.24まで段階的に逐次バージョンアップしていきました。Ingress-nginxといった周辺コンポーネントも同時に上げていきました。
  • NKEとGKEでHPAでの最大Pod数の上限に差をつけて自動デプロイされる仕組みにしました。GitHub ActionsとKustomizeを組み合わせました。

こんなところかなぁ。だいたいここまでの改善は1月〜8月までに固めてやったものです。細かいものはまだまだありそうですが長いブログになったのでこのへんで。

来年は何するの

本当は今年完遂したかったlens2をNKEで動かす施策は来年ぜひ完了させたい…!

すずめの戸締まりを見た

金曜日の退社後、池袋のグランドシネマサンシャインへ行って見てきた。

見終わった後はぐったり……ジェットコースターのような映画だ。

どうせ見るなら…とIMAXレーザーGTという日本最高峰の鑑賞環境でみて、それはそれは迫力があったが、あまりの映像のパワーにこれはむしろ普通の映画館で見ないと心がすり減るよなぁ(そのくらいすごい映画だったということだ)

世界中で公開されるみたいだけれど、日本人がいちばん没入できるように作られている映画だと思う。そういった意味で、良質なアニメーションを楽しむことができる日本に生まれて良かったなとも思った。