もうずっといなかぐらし

かたいなかのブログ

ArgoCD Image Updaterの新機能でイメージ更新用のPRの作成を自動化する

こんにちは、かたいなかです。

Kubernetes内のリソースを管理する際、Argo CDでのGitOpsは優れたGUIを備えていることなどから魅力的です。最近ではArgo CD Image Updaterというコンポーネントもあるため、Kubernetesでデプロイしたアプリケーションのイメージの更新まで自動で行えるようになっています。

今回はそんなArgo CD Image Updaterのv0.12.0から入った機能で、PRによるアプリケーションのイメージの更新が簡単に自動化できるようになっていたため、実際に動かして検証していきます。

目次

Argo CD, Argo CD Image Updaterとは

Argo CDは、GitOpsでKubernetes内の設定を管理するためのツールです。使いやすいGUIを備えていることも特徴の一つです。

Argo CD Image Updaterは、ArgoCDで管理されているアプリケーションのDockerイメージのリポジトリを監視し、新しいイメージを検出した際にイメージの更新を行うことができます。

この2つを組み合わせることでGitOpsでKubernetes内のリソースを管理しながらイメージの更新を自動化できます。

個人的に使いづらかった点

Argo CD Image Updaterによる変更はデフォルトではArgoCDのApplicationで指定しているリポジトリおよびブランチに書き込まれます。

Image Updaterによるコミット後のイメージの変更の同期は自動にできる(spec.syncPolicy.automated)のですが、その場合デプロイのタイミングの微妙な制御が行いづらくなります。今関わっている案件ではリリースのタイミングをスクラムイベント等に合わせて調整したいため、特に本番への自動デプロイは採用しづらいという問題がありました。

その場合、手動でSyncするという案が考えられるのですが、ArgoCDの権限上、手動Syncを行えるユーザは任意のバージョンへのロールバックも行えるため、本番環境を悪意を持って壊すこともできてしまう状態になってしまいます。そのため、業務委託の方が多い今の環境では採用しづらいです。

また、イメージの変更がリモートブランチへの直接コミットにより行われるため、ロールバックを行う際のRevert作業がGitHub上で完結しないという問題もありました。

新機能

そんな中、ArgoCD Image Updaterのv0.12.0で、ソースブランチに直接コミットさせるのではなく、新しいブランチにイメージの変更をコミットさせる機能が導入されました

この機能でArgoCD Image Updaterでイメージの変更のブランチの作成までを行い、GitHub Actionsでブランチ作成を契機にPRの作成を行うことで、イメージの更新のためのPRの作成が自動化できるようになります。

これにより、新しいイメージのデプロイをPRのマージでいつでも実行できるようにしつつ、ロールバック作業もPRをRevertするだけで行えるようになります。

実際にやってみた

実際にArgoCD Image UpdaterをGitHub Actionsと組み合わせてPRの作成を自動化していきます。

以下の図のようなフローを組んでいきます。

f:id:katainaka0503:20220224010407p:plain

検証した環境

  • Mac OS 12.2
  • Minikube 1.25.1
  • docker desktop 4.4.2
  • Argo CD 2.2.5
  • Argo CD Image Updater 0.12.0

Argo CDのインストール

まずは、こちらのドキュメントに従い、Argo CDおよびArgo CD Image Updaterをインストールします

https://argo-cd.readthedocs.io/en/stable/getting_started https://argocd-image-updater.readthedocs.io/en/latest/install/start/

また、GitHubに接続するためのクレデンシャルの設定もしておきます

argocd repo add https://github.com/${ORGANIZATION_NAME}/${REPOSITORY_NAME} --username ${USERNAME} --password ${PASSWORD} --port-forward --port-forward-namespace argocd

必要に応じてここでDockerリポジトリへの接続の設定をしておくと良いでしょう。今回はパブリックリポジトリのイメージを使用するのでここでは設定しません。

書き込みブランチを指定する機能を試す

ここまででArgoCD Image Updaterの準備ができたので以下のようなApplicationを作成して書き込みブランチを指定する機能を試していきます。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: test-service
  namespace: argocd
  annotation:
    argocd-image-updater.argoproj.io/image-list: some/image[:<version_constraint>]
    argocd-image-updater.argoproj.io/write-back-method: git
    argocd-image-updater.argoproj.io/git-branch: :image-updater{{range .Images}}-{{.Name}}-{{.NewTag}}{{end}}
spec:
  destination:
    namespace: test-service
    server: https://kubernetes.default.svc
  source:
    repoURL: https://github.com/katainaka0503/argocd-image-update-test.git
    targetRevision: main
    path: test-service
  syncPolicy:
    automated:
      prune: true

ポイントはargocd-image-updater.argoproj.io/git-branchアノテーションの値に :が含まれていることです。 値の:で区切られた左側は新しいブランチのチェックアウト元のブランチになります。そして、右側は新しいブランチの名前のテンプレートを指定します。

このApplicationを作成した状態で、新しいDockerイメージをpushすると、以下の画像のようにGitHub上にイメージ更新のコミットを含んだ新しいブランチが作成されます。

f:id:katainaka0503:20220224004502p:plain

GitHub Actionsと組み合わせてみる

ArgoCDによってイメージ更新用の新しいブランチの作成まで行えるようになったので、もう少し工夫してGitHub ActionsでPRの作成まで自動化します。

以下のようなファイルでGitHub Actionsのワークフローを作成します。

name: create-pr-for-image-updater

on:
  push:
    branches:
      - 'image-updater-**'

jobs:
  create-pr:
    runs-on: ubuntu-latest
    steps:
      - name: Create Pull Request
        uses: actions/github-script@v6
        with:
          script: |
            const { repo, owner } = context.repo;
            const result = await github.rest.pulls.create({
              title: '[Image Updater] イメージの更新',
              owner,
              repo,
              head: '${{ github.ref_name }}',
              base: 'main',
              body: 'Dockerイメージを更新します'
            });

この設定がmainブランチにある状態で再度ArgoCD Image Updaterにイメージ更新ブランチを作成させると、PRが自動で作成されます。

f:id:katainaka0503:20220224004630p:plain

実運用ではPR作成処理実行時に古いブランチを削除したりするとよいでしょう。

また、複数の環境用に複数のブランチを使うフローになっている場合はブランチ名等にPRの対象のブランチ名を含ませるようにすると柔軟に対応できそうです。

さらには自動マージ等も併用することでステージング環境へのデプロイはレビュー不要にすることもできます。

まとめ

ArgoCD Image Updaterの新機能を使うことでイメージの更新のPRの作成までを自動化することができました。イメージの更新がPRベースなため、スクラムイベント等のデプロイを行いたいタイミングに合わせてデプロイできます。また、GitHubのApprove関連の設定により誰がデプロイを行えるかを設定できるようになりました。さらには、ロールバックが必要になった際のRevertもGitHub上の操作で完結します。

今回の機能はかゆいところに手が届く機能で個人的にもとても嬉しい機能です。この機能がリリースされたらArgoCDを使っている環境では試してみる価値があるのではないでしょうか?

余談: Fluxでは

Argo CD + Argo CD Image Updaterの機能を提供する他のツールとしてFluxというツールがあります。FluxではPRの作成までGitHub Actionsに頼らず行うことができるようです。

https://fluxcd.io/docs/use-cases/gh-actions-auto-pr/

今回のリポジトリ

参考

編集履歴

  • ArgoCD Image Updater 0.12.0リリースに伴い、リリース前の機能を使うためのセットアップ手順を削除しました。

マイクロサービスの運用をスケールさせるにはどうしたらいいんだろう

こちらは、Livesense Adventcalendar 2021 19日目の記事です

こんにちは、かたいなかです。最近、マイクロサービスで構築されたシステムをインフラの運用を破綻させずにスケールさせていくために必要なことをずっと考えています。

自分のための備忘録も兼ねてマイクロサービスを破綻させずにスケールさせていくためにSREの観点で使う技術に関係なく最低限必要だと考えていることをポエム記事としてまとめてみます。

問題

システムが巨大になってくると、マイクロサービスの数や関わるエンジニアの数に比例してインフラの運用工数が増えていきます。インフラの運用工数が増え続けると、いずれサービスレベルの低下やインフラ作業のリードタイムの増大に繋がり、システムの品質や開発効率に悪影響を及ぼします。

この問題に対処するために、運用工数の増大に対してスクリプトによる自動化等で工数を減らすというのがよくある打ち手ですが、マイクロサービスごとに構成が異なったりするとに簡単に自動化できなかったりします。 そんなふうにして自動化が遅れていると、最終的に自動化によって運用工数が減るペースよりもシステムの成長に伴って運用工数が増えるペースのほうが大きくなり、自動化による解決が難しくなります。 そうすると、自動化ではなく抜本的にシステムを作り変え解決を図るのですが、これもシステムの大きさが大きくなればなるほど移行完了までかかる時間がかかることになり、ビジネスに対する影響が大きくなってしまいます。

個人的な経験でも、このような状態に陥りながらも運用エンジニアのがんばり等でなんとかシステムを動かし続けているシステムは世の中に沢山存在するのではないかと思っています。

そのような状態に陥ってしまうのをできる限り避けるにはどのようにしたら良いでしょうか?

こうしたらいいんじゃないかと思っていること

上記のような問題はよくある問題だと思いますが、個人的にこのような問題に対処するにはSREの観点では以下のような対策が必要ではないかと思っています。

  • トイルの量の計測

  • システムの標準化

  • 作業の自動化・運用のセルフサービス化

それぞれについて具体的に書いていきます。

トイルの量の計測

なによりもまず運用コストがどの程度なのか計測する必要があると思っています。計測することにより、トイルが増えすぎて改善に手が回らない手遅れの状態になる前に改善に向けた施策を検討することができるようになります。

計測には例えばtogglのようなツールにより計測したトイルにかかっている時間やトイルに分類されるチケット数を用いることが考えられます。また、開発者自身が行っている作業の中でもトイルとなっている作業があるかもしれないため、開発者サーベイ等の結果も併用すると良いでしょう。

測定した値をインフラチーム内で定期的に共有し、どのような種類のトイルがあるか等を定期的に分析すると良いでしょう。トイルを減らすにはどうしたらよいか優先的に自動化するべきタスクはどれかなどSRE内部で定期的に議論する場を設定します。トイルの量が増えすぎた場合には運用を破綻させないために行う作業の量を制限する必要があるかもしれません。

システムの標準化

マイクロサービスごとに構成に大きな差分があるとシステム全体の認知コストが大きく増大します。認知コストが大きいシステムは自動化やセルフサービス化等の取り組みを行うのが難しくなります。 そのため、なるだけ自然に標準的な構成に従えるようなしくみを準備する必要があります。

標準的な構成等について書かれたドキュメント等はどのシステムにもあると思いますが、個人的にはそれだけでは不十分だと考えています。マイクロサービスの場合、特に複数のチームのエンジニアが関わることになりドキュメントへの準拠を担保できなくなるためです。

たとえば、Terraformを使用して運用している場合にはlint, tfsec, モジュール機能等を使用し、可能な限り自然に標準に従えるようにし、違反した場合には自動で検出できるような仕組みを考えていく必要があります。

このとき、すべてのシステムを標準に合わせようとすると無理が出てくるため、8~9割程度を標準でカバーし残りの1割を個別運用するようにすると現実的かと思います。

作業の自動化・運用のセルフサービス化

システムの成長に伴って増えていくトイルに対して、まず浮かぶ対策はスクリプト化等による自動化です。また、システムの構成変更やクラウドサービスの新機能の導入等により作業自体を不要にしてしまうこともできるかもしれません。

このような自動化を行っていく中で、開発に必要な作業は可能な限りインフラに依存せずセルフサービスで行えるようにしていく必要があります。開発者自身がオーナーシップを持って運用していくことは高品質なシステムを構築する上で不可欠です。また、インフラエンジニアの数はアプリケーション開発者の数に比べて限られているため開発者側で行える作業はセルフサービスで行ってもらうことが合理的です。

このようなセルフサービス化の対象には、新バージョンのデプロイや環境変数の修正、エラー調査に必要なログの取得や新しいマイクロサービスのデプロイやマイクロサービスごとのダッシュボードの作成、まで様々な作業が含まれます。

このようなセルフサービス化に伴いSLOや権限の管理等でガードレールを設けていく作業も同時に行うことで品質やセキュリティの担保も行っていくとよいでしょう。

個人的にはセルフサービス化等により先回りして開発者の作業が快適に行える環境を作っていくことこそSREの仕事なのではないかと思っています。

雑感

個人的にはトイルの増大に早めに対処していくことこそがSREの本質だと思っています。

SRE本ではトイルの量を工数の50%に抑える必要性が述べられています。最初読んだときにはやりすぎではないかと思っていたのですが、最近様々なシステムに関わることが増えるにつれて運用が個人の頑張りに依存せず持続可能な状態を維持していくためには現実的に必要な対処ではないかと思っています。

参考資料

TOEIC L&R 980 取得記

2020年9月13日に行われた第252回 TOEIC® Listening & Reading 公開テストにて 980 点を取得しました。他の方の参考になればと思い、使用した参考書や行った勉強法を共有します。

f:id:katainaka0503:20201006170913p:plain

TL;DR

  • 目標点をとる上で自分に足りない能力はなにか」「しっかり負荷がかかる学習が行えているか」を意識しながら学習を行うことが一番大事
  • DUO3.0Core1900で基礎的な英単語をみっちり鍛えることでTOEIC対策に耐える基礎力をつけましょう。時間をかければ新しい単語や表現の意味を思い出せる状態でやめないこと。英会話レッスン等と組み合わせて覚えた単語を自在に使える状態まで持っていくことが重要。
  • 速読速聴・英単語 TOEIC TEST GLOBAL 900TOEICの問題に似た文章で英単語を覚えていく形式であり、TOEIC風の文章に慣れながら単語を覚えていけるため強くおすすめできる。
  • Part2とPart5は対策が効きやすいパートであり、対策のコスパが非常に高い。
  • 公式模試を解き終わったら本番より難しめの模試を解くのがおすすめ。私は精選模試2のリスニングリーディングを使用

具体的な学習内容以前の話

使った参考書やWebサービス等の解説の前に先に書いておきたいのですが、「この記事に書いてあったからこの本をやる」「この記事に書いてあったから参考書をN回繰り返す」というスタンスで勉強に取り組んでいるうちは個人的にはどこかでTOEICの点数が頭打ちになってしまう可能性が高いんじゃないかと思っています。

あくまでこの記事で書かれていることは参考程度にとどめ、「目標を達するために自分に今足りない能力はなにか」「どのようなトレーニングを行うと目標に近づけるか」「今の学習方法はしっかり負荷がかかる方法になっているか(特にシャドウイング)」を常に自問自答し、学習法を決めていくことをおすすめします。

具体的な学習内容

英語学習スタート前の状態

  • 小中学校では英語が比較的得意だった
  • 高校時代にDUO3.0を使ったことがある
  • 大学受験をしなかったので大学受験のための英語学習は未経験
  • TOEIC受験経験なし

TOEIC用の勉強開始前(2019.06 ~ 2020.5)

この頃は特に資格試験を受けるつもりはなく、「英語のドキュメントや海外カンファレンスの動画を理解できるのってかっこいいな」という単純な動機で学習をはじめました。

当初の動機は仕事で活かすことでしたが次第にレアジョブのレッスン内でいかにうまく話せるかということに主眼が移っていきました。

今から考えるとすぐにTOEIC対策に取り掛からず、DUO3.0やCore1900および英会話レッスンで基礎的な英語力をじっくり高めたことが、後のTOEICでの高得点につながったのではないかと思います。

英会話レッスン(レアジョブ)

この時期に英語学習の中心になっていたのはレアジョブというサービスでの1日25分の英会話レッスンでした。

レアジョブのレッスンでは、最初は日常会話用の教材を使っていたのですが、レッスンに慣れた後はDaily News Articleという教材を使っていました。この教材は毎日配信される記事を題材にディスカッションするというもので、記事の内容が面白く毎回レアジョブの先生と議論するのが楽しかったのを覚えています。

ただレッスンを惰性で受けるだけでは受動的な学習になってしまい効果が薄いと感じたため、瞬間英作文の練習やDUO3.0/Core1900の音読・シャドウイングを行った直後にレッスンを受けるようにしていました。これにより、覚えたばかりの能動的に使うことができない語彙を自然と背伸びして使うことになったため、結果として能動語彙が増え、能動語彙が増えると英会話のレッスンが楽しくなるという良いループが生まれていました。

瞬間英作文

英会話のレッスンを受け始めてすぐに、言いたいことを文章の形で伝えることができないという壁にぶつかったため瞬間英作文の練習を行いました。

中学校で習うような英文を文字通り瞬間的に英作文する学習法です。

上記2冊の参考書でしばらくトレーニングを行うことで、簡単な英文であれば一瞬で口から出せるようになりました。

ある段階で瞬間英作文は卒業して単語帳のシャドウイングに練習の中心を移したのですが、TOEIC用の勉強を始めた後も特定の文法が苦手(使役動詞等)だと感じたら瞬間英作文風に作文して練習するなど、文法を能動的に使える状態に持っていく方法としては学習期間を通して活用していました。

単語帳

よく使われる表現を覚え英会話で役立てるためにDUO3.0とCore1900という2つの単語帳を使用しました。この頃はTOEICの受験は考えていませんでした。

DUO3.0

DUO3.0は1600個の重要単語と熟語1000個を560本の基本例文を通して覚えていく、いわゆる短文型の単語帳です。Natto smells awful but tastes terrific. のような短文の中に覚える必要がある単語が散りばめられており、例文を暗記するだけで重要な単語を効率的に覚えられます。短文自体も退屈にならないように工夫がされており、ドジなボブの話などクスッと笑ってしまうような短文も多いです。

別売りで基礎用と復習用の2種類のCDが発売されているのですが、CDのあるなしで学習効率が段違いなのでかならずCDを入手し耳も使って覚えていくと良いと思います。基本的には復習用のみ買えばシャドウイング等の練習には十分です。

レアジョブのレッスン前の準備体操として毎日1冊の1/4程度をシャドウイングし、土日など時間がある時に音読をするという感じで1ヶ月程度使用していました。高校時代に学習教材として使っていて内容を半分程度覚えていた状態だったため、完全に初見の方はもう少し長めにやっても良いかもしれません。

速読速聴・英単語 Core1900

速読速聴・英単語は単語帳の中で個人的に最もおすすめできるシリーズです。

このシリーズはいわゆる"文脈型"単語帳であり覚える必要がある単語が散りばめられた 120 語程度の文章を使って単語を学習していきます。長文を使って学習していくので、シャドウイングや音読の教材として使うことで新しい単語を覚えることにとどまらず、リスニングや長文読解等すべての英語力をバランス良く伸ばせる教材だと感じています。

Core1900はPart1とPart2に分かれていて、Part1は120語程度のニュース記事とその中に登場した見出し語がまとまっているいわゆる文脈型単語帳の形式、Part2はDUOのような短文型単語帳形式です。特にPart1の文章には「光害の問題」など読み物としても普通に面白い記事が多くおすすめできます。

新しい記事に取り組む際には以下のようにしていました。

  • 英文を一度も読んでいない状態で音声を3回程度聞き、耳だけで英文を理解する練習をする
  • 英文を読んで聞き取れなかった部分・新しい単語を確認
  • 3回音読
  • シャドウイングを数回(1.2 倍速まで次第にスピードアップ)

この本で出た表現は、2ヶ月程度この本音読・シャドウイングをした直後に英会話レッスンを受けることを繰り返すことで、一部の難しい表現を除いて英会話の中で自然に使える状態を目指してトレーニングを行っていました。これにより基礎的な単語の運用力がかなり鍛えられました。

TOEIC 勉強開始(2020.05 ~ 2020.07)

レアジョブのレッスン内で自信がついたので、力試しを兼ねてTOEICを受けてみようと学習を開始しました。

この時期に最も重視したのは以下です。

  • TOEIC用単語の習得
  • TOEICのリーディングパートより少し難しい記事の多読によるリーディングスピードの向上
  • Part2対策(対策が行いやすいため早めにスタート)
  • Part5対策(対策が行いやすいため早めにスタート)

TOEIC用単語

速読速聴・英単語 TOEIC TEST GLOBAL 900

TOEIC用単語の習得には主に速読速聴・英単語 TOEIC TEST GLOBAL 900を用いました。

TOEICのPart3,4,6,7のような文章を使って単語を覚えていく文脈型単語帳です。使用法はCore1900のときとほぼ同様で、音読・シャドウイングを繰り返し、直後に英会話レッスンを受けることでなるべく新しく学んだ語彙を能動的に使える状態を目指して練習しました。

個人的にはこの本で飽きるまでTOEICっぽい文章を音読・シャドウイングしたことでリスニング力とリーディング速度の両方に効いたと感じており、TOEIC対策単語帳として強くおすすめできます。

金のフレーズ

GLOBAL 900の補助としてTOEIC L & R TEST 出る単特急 金のフレーズ(通称金フレ)を使用しました。こちらは語彙に抜けがないことやTOEIC特有の語義等をチェックするために使いました。ただ、個人的にフレーズで覚えていくスタイルが肌に合わなかったので2周程度でやめてしまいました。

Japan Timesの記事を読む

TOEIC のリーディングパートよりやや難しい文章でリーディング速度向上トレーニングをするため、Japan Timesの記事を空き時間で読むようにしていました。

新しい記事を読む際には以下のように読んでいました

  1. 時間を計測しながら読む
    • 意味がわからなくなったり文章構造が掴めなくなってもなるべく戻り読みをしない
    • 英単語の意味は調べない
    • どのあたりに何が書いてあったかなんとなく理解できる程度の理解度で読めるのが理想(Part7のような問題を解くために必要な情報が文章のどのあたりに書かれていたかぼんやりわかるぐらい)
  2. 読み終わったら単語数を時間で割り、おおよその WPM を確認する(私の場合はWPM160ぐらいで理解度を最大にするため、WPMを元に読み方を調整していました)
  3. 同じ記事を時間をかけて文章構造を正確に把握することを意識しながらもう一度読む。英単語は記事の意味を理解するのに支障がある場合だけ調べる

Part5 対策

Part5 の対策としては、まず Evergreen(旧 Forest)を問題集を合わせて一通り通読しました。

後から考えるとこの本は重箱の隅をつつくような文法事項の解説も多く、通読するよりも辞書的に使ったほうが効率的だったかもしれません。

その後、Evergreen(旧 Forest)を 7 割程度頭に入れた状態で以下の問題集を使って対策を行いました。

多いようですが、Part5は明らかに対策が効きやすいパートであると感じていたため、対策を重点的に行いました。

Part2 対策

Part2 の対策としては、以下の問題集を用いて問題演習および聞けなかった問題の復習を繰り返しました。

Part2 の勉強では必然的にたくさんの短い文を聞くことになるため、自分がリスニングする上で苦手な音や表現を自覚することができました。

以下の参考書もレビューが良いので Part2 が苦手な方は追加で取り組んでも良いかもしれません

受験直前期(2020.07 ~ 2020.09)

直前期には以下の模試をひたすら解いていました。

精選模試は本番より難しいという評判なのですが、難しい問題を高地トレーニングとして解き、また一度解いた難しい問題を使ってリスニングの練習等を行ったことで、本番ではかなり余裕を持って解答できました。

模試を解くときには以下のようにしていました。

  • 新しい模試を解く・採点
  • 間違えた問題・わからなかった単語の確認
  • Part2 で聞けなかった問題をリストアップ・数回聴く
  • Part3,4 を1.3倍速でシャドウイング(何日かに分けて 3~5 周)
  • 1.3倍速にしてリスニングパートを再度解く(リーディングパートは解かなかった)

1.3倍速でリスニングパートを再度解いたのは、先読み等の解答プロセスに集中した練習を行うためでした。模試を解く中で先読みが苦手だと感じていましたが、一度解いた問題を1.3倍速再生しながら先読みの練習をすることで、普通の速度で問題を解く時に余裕を持って先読みが行えるようになりました。

模試の素点

個人的に模試を解いている際に、「模試の素点が何点ぐらいなら本番で目標点に達するか」がわからなくてとても不安だったため、せめてもの目安になればと私が模試を解いた際の素点を晒しておきます。

L R 備考
公式模試 6 T1 91 92
公式模試 6 T2 94 97
精選模試 2 T1 77 93 精選模試の洗礼で大きく L が下がる
精選模試 2 T2 93 91
精選模試 2 T3 88 93
精選模試 2 T4 89 94
精選模試 2 T5 89 98
公式模試 5 T1 98 94
公式模試 5 T2 98 97
公式模試 4 T1 97 97
公式模試 4 T2 99 94

精選模試には本番での予想得点の換算表が載っているのですが、私個人の話で言うと本番では換算表の点数より+20~30点ぐらいの点数が出たのでこちらも参考として。

まとめ

ここに紹介したような対策でTOEICの本番では980点を獲得することができました。誰かのお役に立てば幸いです。

Kubernetesにシステム系コンポーネントを入れるときは監視も一緒に設定しよう

Kubernetesの利点の一つとしてOSSのコミュニティで開発された独自のシステム系コンポーネントを導入することで、自分たちのニーズに合わせた運用性の向上が望めるという点があります。

転職会議でもALB Ingress ControllerやSealedSecrets等のシステム系コンポーネントを導入しています。

このようなコンポーネントは多くの場合ドキュメントに従って手を動かすだけで(場合によってはYAMLクラスタに適用するだけで)導入が完了するほど容易に導入できるのですが、ここで見落としがちなのが導入後の運用です。運用を滞りなくおこなうためには導入されたコンポーネント当然監視されている必要があります。

転職会議ではDatadogのAutodiscovery機能を使用してPrometheusのエンドポイントからDatadogにメトリクスを取得して監視を行っているのでその設定例等を紹介します。

何を監視したいか

転職会議ではEKS上に以下のようなコンポーネントを導入しています。

  • Flux
  • Sealed Secrets
  • Velero
  • Reloader
  • AWS ALB Ingress Controller
  • Cluster Autoscaler
  • ExternalDNS

このとき例えば以下のような問題が起きたらすぐにSlack等に通知させたいです。

転職会議ではSREだけではなくアプリケーションエンジニアも自分が担当するサービスのKubernetesYAMLを日々編集しています。そのためSREとしては、上のような問題が起きた場合には本番だけでなくステージングや開発環境でもなるべく早く開発者に知らせてあげることで、安心してKubernetesYAMLを修正しやすい環境を作りたいと考えています。

そのため、独自システムコンポーネントの監視によって問題発生時にはすぐに通知されることは重要です。

どうやって監視するか

実は多くの場合独自のシステム系コンポーネントにはPrometheusのエンドポイントが生えているため、Prometheus形式のメトリクスに対応したツールを使うことでこれらのメトリクスを監視に用いることができます。

例えばALB Ingress Controllerであれば 10254 番ポートの /metrics というパスにアクセスすることで以下のようなPrometheus形式のメトリクスが返ってきます。

...略....
aws_alb_ingress_controller_aws_api_errors{operation="DescribeListenerCertificates",service="elasticloadbalancing"} 2
aws_alb_ingress_controller_aws_api_errors{operation="DescribeLoadBalancers",service="elasticloadbalancing"} 9
aws_alb_ingress_controller_aws_api_errors{operation="DescribeTargetGroups",service="elasticloadbalancing"} 15
aws_alb_ingress_controller_aws_api_errors{operation="GetToken",service="ec2metadata"} 1
aws_alb_ingress_controller_aws_api_errors{operation="ModifyRule",service="elasticloadbalancing"} 38
aws_alb_ingress_controller_aws_api_errors{operation="RegisterTargets",service="elasticloadbalancing"} 7
...略....

このようなPrometheusのエンドポイントから取得したアプリケーションの動作を表すメトリクスを用いて問題発生時にアラートを飛ばすことができます。監視ツールとしてPrometheusを使用している場合はAlertmanager等を使ってアラートを飛ばすことができます。

転職会議では監視にDatadogを使用しているため一旦Datadogにメトリクスを集めて、Datadogからアラートを飛ばすようにしています。

その際に使用しているのがDatadogのAutodiscovery機能です。監視対象のPodに以下のようにアノテーションを設定するだけで、Datadog AgentがPodのアノテーションを解釈し自動的にメトリクスをDatadogに送るようになります。

# 以下はCluster AutoScalerでの設定例
# Podのアノテーション
annotations:
  # datadogでPrometheusエンドポイントからメトリクスを取得する
  # ad.datadoghq.com/<コンテナ名>から始まるアノテーションを設定
  # コンテナ名はPodのcontainers[].nameで指定している名前
  ad.datadoghq.com/cluster-autoscaler.check_names: '["openmetrics"]'
  ad.datadoghq.com/cluster-autoscaler.init_configs: '[{}]'
  ad.datadoghq.com/cluster-autoscaler.instances: '[
    {
      # Prometheusエンドポイント
      # "prometheus_url": "http://%%host%%:<ポート番号>/metrics",
      "prometheus_url": "http://%%host%%:8085/metrics",
      # Datadogのメトリクスのnamespace。
      # これによりDatadog上でのメトリクスが"<ここで設定したnamespace名>.<メトリクス名>"というような名前になる
      "namespace": "prometheus",
      # 取得するメトリクス名のフィルタ 
      # "metrics": ["<metrics名のフィルタ>"]
      # Goのランタイムのメトリクス等も取りたいなら以下
      # "metrics": ["*"]
      "metrics": ["cluster_autoscaler_*"]
    }
  ]'

あとはDatadogの機能で普通にアラートを設定するだけです。Slackに通知すると以下のような感じです。

f:id:katainaka0503:20200811164840p:plain

まとめ

運用を楽にするような独自コンポーネントを導入しやすいのはKubernetesの大きな利点です。この大きな利点を最大限に利用するためにも、新しいコンポーネントを入れる際には監視やアップデートを含めたプロダクトのライフサイクルで起こりそうな問題を考えておくようにしましょう。

参考資料

EKSのIAM Role for Service Accountsを使用するときにはSTSのリージョナルエンドポイントを使うように設定しようという話

先日、DynamoDBにアクセスするAPIをLambdaからEKS上への再実装・載せ替えを行いました。その際、IAM Role for Service Account(IRSA)を使用した一時クレデンシャル取得時のレイテンシが大きく高まる事象に遭遇しました。

原因と解決策を調査から解決に至るまでの過程とともにご紹介します。

TL;DR

IRSAを使用する際は一時クレデンシャル取得時にSTSにリージョナルエンドポイントを使用させるためAWS_STS_REGIONAL_ENDPOINTS=regionalという環境変数を設定することを検討すること。

経緯

現在転職会議ではECS or Lambdaで実装された既存のサービスを順次EKS上に載せ替えていっています。

先日、私がLambdaで実装されていたAPIをGoで再実装しEKS上に載せ替えました。このAPIはDynamoDBの中身をレスポンスとして返すという極めてシンプルなAPIです。

リプレース後、レイテンシの平均をとってみると以前のLambdaでの実装よりも速くはなっていたのですが、1時間おきにレスポンスが2s程度まで跳ねあがる現象が発生しました。

f:id:katainaka0503:20191225185947p:plain
レイテンシの最大値

Datadog APMでレイテンシ急増時のフレームグラフを見てみると、2秒かかっているうちDynamoDBへのアクセスにかかっているのはリクエスト処理時間のうち最後の方の0.2秒程度だけであることが分かりました。

f:id:katainaka0503:20191225191141p:plain

ここまでのことから、DynamoDBにアクセスする前に必要な処理でありかつ1時間おき発生する処理が原因と考えられ、その両方に当てはまるIAM Role for Service Accounts(IRSA)による一時クレデンシャルの取得処理が疑わしいと考えました。

そこで、行われている動作をもう少し詳しく調べるため、ステージング環境でtcpdumpで調べてみることにしました。具体的な手順としては以下です。

  1. kubectl execAPIサーバのコンテナに入る
  2. tcpdumpをコンテナにインストール
  3. tcpdumpでダンプを取得
  4. ダンプファイルをkubectl cpでローカルにコピー
  5. Wiresharkでダンプファイルを見る

ダンプファイルを見てみると、STSとの通信ログからSTSへのアクセスに1.5s程度時間がかかっていることがわかりました。どうやら、Kubernetesの発行したトークンで一時クレデンシャルを取得するSTSAssumeRoleWebIdentityで時間がかかっているようです。 f:id:katainaka0503:20200106180125p:plain

ダンプの中身を詳しく見てみると、DNSの通信ログからそのSTSへのアクセスにグローバルエンドポイント(sts.amazonaws.com)が使用されていることがわかります。

STSでは可能な場合はリージョナルエンドポイント(sts.<region名>.amazonaws.com)を使用することが推奨されています。そこで、AWS_STS_REGIONAL_ENDPOINTS=regionalという環境変数を設定してリージョナルエンドポイントが使用されるように設定しました。

すると、デプロイ直後と1時間毎に発生している一時クレデンシャル取得時のAPIのレイテンシが2秒から0.4秒程度まで大きく改善しました!

f:id:katainaka0503:20200106182058p:plain
レイテンシの最大値

念の為ダンプファイルをとってみると、変更後は一時クレデンシャル取得時にはちゃんとSTSのリージョナルエンドポイントと通信していました。

f:id:katainaka0503:20200106181535p:plain

まとめ

IRSAにより一時クレデンシャルを取得してAWSAPIへアクセスするときはAWS_STS_REGIONAL_ENDPOINTS=regionalという環境変数を設定することを検討してください。この環境変数は最近のバージョンのAWS SDKおよびAWS CLIであれば使用できるようです。(ほかにSTSのリージョナルエンドポイントを使用させるもっとよい方法があればこっそり教えて下さい)

問題調査をするときには必要とあれば抽象化の皮を剥がして調査する能力が大事だと強く感じる事件でした。

参考資料

株式会社リブセンスに入社して1ヶ月が経ちました

はやいもので明日でリブセンス入社1ヶ月目の業務が終わり今年の仕事納めとなります。

今回の記事では私の入社1ヶ月での業務内容や入社1ヶ月目の所感などを書いていきます。

やったこと

キャッチアップ

入社時点で配属先である転職会議のインフラ周りがかなり整っていたことや、1on1等を厚めに行ってくれたこともあり、現状の把握に大きく時間を取られることがありませんでした。

AWS Lambda(Node.js)で作られたAPIをGo(Gin) on EKSにリプレース

  • コピペされることを想定してKubernetesYAMLは基本的なベストプラクティスを意識して作成
    • limit, requestsの設定
    • preStopでsleep
    • PodDisruptionBudget
  • Webサーバも最低限必要そうな事項を意識して実装
  • IRSAを使用したことに関連するパフォーマンスバグ対応(後日記事化)

DatadogのエージェントをEKSクラスタに導入

RDSの証明書更新対応準備

  • メンテ日時の設定
  • ステージング環境でのリハーサル

HackDays(年末最終週がリリース禁止であるため、その間エンジニアが好きな技術で遊んで成果を発表する社内イベント)

その他

  • Node.js製APIサーバにDatadogのAPMのトレーサーを仕込む
  • メールのテスト用に使用していたmailhogをECSからEKS上に移行
  • DynamoDBをオンデマンド化

入社1ヶ月の所感

事業会社のSREとして働くのは初めての経験でしたが、周りの方のサポートもありスムーズに入っていくことができました。一緒に働くエンジニアの方々はみんなそれぞれレベルが高く、担当領域を超えたところにも改善を行っていく強いマインドを持っていて一緒に働いていてとても楽しい1ヶ月でした。

コミュニケーションの面でも1 on 1やエンジニアが全員集まって課題を話し合うMTGなどうまく交通整理されており、かなりやりやすさを感じました。

転職会議のインフラ面での現在の課題である全面EKS化に向け、来年はさらに気合いを入れて頑張っていきたい所存です。

EKS上でもBerglasみたいにクレデンシャルをいい感じにしたい!!!

こちらはAmazon EKS #1 Advent Calendar 2019 4日目の記事です。

こんにちは、かたいなかです。株式会社リブセンスに入社してはや一ヶ月弱が経過しました。

私が所属する転職会議事業部では、ECSからEKSへの移行を進めています。その中で現在議論が白熱しているのがクレデンシャルの管理方法についてです。検討の中で「GCPにはBerglasという便利ツールがあり、Podに渡す環境変数をよしなに指定するとSecret Managerにあるクレデンシャルをいい感じに展開してくれる」という話を同僚に聞きました。

github.com

そこで、今回はMutationWebhookの実装の素振りを兼ねてEKS上でAWS Secrets ManagerからBerglas同様にクレデンシャルを取得するしくみを作ってみましたのでご紹介します。

TL;DR

Beglas風にAWS Secrets Managerからクレデンシャルを取ってきてくれるツール(Not Production Ready)を試験的に作成。MutationWebhookは便利だが劇薬なので用法用量注意が必要。

Berglasとは

BerglasはGCPが提供しているGCP上に保存されたクレデンシャルを便利に扱うツールです。

BerglasのCLIにはberglas exec --というコマンドで実行するラッパーの機能があります。このコマンドを実行する際、berglas://<credentialへの参照>という形式の値を持つ環境変数があると、当該の環境変数を参照先から取得した実際の値に置き換えてラップされたコマンドを実行します。

$ export API_KEY="berglas://<credentialへの参照>"
#"API_KEY"の値が参照先のクレデンシャルの値で置き換わった状態で"--"の後ろに指定したコマンドを実行
$ berglas exec -- some command

MutationWebhook

前節で紹介したCLIツールをどのようにKubernetesと連携させればよいかについては、BerglasのGitHubリポジトリKubernetesのMutationWebhookのサンプル実装が置かれています。(https://github.com/GoogleCloudPlatform/berglas/tree/master/examples/kubernetes)

このサンプル実装では、Podの作成時にberglas://<credentialへの参照>環境変数が設定されている場合、initContainerでBergalsのコンテナからemptyDirのボリュームにバイナリをコピーする設定を追加します((1)の部分)。そして、commandを書き換え、もともと指定されていたコマンドをberglas exec --でラップするように設定します((2)の部分)。以下のようなYAMLが作成されます。

apiVersion: v1
kind: Pod
metadata:
  name: envserver
spec:
  # ボリュームにberglasのbinを流し込むinitContainersを追加(1)
  initContainers:
    - name: copy-berglas-bin
      image: gcr.io/berglas/berglas:latest
      imagePullPolicy: IfNotPresent
      command: ["sh", "-c", "cp /bin/berglas /berglas/bin/"]
      # berglasのバイナリ流し込み用ボリューム
      volumeMounts:
        - name: berglas-bin
          mountPath: /berglas/bin/
  containers:
    - name: server
      image: katainaka0503/server
      imagePullPolicy: Always
      # もとのPodで指定していたcommandを「"berglas exec --"でラップして実行するように修正(2)
      # command: ["/bin/server"]
      command: ["/berglas/bin/berglas"]
      args: ["exec", "--", "/bin/server"]
      env:
        # berglas://の形式の環境変数がある場合にPodを編集
        - name: API_KEY
          value: berglas://berglas-test-secrets/api-key
        - name: TLS_KEY
          value: berglas://berglas-test-secrets/tls-key?destination=tempfile
      # berglasのバイナリ流し込み用ボリューム
      volumeMounts:
        - name: berglas-bin
          mountPath: /berglas/bin/
  volumes:
    # berglasのバイナリ流し込み用ボリューム
    - name: berglas-bin
      emptyDir:
        medium: Memory

これにより以下のような利便性が提供されます。

  • 環境変数GCP上に保存されたSecretの参照を指定するかたちのため、リポジトリ上でのYAMLの暗号化等は不要
  • 実行時にemptyDirボリューム経由でラッパーのバイナリを埋め込むことで、アプリケーションのDockerfileに手を入れることなくKubernetesデプロイ時にラッパーを実行
  • Podのenv等にクレデンシャルが展開されたりもしないので機密性が高い

ただし、Podの設定のcommand, argsで指定しているコマンドをラッパースクリプトの中で実行させる仕組みであるため、command, argsがPodのYAMLの中で明示的に指定されている必要があります。(Dockerfileで指定したENTRYPOINT, CMDをそのまま使わせることができない)

AWSでもBerglas風にクレデンシャルを管理したい

さて、ここからが本題です。GCPでできることはAWS上でも同じようにやりたくなるのが自然な心理です。そこで、今回はBerglasと同様の仕組みでSecretsManagerから値を取得できるようなWebhookおよびCLIを写経を兼ねて実装してみました。

今回のコードは私が自身の学習用に作成したものです。継続的にメンテナンスするつもりは現在のところないので、そのまま使用することはおすすめしません。

動作

今回作成したものをデプロイした状態で以下のようなYAMLでPodを作成しようとすると、

apiVersion: v1
kind: Pod
metadata:
  name: server
  annotations:
     eks.amazonaws.com/role-arn: hogehoge
spec:
  containers:
    - name: server
      image: server:any-tag
      command: ["/bin/server"]
      # berglas-aws://<Secrets ManagerのARN>の形式の環境変数がある場合にPodを編集
      env:
        - name: API_KEY 
          value: berglas-aws://arn:aws:secretsmanager:<REGION>:<ACCOUNT_ID>:secret:<SECRET_ID>

以下のようなYAMLに修正されてPodが作成されます。

apiVersion: v1
kind: Pod
metadata:
  name: server
  annotations:
     eks.amazonaws.com/role-arn: hogehoge
spec:
  # ボリュームにberglas-awsのbinを流し込むinitContainersのを追加
  initContainers:
    - name: copy-berglas-aws-bin
      image: katainaka0503/berglas-aws:latest
      imagePullPolicy: IfNotPresent
      command: ["sh", "-c", "cp $(which berglas-aws) /berglas-aws/bin/"]
      # berglas-awsのバイナリ流し込み用ボリューム
      volumeMounts:
        - name: berglas-aws-bin
          mountPath: /berglas-aws/bin/
  containers:
    - name: server
      image: katainaka0503/server
      imagePullPolicy: Always
      # もとのPodで指定していたcommandを「"berglas exec --"でラップして実行するように修正(2)
      # command: ["/bin/server"]
      command: ["/berglas-aws/bin/berglas-aws"]
      args: ["exec", "--", "/bin/server"]
      env:
        # berglas-aws://<Secrets ManagerのARN>の形式の環境変数がある場合にPodを編集
        - name: API_KEY 
          value: berglas-aws://arn:aws:secretsmanager:<REGION>:<ACCOUNT_ID>:secret:<SECRET_ID>
      # berglas-awsのバイナリ流し込み用ボリューム
      volumeMounts:
        - name: berglas-aws-bin
          mountPath: /berglas-aws/bin/
  # berglas-awsのバイナリ流し込み用ボリューム
  volumes:
    - name: berglas-aws-bin
      emptyDir:
        medium: Memory

これにより、特定の形式で環境変数を指定するとAWS Secrets Managerに保存した値を取ってきて環境変数に設定した状態でコマンドを実行してくれるようになります。

CLI

CLI部分はBerglasの実装を参考に実装しました。

https://github.com/katainaka0503/berglas-aws

本家Berglasと同様に、ラップされたコマンドを実行する際に、berglas-aws://<SecretsManagerのARN>という形式の環境変数があればSecretManagerから取得した実際のシークレットの値で当該環境変数を置き換えます。

$ export API_KEY="berglas-aws://<SecretsManagerのARN>"
#"APK_KEY"の値が参照先のAWS Secrets Managerに保存された値で置き換わった状態で"--"の後ろに指定したコマンドを実行
$ berglas-aws exec -- some command

Webhookの実装

Webhookの部分も同様にBerglasのKubernetesへのデプロイのサンプルを参考にkube-webhookというフレームワークを使用して作成しました。

https://github.com/katainaka0503/berglas-aws-webhook

こちらも本家と同様の仕組みで、Pod作成時にberglas-aws://<SecretsManagerのARN>という形式の環境変数がある場合にcommandargsをいじってberglas-aws exec --でラップして実行されるように修正します。

Kubernetesへのデプロイ

最後にWebhookをKubernetesにデプロイする部分です。

https://github.com/katainaka0503/berglas-aws-webhook/tree/master/deploy

MutationWebhookを設定する際には、Webhookサーバ用の証明書とkube-apiserverがWebhookサーバが提供する証明書を検証するためのルート証明書が必要です。今回はCertManagerを使用して証明書を発行しました。それ以外は概ね通常のDeploymentとServiceを使用したAPIサーバの構成です。

まとめ

Secret管理方法の検討とMutationWebhookの学習を兼ねてBerglas風にAWS Secrets Managerからクレデンシャルを取得できるツールを作成して遊んでみました。想像の数倍容易に実装できたので意外でした。実装したツールを継続的にメンテナンスしていける組織であれば、このようなWebhookの仕組みを用意する方法がシークレットの管理方法として選択肢に入ってくるのかもしれません。

今回のコード

参考資料

補足: ECSでは

ちなみにECSでは、ここまで紹介してきたようなECSのタスク起動時にクレデンシャルの値をとってきて環境変数に設定する機能がすでに提供されています。タスク定義内のコンテナ定義のsecretsという属性にAWS Secrets Manager/AWS Systems ManagerパラメータストアのARNを指定すれば、参照先から保存された値を取ってきてくれます。

dev.classmethod.jp