BigQuery パイプ構文を試す - 直感的で読みやすい新しいSQLクエリの書き方

この記事は個人ブログと同じ内容です

www.ritolab.com


2024 年 10 月 9 日の Google Cloud Blog にて、BigQuery と Cloud Logging における「パイプ構文」導入の記事がアップされました。

Introducing pipe syntax in BigQuery and Cloud Logging | Google Cloud Blog

SQL のクエリといえば、長らく同じ記法で歴史を刻んできた古に伝わる呪文ですが、今回の「パイプ構文」は、それに一石を投じる新しい書き方になっています。まさに黒船。ペリー来航です。(といいつつ、今回紹介するパイプ構文はあくまでも GoogleSQL としての機能です。MySQLPostgreSQL などでは現状パイプ構文をサポートしていません)

そんなパイプ構文を、BigQuery で試してみたいと思います。

パイプ構文

BigQuery のパイプ構文は、SQL クエリをより直感的で読みやすくするための記法です。パイプ演算子 (|>) を使って処理を順番にチェーンすることで、クエリの各操作をステップごとに記述できるのが特徴です。 これにより、クエリの記述量の削減、可読性の向上が期待できます。

従来の SQL のようにすべての操作を 1 つのブロックでまとめるのではなく、フィルタリング、選択、集約などの操作をそれぞれの行で分けて記述します。詳細な記法は公式ドキュメントをご確認ください。

パイプ構文を体験

では、実際にデータを用意してクエリを書いてみましょう。

今回用意したのは、e-sports のゲームをプレイした履歴のデータです。

esports_play_sessions テーブル(n=30,000)

player_id esports_genre session_date match_result
31 sports_games 2024/01/01 win
133 moba 2024/01/01 lose
32 sports_games 2024/01/01 win

どのプレイヤーが、いつ、どのジャンルのゲームをプレイして、勝敗がどうだったか。を収録したデータです。

players テーブル(n=163)

id name
1 Caspian
2 Kamryn
3 Charmaine

こちらはプレイヤーを収録したテーブルです。

さて、こんなオーダーが来ました。

「sports_games と、card_games をプレイしたプレイヤーの、最終プレイ日を抽出したい」

このとき、どんなクエリを書くでしょうか。通常の SQL なら、以下のようなクエリになると思います。

SELECT
    p.id as player_id,
    p.name,
    s.esports_genre,
    MAX(s.session_date) AS session_date
FROM `sample.players` p
INNER JOIN `sample.esports_play_sessions` s ON p.id=s.player_id
WHERE s.esports_genre IN ('sports_games', 'card_games')
GROUP BY p.id, p.name, s.esports_genre
ORDER BY p.id, MAX(s.session_date)

ジャンルを絞り込み、それに対して集計。ユーザーごとの sports_games, card_games をプレイした最新の日時を抽出します。

これをパイプ構文を使用してクエリを組み立てると、以下になります。

FROM `sample.players` AS p
|> JOIN `sample.esports_play_sessions` AS s ON p.id = s.player_id
|> WHERE s.esports_genre IN ('sports_games', 'card_games')
|> AGGREGATE MAX(s.session_date) AS session_date GROUP BY p.id, p.name, s.esports_genre
|> SELECT id as player_id, name, esports_genre, session_date
|> ORDER BY player_id, session_date;
  1. [|> JOIN] players テーブルに esports_play_sessions テーブルを結合
  2. [|> WHERE] ジャンルを絞り込む
  3. [|> AGGREGATE] 集計し最新日を算出
  4. [|> SELECT] 出力するカラムを指定
  5. [|> ORDER BY] 並び替え

上から順番に読めるので、可読性は確かに向上するかもしれません。一方で、記述量はあまり変わってはいない印象を受けます。

もう少し複雑なクエリで試してみます。 各ジャンルにおける、一年間のプレイ数トップ 3 を出してみます。まずは通常の SQL です。

WITH ranked_sessions AS (
    SELECT
        esports_genre, player_id, COUNT(*) AS session_count,
        RANK() OVER (PARTITION BY esports_genre ORDER BY COUNT(*) DESC) AS session_rank
    FROM `sample.esports_play_sessions`
    WHERE session_date BETWEEN '2024-01-01' AND '2024-12-31'
    GROUP BY esports_genre, player_id
)
SELECT
    s.esports_genre, s.session_rank, p.id, p.name, s.session_count
FROM `sample.players` p
INNER JOIN ranked_sessions s ON p.id=s.player_id
WHERE s.session_rank <= 3
ORDER BY s.esports_genre, s.session_rank;

ランキングで絞りトップ 3 としたいため、一度 CTE を定義し session_rank を付与、それから絞り込んでいます。

対して、このクエリをパイプ構文で記述すると以下になります。

FROM `sample.esports_play_sessions` AS s
|> WHERE session_date BETWEEN '2024-01-01' AND '2024-12-31'
|> AGGREGATE COUNT(*) AS session_count GROUP BY esports_genre, player_id
|> WINDOW RANK() OVER (PARTITION BY esports_genre ORDER BY session_count DESC) AS session_rank
|> WHERE session_rank <= 3
|> JOIN `sample.players` AS p ON player_id=p.id
|> SELECT esports_genre, session_rank, p.id AS player_id, p.name, session_count
|> ORDER BY esports_genre, session_rank;
  1. [|> WHERE] 日付を絞り込む
  2. [|> AGGREGATE] 集計し各ジャンルのプレイ数を算出
  3. [|> WINDOW RANK()] プレイ数によってジャンルごとにユーザーをランク付け
  4. [|> WHERE] トップ 3 に絞り込み
  5. [|> JOIN] esports_play_sessions テーブルに players テーブルを結合
  6. [|> SELECT] 出力するカラムを指定
  7. [|> ORDER BY] 並び替え

抽出の順は確かに追いやすいですね。ただ、記述量は減ったか?と言われればそうでもない気がします。

最後に、もう少し複雑なクエリで試してみます。各ジャンルにおいて、勝率が最も高いユーザートップ 3 を抽出してみます。

まずは、通常の SQL です。

WITH player_stats AS ( -- 各プレイヤーの各ジャンルにおける総試合数と勝利数を算出
    SELECT
        e.player_id,
        p.name as player_name,
        e.esports_genre,
        COUNT(*) AS total_matches,
        SUM(CASE WHEN e.match_result = 'win' THEN 1 ELSE 0 END) AS wins
    FROM `sample.esports_play_sessions` e
    JOIN `sample.players` p ON e.player_id = p.id
    GROUP BY e.player_id, p.name, e.esports_genre
),
ranked_players AS ( -- 勝率を算出し、各ジャンルごとに勝率の高い順にランク付け
    SELECT
        esports_genre,
        player_id,
        player_name,
        ROUND(SAFE_DIVIDE(wins, total_matches), 2) AS win_rate,
        ROW_NUMBER() OVER (PARTITION BY esports_genre ORDER BY SAFE_DIVIDE(wins, total_matches) DESC) AS rank
    FROM player_stats
)
SELECT
    esports_genre,
    rank,
    player_id,
    player_name,
    win_rate
FROM ranked_players
WHERE rank <= 3
ORDER BY esports_genre, rank;

これをパイプ構文で記述すると以下になります。

FROM `sample.esports_play_sessions` AS s
|> JOIN `sample.players` AS p ON s.player_id = p.id
|> AGGREGATE COUNT(*) AS total_matches, SUM(CASE WHEN s.match_result = 'win' THEN 1 ELSE 0 END) AS wins GROUP BY s.player_id, p.name, s.esports_genre
|> EXTEND ROUND(SAFE_DIVIDE(wins, total_matches) * 100, 2) AS win_rate
|> WINDOW ROW_NUMBER() OVER (PARTITION BY esports_genre ORDER BY win_rate DESC) AS rank
|> RENAME name as player_name
|> WHERE rank <= 3
|> SELECT esports_genre, rank, player_id, player_name, win_rate
|> ORDER BY esports_genre, rank;
  1. [|> JOIN] esports_play_sessions テーブルに players テーブルを結合
  2. [|> AGGREGATE] 各プレイヤーの各ジャンルにおける総試合数と勝利数を算出
  3. [|> EXTEND] 勝率を算出
  4. [|> WINDOW] 勝率によってランク付け
  5. [|> RENAME] カラム名 name を player_name に変更
  6. [|> WHERE] トップ 3 に絞り込み
  7. [|> SELECT] 出力するカラムを指定
  8. [|> ORDER BY] 並び替え

追いやすさはこれまで通りポジティブですが、今回は記述量が大分減りました。CTE で切り出し、ないしはサブクエリの記述が無くなった分、SELECT 文分の記述量が主に削減されています。パイプ構文では、SELECT や集計でカラムが絞られない限りはそのまま次の行に持ち越されるため、追加したい新たなカラムだけを記述すればよい点が削減に寄与しています。

もう一点、通常の SQL では、ranked_players 定義時に win_rate と rank を算出していますが、その両方で SAFE_DIVIDE(wins, total_matches) を行っています。対してパイプ構文だと同じ計算は 1 回済んでおり、冗長さが解消されています。こういった点は地味にうれしいポイントです。

直感的で追いやすくクエリ冗長に成り難し

2024 年 10 月 16 日現在、パイプ構文のリリースステージは「プレビュー」であり、一般提供はされていません。

BigQuery でパイプ構文を使ってみたい場合は、BigQuery パイプ構文登録フォームに記入し、パイプ構文プレビューにプロジェクトを登録する必要があります。

また、今回紹介したパイプ構文は、GoogleSQL としての機能です。MySQLPostgreSQL などでは現状パイプ構文をサポートしていません。

さて、パイプ構文を使ってみましたが、読みやすさの向上、記述量の削減など、複雑なクエリほどパイプ構文の恩恵を受けやすくて良い機能でした。パイプ構文で記述したクエリも CTE として切り出せました。

「直感的で追いやすくクエリ冗長に成り難し」

BigQuery のパイプ構文、是非使ってみてください。

271_sample_data - Google スプレッドシート

(今回使用したサンプルデータはスプレッドシートで公開しています。CSV ダウンロードしてご自身の BigQuery にインポートするなど、ご自由にお使いください。)

XP入門2

この記事は個人ブログと同じ内容です


前回の振り返り

簡単に前回を振り返ると、XP (エクストリームプログラミング) は、アジャイル開発の手法の一つです。 XP において「価値」「原則」「プラクティス」は重要な概念であり、それぞれの要素が連携してソフトウェア開発を効率化します。

前回の記事はこちら

今回の内容

今回は9~16章を読み進めました。 その中で印象的だった、導出プラクティス、チーム全体、制約理論と時間の重要性についてまとめていきます。

エクストリームプログラミング / ケント・ベック

導出プラクティス

導出プラクティスは主要プラクティスを補強し、より効果的な開発を可能にするプラクティスです。 そのため主要プラクティスを実践していて、ある程度チームが XP に成熟していることが推奨されます。

本物の顧客参加

  • 自分たちのシステムによって生活やビジネスに影響を受ける人をチームの一員にすること。
  • 顧客参加のポイントは、ニーズを持つ人とそれを満たす人が直接やりとりをして、ムダな労力を減らすこと。
  • 信頼できる行動をとり、何も隠さなければ、生産性は高まる。(隠すことや取り繕うことに時間を費やす必要がないため)

チームの継続

  • 優秀なチームは継続させること。
  • 大きな組織は、ヒトをモノに抽象化する傾向がある。互換性のあるプログラミングユニットだと考えている。
  • ソフトウェアのバリューは、みんなが知っていることや行なっていることだけでなく、人間関係やみんなで一緒に成し遂げることによっても生み出される。
  • 要員計画の問題を単純化するためだけに、人間関係や信頼の大切さを無視するのは経済的ではない。

チームの縮小

  • チームの能力が高まったら、仕事量を維持しながら少しずつチームの規模を縮小すること。
  • チームを離れた人は、また別のチームを作ることができる。
  • より多くの仕事量をこなすために、チームの規模を拡大するような戦略もあるが、それではうまくいかない。他の方法を考えるべき。
  • チームメンバーの誰かの手が空くまで、開発を改善していくこと。そうすれば、規模を縮小しながらチームを継続できるはず。

コードとテスト

  • コードとテストだけを永続的な作成物として保守すること。
  • その他のドキュメントについては、コードとテストから生成すること。
  • プロジェクトの重要な履歴の維持については、社会的な仕組みに任せること。
  • 顧客は、システムの今日の挙動と、チームが開発する明日のシステムの挙動に対してお金を支払っている。
    • この2つのバリューの源泉に貢献する作成物は、それ自体にバリューがある。

利用都度課金

  • 利用都度課金システムがあれば、システムが利用されるたびにお金を請求することができる。
  • お金は究極のフィードバック。
    • お金には実体があり、これから自分で使うこともできる。
    • お金の流れをソフトウェア開発に直接接続すれば、改善を推進するための正確でタイムリーな情報が得られるはず。
  • 利用都度課金にできなくても、サブスクリプションモデルに移行することはできるかもしれない。
    • チームは自分たちの行動の状況を把握する情報源として、少なくとも定着率(サブスクライブを継続する顧客数)を見ることができる。
  • ライセンス収益のフィードバックだけを頼りにしているチームよりも、利用都度課金の情報を使っているチームの方が効果的な仕事ができるはず。

XP チーム全体

XP における「チーム全体」は、単なる開発チームではなく、プロジェクトの成功のために協力し合う、より広義のチームを指します。 さまざまな人の視点を注ぎ込み、チーム全体が一体となって取り組むことで、より良いソフトウェアを開発し、ユーザーの満足度を高めることができます。

この章では次の比喩を用いて、チーム全体の重要性が説明されていました。

  • 異なる視点を持つ人たちがロープに結ばれて氷河の上を歩いているときに、誰が先頭になるかは重要ではない。
  • 本当に重要なのは、全員がロープに結ばれているという感覚をチーム全体が共有していること。
  • 誰かが先頭になって他の人を追従させるよりも、全員で足並みをそろえて歩いたほうが、ずっと先まで進める。

また XP においてそれぞれの役割は固定化されるものではなく、チーム全体の成功のために柔軟に変化することが重要です。 チームの状況や目標に合わせて変化し、チーム全体の成功に貢献することが求められます。

プロジェクトマネージャー

  • XP チームのプロジェクトマネージャーは、チーム内のコミュニケーションを円滑にしたり、顧客、サプライヤー、その他のチーム外の組織とのコミュニケーションを調整したりする。
  • チームの歴史学者となり、チームに進捗状況を思い出させる。
  • プロジェクトの情報をまとめて、経営幹部や同僚にプレゼンするために、クリエイティブでなければいけない。
  • 正確性を保つために、プロジェクトの情報を頻繁に変更することになる。
    • したがってプロジェクトマネージャーには変更をうまく伝える能力が求められる。
  • チーム内のコミュニケーションを円滑にして、一体感や信頼関係を築くようにしなければいけない。
    • そのためには、重要な情報の管理者になるよりも、効果的なファシリテーターになるほうが、得られる力は大きい。

プロダクトマネージャー

  • XP のプロダクトマネージャーは、ストーリーを書いたり、四半期サイクルのテーマやストーリーを選択したり、週次サイクルのストーリーを選択したり、実装によって明らかになったストーリーのあいまいな部分の質問に答えたりする。
  • チームがオーバーコミットしていたら、想定していた要件と現実の違いを分析して、チームが優先順位をつけられるように支援する。
  • プロダクトマネージャーは、今実際に起きていることにストーリーやテーマを適応させる。
  • ストーリーの順番は、技術ではなくビジネスの理由で決めるべき。
  • プロダクトマネージャーは、顧客とプログラマーのコミュニケーションを促進する。
    • 顧客の最も重要な課題がチームに伝わり、きちんと対処されるようにしなければいけない。
    • チームが本物の顧客参加を実践していれば、ストーリーを選択した顧客やマーケット全体のニーズを満たせるように、システムの成長を促さなければいけない。

経営幹部

  • 経営幹部は、XP チームに勇気、自信、説明責任を提供する。
  • 共通の目標に向かって一緒に進んでいく XP チームの強みは、弱みにもなり得る。
    • チームのゴールが会社のゴールと合っていなかったらどうなるだろう?
    • 成功のプレッシャーと興奮によって、ゴールを見失ってしまったらどうなるだろう?
    • 大きなゴールの明確化と維持は、XP チームのスポンサーや監督を務める経営幹部の仕事。
  • もうひとつの仕事は、改善の監視、促進、円滑化。
    • 経営幹部はチームが作り出す優れたソフトウェアだけではなく、継続的な改善についても目を配らなければいけない。
  • 経営幹部は、XP プロジェクトのあらゆる側面について自由に説明を求めることができる。
    • 説明は筋が通ったものでなければいけない。
    • 筋が通っていなかったなら、経営幹部はチームに対して考察と明確な説明の提供を求めるべき。
  • XP チームの評価を決める人たちは、優秀なチームがどのようなものかを理解するべき。
    • XP チームは会話しながら仕事をする。
    • にぎやかな話し声は健全である証拠。
    • 静寂はリスクがたまっている音色。
    • 経営幹部が XP チームを理解し、自身の経験や視点をうまく適用するには、新しい経験則を学ぶ必要がある。

テクニカルライター

  • XP チームにおけるテクニカルライターの役割は、フィーチャーのフィードバックを早期に提供したり、ユーザーとの密接な関係を築いたりすることである。
    • フィーチャーのフィードバックを早期に提供。
      • 文章と図を使ったシステムの説明は、チームにフィードバックをもたらす要素のひとつである。
    • ユーザーとの密接な関係を築くこと。
      • ユーザーがプロダクトを学習できるように支援したり、ユーザーからのフィードバックを受け取ったり、ユーザーが混乱しないように発表資料や新しいストーリーを追加したりする。
  • XP チームは実際の利用状況からフィードバックを得るべき。
    • マニュアルサイトを掲載しているなら、利用状況を監視できる。
    • ユーザーがドキュメントを見ていなかったら、その部分を書くのはやめる。
    • そして、空いた時間をもっとうまく活用する。

ユーザー

  • XP チームのユーザーは、開発中のストーリーの記述や選択の支援をしたり、専門領域の意思決定をしたりする。
    • 構築中のシステムと類似したシステムに関する幅広い知識や経験を持っていたり、システムを実際に利用するユーザーコミュニティとの強い関係性を持っていたりすれば、そのユーザーは非常に大切な存在だ。
    • ユーザーはコミュニティの代表者であることを忘れないようにしなければいけない。

プログラマー

  • XP チームのプログラマーがやること。
    • ストーリーやタスクを見積もる。
    • ストーリーをタスクに分解する。
    • テストを書く。
    • フィーチャーを実装するコードを書く。
    • 退屈なプロセスを自動化する。
    • システムの設計を少しずつ改善したりする。
    • 技術的に密接に協力しながら一緒に働く。
      • つまりプログラマーは社交性や人間関係のスキルを身に付ける必要がある。

人事

  • チームが XP を適用し始めるとき、人事評価と雇用という2つの課題が発生する。
    • 人事評価の課題
      • XP はチームのパフォーマンスに集中しているのに、実際の人事評価や昇給は個人の目標や達成度に対して行われているから。
      • XP 適用前の評価の仕方を大きく変える必要はない。
      • 以下は、XP における重要性の高い従業員
        • リスペクトを持って行動できる。
        • 他人とうまくやれる。
        • イニシアチブをとれる。
        • 約束したものをデリバリーできる。
      • 2つの方法で解決できる。
        • このまま個人ベースの目標、評価、昇給を続ける。
        • もしくはチームベースのインセンティブや昇給に移行するか。
    • 雇用の課題

制約理論

制約理論とは、システムのボトルネック(制約)を特定し、それを改善することで、システム全体の効率を最大化する理論です。 導入するには組織全体の意識改革が求められます。個人の生産性よりも、システム全体の効率を重視し、ボトルネック解消に協力する体制作りが重要です。

  • 先ずはどの問題が開発の問題かを見極めるところから、ソフトウェア開発の改善の機会を発見すること。
  • 洗濯を例にした理論の説明
    • 洗濯機が衣類を洗濯するのに45分かかり、乾燥機が衣類を乾燥するのに90分かかり、衣類を畳むのに15分かかるとする。
    • このシステムのボトルネックは乾燥。洗濯機が2台あっても、洗濯がすべて完了した衣類が増えるわけではない。
    • 洗濯だけが終わった衣類は一時的に増えるかもしれないが、濡れたままの衣類が至るところに山積みになり、その対応をしなければいけなくなる。
    • より多くの衣類の洗濯をすべて完了させたければ、乾燥をどうにかする以外に選択肢はない。
  • システム全体のスループットを改善するには、最初に制約を見つけなければいけない。
    • 次に、その制約が最大限に稼働していることを確認する。
    • そして、制約のキャパシティーを増やすか、制約以外の負荷を下げるか、制約を完全に排除するかのいずれかの方法を探す。
  • システムの制約をどのように発見するか。
    • 仕掛品が山積みになっているところが制約。
    • 洗濯の例では、これから畳まなければいけない乾燥した衣類は山積みになっておらず、これから乾燥する濡れた衣類が山積みになっている。
    • ER 図が多くのフィーチャーを網羅しているにもかかわらず、やることが多すぎて実装から外されている場合は、実装プロセスが制約になっているかもしれない。
    • 多くのフィーチャーの実装が終わっているにもかかわらず、インテグレーションやデプロイが待機中になっている場合は、インテグレーションプロセスが制約になっているかもしれない。

設計 : 時間の重要性

ソフトウェア開発における設計は、一度で完璧なものを作り上げるのではなく、段階的に改善していくことが重要です。 柔軟性、シンプルさ、チームとの連携を意識することで、より良いソフトウェアを開発することができます。

  • インクリメンタルな設計は、機能を早期に届ける方法であり、プロジェクトの全期間にわたって毎週継続して機能を届ける方法。
  • 設計が日常の業務の一環になれば、プロジェクトがもっとスムーズに進む。
  • ソフトウェアはレバレッジゲーム
    • ひとつの優れたアイデアが何百万ドルものコストを削減したり、何百万ドルもの収益を生み出したりする。
  • 残念ながらソフトウェアの設計は、物理的な設計活動のメタファーにとらわれている。
    • たとえば、50階建てのビルを所有しており、すべての空間をすでに貸し出しているからといって、そこに別の50階を付け足すことはできない。
    • ソフトウェア開発における実践的でリスクの低い方法。
      • 犬小屋から開始して、少しずつ部品を置き換えながら、基本的な構造はそのままにして、最終的に超高層ビルにする。
  • ソフトウェアの世界でインクリメンタルな設計が重要なのは、アプリケーションをはじめて書く機会が多いから。
  • 設計には大きな影響力があり、設計のアイデアは経験によって改善される。
    • しかがって、ソフトウェア設計者が持つべき最も重要なスキルのひとつは忍耐。
    • フィードバックが十分に得られる分だけ設計を行い、そこから得られたフィードバックを使って、次回のフィードバックが十分に得られる分だけ設計を改善する。そうした技能が求められる。
  • 前もった設計は必要だが、最初の実装ができるだけで十分。
    • それ以上の設計は実装後に設計の本当の制約が明らかになってから行えばいい。
    • XP の戦略は「何も設計しない」ではなく「常に設計する」。
  • ソフトウェア設計のおもしろいところ。
    • 設計の品質は成功を約束するものではないが、設計の失敗は確実に失敗につながる。
  • 最も強力な設計方法は、「Once and Only Once (一度、ただ一度)」
    • データ、構造、ロジックなどは、システムのひとつの場所に存在するべき。
    • 重複を見つけたら設計を改善する必要がある。
    • 重複した表現をひとつにまとめる方法を思いつくまで、設計の改善に取り組んでいく。
  • ソフトウェアの設計はそれ自体では完結しない。
    • 設計とは、技術側の人間とビジネス側の人間の信頼関係を築くためのもの。
    • 要求された機能を毎週デリバリーすることが、信頼関係の構築に欠かせない。
    • チームの中でバリューを生み出す多様な関係性を維持することに比べたら、設計者の利便性は優先順位が低い。
  • XP チームはできるだけシンプルな解決策を好む。
    • 設計のシンプリシティを評価する4つの基準
      1. 対象者に適している
        • 設計がいかに見事で洗練されているかは重要ではない。その設計を使うべき人たちが理解できなければ、それはシンプルではない。
      2. 情報が伝わりやすい
        • 伝えるべきすべてのアイデアがシステムに表現されている。
        • システムの要素は、用語の単語と同じように、未来の読者に情報を伝えるものである。
      3. うまく分割されている
        • ロジックや構造の重複は、コードの理解や修正を困難にする。
      4. 最小限である
        • 上記の3つの制約を守った上で、システムの要素はできるだけ少なくする。
        • 要素が少なければ、その分だけ必要なテスト、ドキュメント、コミュニケーションが少なくなる。

まとめ

今回読んだ範囲では、より実践的な側面に焦点を当てた内容が多かったと感じました。 特に印象的だったのは「チーム全体」で、経営幹部や人事など、チーム外の人たちがどのような役割を果たすべきかも学べたので、メンバーをサポートするためにより大きな視点を得ることができました。 XP は単なる開発手法ではなく、組織全体の文化を変えるための取り組みとも言えそうです。 メンバーがチームに最善を尽くせるように、これからも学びを深めていきたいと思います。

Workload Identity 連携で GithubActions から GCP リソースをデプロイする

この記事は個人ブログと同じ内容です

www.ritolab.com


GCP 外のアプリケーションから GCP リソースを操作する場合に、サービスアカウントキーを用いずに安全にリソースへアクセスできる Workload Identity 連携を用いて、Github Actions から GCP リソースのデプロイを行います。

Workload Identity 連携:簡単で安全な Google Cloud アクセス

Workload Identity 連携は、Google Cloud外のアプリケーションが安全かつ簡単にGoogle Cloudリソースを利用できるようにする機能です。

従来、外部アプリケーションはサービスアカウントキーを使ってGoogle Cloudにアクセスしていました。しかし、このキーは、漏洩リスクやローテーションなど管理が難しく、セキュリティリスクも高いものです。

Workload Identity 連携はこの問題を解決します。キーの代わりに、外部の ID システム(例:AWS IAMなど)と Google Cloud の IAM を連携させます。これにより、以下の利点があります。

  1. セキュリティが向上します:キーの漏洩リスクがなくなります。
  2. 管理が楽になります:キーの作成や更新、削除の手間が不要になります。
  3. きめ細かな制御が可能になります:外部 ID に直接権限を付与したり、一時的に権限を貸し出したりできます。

例えば、AWSで動いているアプリケーションが Google Cloud Storage のデータを読み取りたい場合、Workload Identity 連携を使えば、AWS の IAMロールに Google Cloud の読み取り権限を付与できます。これにより、安全かつ簡単にクラウド間でのデータアクセスが実現します。

Workload Identity 連携は、こういったマルチクラウド環境や、クラウドとオンプレミス環境を跨ぐシステムで特に力を発揮します。

GithubActions から GCP リソースのデプロイ

今回は、GithubGCP を連携させて動作させてみます。

例えば、CloudFunctions の関数を Git 管理しているとして、それらのデプロイを Workload Identity 連携で実施するようなイメージです。本来ならば、デプロイヤーとして作成したサービスアカウントとして GithubActions からデプロイを実行する場合、サービスアカウントキーを用いた認証が必要ですが、それが不要になります。

まずは、GCP リソースをデプロイするサービスアカウントを作成します。Terraform で定義していきます。

resource "google_service_account" "github_action_deploy" {
  account_id   = "github-action-deploy"
  display_name = "github_action_deploy"
  project      = var.project_id
}

resource "google_project_iam_member" "github_action_deploy_roles" {
  for_each = toset([
    .
    .
    (GCPリソース操作に必要な権限)
    .
    .
  ])
  project = var.project_id
  role    = each.value
  member  = "serviceAccount:${google_service_account.github_action_deploy.email}"
}

Workload Identity 連携における pool と provider

ここから Workload Identity 連携を構築していきますが、プールとプロパイダという概念があります。

  • Workload Identity Pool (プール):
    • 外部のIDシステム(例:AWS、Azure, Github)からのアイデンティティを一つにまとめて管理する仕組みです。これにより、複数の外部システムのユーザーやアプリケーションを一元的に扱うことができます。プールは、これらの外部 ID を GoogleCloud のリソースにアクセスできるよう橋渡しする役割を果たします。
    • 一般には、外部アプリの環境(開発環境、ステージング環境、本番環境など)ごとにプールは分けるのが望ましいとされています。
  • Workload Identity Pool Provider (プロバイダー):
    • プール内に存在し、特定の外部 ID プロバイダー(例:AWS IAM、Azure AD)と GoogleCloud を接続する設定(認証方法)を管理します。プロバイダーは、外部システムの認証情報を GoogleCloud が理解できる形式に変換(属性マッピング)し、適切な権限を割り当てる手助けをします。
    • 各プールには、複数のプロバイダーを設定できます。

例えると、プール (Pool) は「空港」のようなものです。様々な航空会社(プロバイダー)からの乗客(認証要求)を受け入れます。

プロバイダー (Provider) は「特定の航空会社」のようなものです。その航空会社特有のチケット(認証情報)を持つ乗客を確認し、適切に処理します。

この構造により、複数の外部IDソースを柔軟に管理し、それぞれに対して細かな設定を行うことが可能になります。

では、プールとプロパイダを定義します。

resource "google_iam_workload_identity_pool" "github_pool" {
  provider                  = google-beta
  project                   = var.project_id
  workload_identity_pool_id = "github-pool"
  display_name              = "GitHub Pool"
}

resource "google_iam_workload_identity_pool_provider" "github_provider" {
  provider                           = google-beta
  project                            = var.project_id
  workload_identity_pool_id          = google_iam_workload_identity_pool.github_pool.workload_identity_pool_id
  workload_identity_pool_provider_id = "github-provider"
  display_name                       = "GitHub Provider"
  attribute_condition                = "assertion.repository_owner_id == 'xxxxxxxxx'"
  attribute_mapping = {
    "google.subject"       = "assertion.sub"
    "attribute.actor"      = "assertion.actor"
    "attribute.aud"        = "assertion.aud"
    "attribute.repository" = "assertion.repository"
  }
  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
}

プールとプロバイダを作成し、プールにプロバイダを紐づけています。

プロバイダでは、属性のマッピングと、認証方法として OIDC を指定しています。

また、attribute_condition を設定し、特定の組織でのみ利用可能に制限しています。
(repository_owner_id の値は https://api.github.com/users/<user_or_org_name> の id を指定)

参考: GitHub または他のマルチテナント ID プロバイダと連携する場合に属性条件を使用する - Google Cloud

そして最後に、デプロイ用のサービスアカウントとこれらを紐づけます。

resource "google_service_account_iam_member" "github_action_deploy_workload_identity" {
  service_account_id = google_service_account.github_action_deploy.name
  role               = "roles/iam.workloadIdentityUser"
  member             = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_pool.name}/attribute.repository/${var.github_org}/${var.github_repo}"
}

デプロイ用のサービスアカウントは、先ほど作成した Workload Identity Pool と Provider を介して、GitHub Actions からの認証を受け付けるように設定されます。

この設定により、GitHub Actions のワークフローが Google Cloud のリソースにアクセスする際、このサービスアカウントの権限を使用することができます。

具体的には、google_service_account_iam_member リソースを使用して、以下の設定を行っています:

  1. service_account_id: デプロイ用に作成したサービスアカウントを指定します。
  2. role: "roles/iam.workloadIdentityUser" を指定することで、外部IDシステム(この場合はGitHub)からこのサービスアカウントを使用する権限を付与します。
  3. member: Workload Identity Pool と、GitHubリポジトリを指定します。これにより、特定の GitHub リポジトリからの認証要求のみを受け付けるよう制限します。

この設定が完了すると、指定された GitHub リポジトリの Actions ワークフローから、Google Cloud のリソースに安全にアクセスできるようになります。ワークフロー内では、OpenID Connect (OIDC) トークンを使用して認証を行い、このサービスアカウントの権限で Google Cloud API を呼び出すことが可能になります。

GithubActions ワークフロー

最後のステップとして、GitHub Actions のワークフローを定義します。ワークフロー内では、公式から提供されている google-github-actions/auth アクションを使用して認証を行い、その後 Google Cloud CLI や他の関連アクションを使用してデプロイやその他の操作を実行できます。

- name: Authenticate to Google Cloud
  uses: google-github-actions/auth@v2
  with:
    workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
    service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}

ここで渡している workload_identity_provider は、プロジェクト番号、プール名、プロバイダー名を含む、ワークロード ID プロバイダーの完全な識別子です。コンソール画面から取得するか、以下の gcloud コマンドでも取得できます。

gcloud iam workload-identity-pools providers describe <プロパイダ名 今回の例では github-provider> \
--workload-identity-pool="<プール名 今回の例では github-pool>" \
--location="global" \
--format="value(name)"

# -> projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider

https://cloud.google.com/sdk/gcloud/reference/iam/workload-identity-pools/providers/describe

service_account は、デプロイ用に作成したサービスアカウント(email)です。

このように、Workload Identity 連携を使用することで、サービスアカウントのキーファイルを管理する必要がなくなります。GithubActions でデプロイを実施する際に、よりセキュアな CI/CD パイプラインを構築できます。

まとめ

Workload Identity 連携は、GCP 外のアプリケーションから GCP リソースを安全に操作する革新的な方法です。本記事では、GithubActions から GCP リソースをデプロイする過程を通じて、その実装方法を解説しました。

この技術を活用することで、サービスアカウントキーの管理が不要となり、セキュリティが向上し、運用負荷が大幅に軽減されます。より安全で効率的な CI/CD パイプラインや外部アプリケーション連携を構築できますので、是非試してみてください。

AWS セッションマネージャーでEC2に接続できない時の対処法

この記事は個人ブログと同じ内容です

はじめに

プライベートサブネットにあるEC2インスタンスに、VPCエンドポイント経由でAWS Systems Manager(SSM)を使って接続する際、かなり苦戦したので、その対処法についてこの記事でまとめます。

SSMで接続できないときのエラーメッセージ

SSMでEC2に接続できない場合、接続画面に以下のようなエラーメッセージが表示されることがあります。

SSM Agent がオンラインになっていません

SSM エージェントは Systems Manager エンドポイントに接続して自身をサービスに登録できませんでした。

私もAWS初心者で、最初は何が問題なのか全くわかりませんでした。原因は複数あるかもしれないので、以下の項目を一つずつ確認していきましょう。

確認すべき項目

  • AmazonSSMManagedInstanceCoreポリシーがアタッチされたIAMロールを持つEC2インスタンス
  • SSM エージェントがインストールされていること
  • 以下の4つのインターフェース型のVPCエンドポイントが設定されていること
    • com.amazonaws.region.ec2
    • com.amazonaws.region.ec2messages
    • com.amazonaws.region.ssm
    • com.amazonaws.region.ssmmessages
  • VPCエンドポイントへのTCP 443ポートの通信が許可されていること

特にネットワーク周りの設定がわかりにくいので、ここを確認するための具体的な方法を紹介します。

対処法1: VPC Reachability Analyzerを使う

VPC Reachability Analyzerは、VPC内のリソース間で接続テストを行うことができるツールです。 これを使うことで、EC2とVPCエンドポイントが通信できているか確認できます。また、通信が失敗している場合、どこで失敗しているかがわかるので非常に便利です。

送信元にEC2インスタンスを指定し、宛先にVPCエンドポイントのENI(Elastic Network Interface)を指定してテストを実施します。失敗した場合、どこで失敗したのかが一目でわかります。

Reachability Analyzerの画面

ただし、返りの通信(EC2からVPCエンドポイントへの応答)は確認できない点と、1回の分析に0.1ドルかかるため、無駄な分析をしないように注意してください。

対処法2: VPCフローログを見る

VPCフローログは、VPCのENI間のIPトラフィックに関する情報をキャプチャする機能です。 これを使うことで、EC2とVPCエンドポイントが通信できているかを確認できます。 ただし、VPC Reachability Analyzerとは異なり、セキュリティグループ(SG)やネットワークACL(NACL)のどちらが原因なのかは判別できないため、少し面倒です。 また、ログの内容はわかりにくいことが多いです。

まず、VPCの設定画面でフローログを作成します。すると、CloudWatchにロググループが作成されるので、その中からEC2とVPCエンドポイントのENIに関連するログを確認します。

フローログの画面

フローログの見方

AWS公式ドキュメントのフローログの例が参考になります。 https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/flow-logs-records-examples.html.

例えば、以下のログでは、IPアドレス 172.31.16.139 からプライベートIPアドレス 172.31.16.21 へのSSHトラフィック(宛先ポート22、TCPプロトコル)が許可されていることがわかります。

2 123456789010 eni-1235b8ca123456789 172.31.16.139 172.31.16.21 20641 22 6 20 4249 1418530010 1418530070 ACCEPT OK

実際にどんなログがあればいいのか

EC2のENIのフローログでは、EC2のIPアドレスからVPCエンドポイントのIPアドレスへのTCP 443通信が行われていることを確認します。

2 123456789010 {EC2またはVPCエンドポイントのENI} {EC2のIP} {VPCエンドポイントのIP} 20641 {443} 6 20 4249 1418530010 1418530070 {ACCEPT OK}

また、VPCエンドポイントのIPアドレスからEC2のIPアドレスへの443通信の返りも確認できれば問題ありません。

2 123456789010 {EC2またはVPCエンドポイントのENI} {VPCエンドポイントのIP} {EC2のIP} {443} 12345 6 20 4249 1418530010 1418530070 {ACCEPT OK}

対処法3: 他の方法でEC2に接続する

EC2に直接アクセスすることで、SSMエージェントがオンラインであるかどうかや、VPCエンドポイントとの疎通状況を確認できます。 他の方法で接続する場合、Instance Connectを利用するのがおすすめです。EC2のサブネットに作成すれば、セキュリティグループの設定し、VPCエンドポイントを作り、IAMポリシーを付与するだけで、接続できるため、手間が省けます。

SSMエージェントがオンラインであるか確認

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/ssm-agent-status-and-restart.html

SSMエージェントがオンラインかどうかは、以下のコマンドで確認できます(Amazon Linux 2023の場合)。

sudo systemctl status amazon-ssm-agent

成功すると次のような表示がされます。失敗している場合は、SSMエージェントのインストールや起動を行いましょう。

SSMエージェントのステータス確認

VPCエンドポイントとの疎通確認

以下のコマンドでVPCエンドポイントとの疎通を確認できます。 ただし、どこで失敗したかの詳細まではわかりません。

curl -v https://VPCエンドポイントのDNS

レスポンスに以下のようなメッセージが含まれていれば、成功です。

* Connected to {VPCエンドポイントのDNS} (IPアドレス) port 443

対処法4: NACLとSGを全開放する

これは最終手段です。すべてのNACLとSGで0.0.0.0/0からのアクセスを許可します。 これで接続が成功すれば、ネットワーク設定に問題があったと考えられます。失敗する場合は、IAMロールやSSMエージェントのインストール状態など、ネットワーク以外の原因と考えられ、原因の切り分けができます。

接続が成功した場合は、NACLやSGのルールを一つずつセキュアな設定に戻していきましょう。

最後に

セッションマネージャーでEC2に接続できないときの対処法を説明しました。VPC Reachability Analyzerは、原因を迅速に特定できるため非常に便利です。VPCフローログも使えますが、内容がわかりにくく初心者には少し難しいかもしれません。少しずつ慣れていきましょう。

AWSのプライベートサブネットにあるEC2にSession Managerを使ってセキュアにアクセスする

この記事は個人ブログと同じ内容です

Session Managerについて

Session ManagerはAWS Systems Managerの一部であり、安全にEC2インスタンスなどに接続するためのツールです。Session Managerを使用すれば、SSHキーやパブリックIPを割り当てることなく、AWSのプライベートサブネットに配置されたEC2インスタンスに接続することができます。

VPCエンドポイントについて

VPCエンドポイントは、VPC内のリソース(例:EC2 インスタンス)がインターネットを経由せずに、他のAWSサービスに接続できるようにするための仮想デバイスです。

VPCエンドポイントを使ったSession Managerでのアクセスに必要なもの

・IAMポリシーのAmazonSSMManagedInstanceCoreを持ったIAMロールを持つEC2
・SSM エージェントがインストールされているEC2
・以下の4つのインターフェース型のVPCエンドポイント
 ・com.amazonaws.region.ec2.
 ・com.amazonaws.region.ec2messages.
 ・com.amazonaws.region.ssm.
 ・com.amazonaws.region.ssmmessages.
VPCエンドポイントへの443通信の許可.

今回作るもの

今回はプライベートサブネットにあるEC2にVPCエンドポイント経由でSession Managerを使ってセキュアにアクセスできるようにします。

作るのは、以下の通りです。
VPCとサブネット.
・EC2インスタンス.
・IAMロール.
VPCエンドポイント.

図にするとこんな感じです。

実際に作っていく

ステップ1 VPCとサブネットを作る

VPCとプライベートサブネットをEC2用とVPCエンドポイント用で2つ作っていきます。

VPCとサブネットのcidr_blockと設計は以下の通りです。
10.0.0.0/16 は全体のアドレス範囲、
10.0.0.0/18 はリージョン、AZ用(4個)、
10.0.0.0/20 はサブネット用(4個)、
アドレス部は12ビット(=4096-5個)
になるような設計をしました。

name cidr_block
VPC 10.0.0.0/16
EC2のサブネット 10.0.16.0/20
VPCエンドポイントのサブネット 10.0.48.0/20
locals {
  aws_region = "ap-northeast-1"
  name = "sample"
  cidr_blocks = {
    vpc       = "10.0.0.0/16"
    private  = "10.0.16.0/20"
    endpoint = "10.0.48.0/20"
  }
}

# VPC
# 10.0.0.0/16 は全体のアドレス範囲
# 10.0.0.0/18 はリージョン、AZ用(4個) 
# 10.0.0.0/20 はサブネット用(4個)
# アドレス部は12ビット(=4096-5個)

resource "aws_vpc" "main" {
  cidr_block           = local.cidr_blocks.vpc
  enable_dns_hostnames = true
  enable_dns_support   = true
  tags = {
    Name = local.name
  }
}
# EC2用のプライベートサブネット
resource "aws_subnet" "private" {
  availability_zone = "ap-northeast-1a"
  cidr_block        = local.cidr_blocks.private
  vpc_id            = aws_vpc.main.id
}

resource "aws_network_acl" "private" {
  vpc_id     = aws_vpc.main.id
  subnet_ids = [aws_subnet.main.id]
}

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.main.id
}

resource "aws_route_table_association" "private" {
  subnet_id      = aws_subnet.private.id
  route_table_id = aws_route_table.private.id
}
# VPCエンドポイント用のプライベートサブネット
resource "aws_subnet" "endpoint" {
  availability_zone = "ap-northeast-1a"
  cidr_block        = local.cidr_blocks.endpoint
  vpc_id            = aws_vpc.main.id
}

resource "aws_network_acl" "endpoint" {
  vpc_id     = aws_vpc.main.id
  subnet_ids = [aws_subnet.main.id]
}

resource "aws_route_table" "endpoint" {
  vpc_id = aws_vpc.main.id
}

resource "aws_route_table_association" "endpoint" {
  subnet_id      = aws_subnet.endpoint.id
  route_table_id = aws_route_table.endpoint.id
}

ステップ2 IAMロールの作成

IAMポリシーのAmazonSSMManagedInstanceCoreを持ったIAMロールを作ります。

data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}
resource "aws_iam_role" "main" {
  name               = "ssm-iam-role"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

data "aws_iam_policy" "systems_manager" {
  arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_role_policy_attachment" "main" {
  role       = aws_iam_role.main.name
  policy_arn = data.aws_iam_policy.systems_manager.arn
}

resource "aws_iam_instance_profile" "systems_manager" {
  name = "ssm-instance-profile"
  role = aws_iam_role.main.name
}

ステップ3 EC2インスタンスの作成

EC2用のプライベートサブネットに、先ほど作ったIAMロールを付与したEC2インスタンスを作っていきます。 AMIは、SSM エージェントがすでにインストールされているAmazon Linux2023を使います。

resource "aws_instance" "main" {
  ami                         = "ami-05779067e4eff0b9d"
  instance_type               = "t4g.nano"
  subnet_id                   = local.cidr_blocks.private
  associate_public_ip_address = false
  vpc_security_group_ids      = [aws_security_group.ec2.id]
  availability_zone           = "ap-northeast-1a"
  iam_instance_profile        = aws_iam_instance_profile.systems_manager.name
}

resource "aws_security_group" "ec2" {
  name        = "ec2-sg"
  vpc_id      = aws_vpc.main.id
  description = "ec2 security group"
}

ステップ4 VPCエンドポイントの作成

以下の4つのインターフェースエンドポイントを作成します。

・com.amazonaws.region.ec2.
・com.amazonaws.region.ec2messages.
・com.amazonaws.region.ssm.
・com.amazonaws.region.ssmmessages.

resource "aws_vpc_endpoint" "main" {
  for_each = {
    ec2         = "com.amazonaws.ap-northeast-1.ec2"
    ec2messages = "com.amazonaws.ap-northeast-1.ec2messages"
    ssm         = "com.amazonaws.ap-northeast-1.ssm"
    ssmmessages = "com.amazonaws.ap-northeast-1.ssmmessages"
  }

  service_name       = each.value
  vpc_endpoint_type  = "Interface"
  vpc_id             = var.vpc_id
  security_group_ids = [aws_security_group.vpc_endpoint.id]
  subnet_ids         = [aws_subnet.private.id]
}

resource "aws_security_group" "vpc_endpoint" {
  description = "vpc-endpoint"
  name        = "vpc-endpoint"
  vpc_id      = aws_vpc.main.id
}

ステップ5 セキュリティグループとNACLの調整

先ほど作成したエンドポイントに対して、EC2からポート443のアクセスができるようにセキュリティグループとNACLを調整します。
注意ポイント:
・行きの通信だけではなく、帰りの通信も通れるようにしよう
・NACLは帰りの通信も記載する必要がありますが、セキュリティグループはステートレスなので、行きの通信のみで良いです

NACLの調整

# ステップ1で作成した、EC2用のプライベートサブネットのファイルの続き

resource "aws_network_acl_rule" "subnet_private1_ingress" {
  for_each = {
    ephemeral         = { action = "allow", cidr_block = local.cidr_blocks.endpoint, from_port = 1024, to_port = 65535, protocol = "tcp", rule_no = 100 }, #ssmの443通信の帰り
  }

  network_acl_id = aws_network_acl.private.id
  rule_action    = each.value.action
  cidr_block     = each.value.cidr_block
  from_port      = each.value.from_port
  to_port        = each.value.to_port
  protocol       = each.value.protocol
  rule_number    = each.value.rule_no
  egress         = false
}

resource "aws_network_acl_rule" "subnet_private1_egress" {
  for_each = {
    https                = { action = "allow", cidr_block = local.cidr_blocks.endpoint, from_port = 443, to_port = 443, protocol = "tcp", rule_no = 100 },                  #ec2からインターネット通信するため
  }

  network_acl_id = aws_network_acl.private.id
  rule_action    = each.value.action
  cidr_block     = each.value.cidr_block
  from_port      = each.value.from_port
  to_port        = each.value.to_port
  protocol       = each.value.protocol
  rule_number    = each.value.rule_no
  egress         = true
}
# ステップ1で作成した、VPCエンドポイント用のプライベートサブネットのファイルの続き

resource "aws_network_acl_rule" "subnet_endpoint1_ingress" {
  for_each = {
    https_from_private = { action = "allow", cidr_block = local.cidr_blocks.private, from_port = 443, to_port = 443, protocol = "tcp", rule_no = 100 },
  }

  network_acl_id = aws_network_acl.endpoint.id
  rule_action    = each.value.action
  cidr_block     = each.value.cidr_block
  from_port      = each.value.from_port
  to_port        = each.value.to_port
  protocol       = each.value.protocol
  rule_number    = each.value.rule_no
  egress         = false
}

resource "aws_network_acl_rule" "subnet_endpoint1_egress" {
  for_each = {
    ephemeral_to_private1 = { action = "allow", cidr_block = local.cidr_blocks.private, from_port = 1024, to_port = 65535, protocol = "tcp", rule_no = 100 },
  }

  network_acl_id = aws_network_acl.endpoint.id
  rule_action    = each.value.action
  cidr_block     = each.value.cidr_block
  from_port      = each.value.from_port
  to_port        = each.value.to_port
  protocol       = each.value.protocol
  rule_number    = each.value.rule_no
  egress         = true
}

セキュリティグループの調整

# ステップ3で作ったEC2のファイルの続き

resource "aws_vpc_security_group_egress_rule" "ec2_egress" {
  from_port         = 443
  to_port           = 443
  ip_protocol       = "tcp"
  security_group_id = aws_security_group.ec2.id
  cidr_ipv4         = local.cidr_blocks.private
}
#ステップ4で作ったVPCエンドポイントのファイルの続き

resource "aws_vpc_security_group_ingress_rule" "vpc_endpoint_ingress" {
  from_port         = 443
  to_port           = 443
  ip_protocol       = "tcp"
  security_group_id = aws_security_group.vpc_endpoint.id
  cidr_ipv4         = local.cidr_blocks.private
}

ステップ6 Session Managerでアクセスする

  1. AWSにアクセスして、EC2のインスタンス選択して、接続ボタンを押す

  2. 画像のようになっていれば成功です!

まとめ

今回SSMでの接続をしましたが、個人的にネットワーク周りにかなり苦戦したので、うまくいかない時は入念に見直しましょう。
あと、公式ドキュメント読みにくくて、個人で書いたブログに行きがちですが、公式は間違いがないので最優先で読みましょう。
不明点や問題点などありましたらコメントお願いします。

参考記事.
https://zenn.dev/kazutech/articles/3559db9605d198
https://dev.classmethod.jp/articles/terraform-session-manager-linux-ec2-vpcendpoint/

Vercel へのビルド&デプロイをローカル環境から行う

この記事は個人ブログと同じ内容です

www.ritolab.com


Vercel は、モダンなウェブアプリケーションの開発、デプロイ、ホスティングを簡単かつ効率的に行うためのクラウドプラットフォームです。AWS Amplify, Firebase Hosting, Netlify, Heroku あたりと同じ位置づけのイメージ。

基本的には、Vercel と Github を連携させるだけで、開発ブランチプッシュやプルリクエスト作成時にプレビュー環境を構築してくれたり、デフォルトブランチにマージしたら本番環境へデプロイしてくれたりなど、簡単に運用が行える点が魅力です。

そんな中、今回は、ローカル環境から WEB アプリを Build し、Vercel へのデプロイを実施してみます。

Vercel CLI

ローカル環境からビルドやデプロイを行うときは、Vercel CLI を用います。まずは Vercel CLI をインストールします。

Vercel CLI インストール

pnpm i -g Vercel

バージョン確認

Vercel --version

認証

Vercel CLIでは、リソースにアクセスしたり管理タスクを実行したりする前に、ログインして認証する必要があります。ターミナル環境では、手動入力が必要な Vercel login を使用できます。

ちなみに、手動入力が不可能な CI/CD 環境の場合は、トークンページでトークンを作成し、--token オプションを使用して認証することができます。

Vercel login

以下の選択肢が表示されるので、自身がいつもログインしている方法を選択します。

? Log in to Vercel
Continue with GitHub
Continue with GitLab
Continue with Bitbucket
Continue with Email
Continue with SAML Single Sign-On
─────────────────────────────────
Cancel

選択するとブラウザが開くのでログインすれば認証が完了します。

プロジェクト設定の取得

ローカル環境でビルドを実行するために、予めプロジェクトの設定を取得しておく必要があります。Vercel pull コマンドで、プロジェクト設定をローカルに作成します。

プロジェクトをすでに作成済みであればこのとき、環境変数も作成されます。プレビュー環境や本番環境で設定している環境変数が別々の場合も多々ありますが、--environment オプションをつけることで、指定した環境のプロジェクト設定を作成できます。

Vercel pull --environment=preview

Vercel pull --environment=production

Vercel pull コマンドを実行すると、以下のように対話型で進むので、適宜入力していきます。

? Set up “~/path/to/your-project-root”?(セットアッププロジェクトの確認)
? Which scope should contain your project?(組織・個人などスコープの選択)
? Link to existing project?(既存のプロジェクトとリンクさせるか)
? What’s the name of your existing project?(プロジェクト名の入力)

🔗  Linked to your-scope/your-project-name (created .Vercel and added it to .gitignore)
> Downloading `development` Environment Variables for Project your-project-name
✅  Created .Vercel/.env.development.local file  [176ms]
  • ? Set up “~/path/to/your-project-root”?(セットアッププロジェクトの確認)
    • Vercel pull コマンドを実行した、ローカル環境の現在位置にプロジェクト設定を保存することを確認
  • ? Which scope should contain your project?(組織・個人などスコープの選択)
    • Vercel のアカウントについて確認される。個人アカウントしかなければ 1 つだけだし、例えば有料でチームを作成していたりすれば、どれをスコープとして使用するかを選択できる
  • ? Link to existing project?(既存のプロジェクトとリンクさせるか)
    • すでに Vercel 上にプロジェクトがある場合は、y(yes) を入力し、次でプロジェクト名を入力する
    • no の場合は、新しくプロジェクトが作成される
  • ? What’s the name of your existing project?(プロジェクト名の入力)
    • リンクさせるプロジェクト名を入力

Vercel pull が完了すると、.Vercel/ ディレクトリが作成され、その中に環境変数ファイルと project.json が作成されます。

.Vercel
├── .env.xxx.local
└── project.json

ビルド

Vercel build コマンドを使用して、ローカル環境で Vercel プロジェクトをビルドします。

Vercel build コマンドは、Vercel プロジェクトをローカル環境または独自の CI 環境でビルドするために使用できます。

# プレビュー環境用ビルド
Vercel build

# 本番環境用ビルド
Vercel build --prod

ちなみにこのコマンドは、ビルドエラー時のメッセージをローカル環境で受け取れるので、普段クラウドでビルド&デプロイしていたとしても、クラウド上でのエラーの原因調査など、Vercel プロジェクトのデバッグにも役立ちます。

ビルド成果物は、Build Output API に従って .Vercel/output ディレクトリに配置されます。

.Vercel
├── output/ # ここにビルドされたソースコードが配置される
├── .env.xxx.local
└── project.json

デプロイ

ビルドができたので、これを Vercel へデプロイします。

# プレビュー環境用デプロイ
Vercel deploy --prebuilt

# 本番環境用デプロイ
Vercel deploy --prebuilt --prod

--prebuilt オプションをつけ、ローカル環境のビルド済みソースをデプロイします。

ビルドコマンドもそうですが、--prod オプションを付けなければ、プレビュー環境用のデプロイになります。

% Vercel deploy --prebuilt

Vercel CLI 36.0.0
🔍  Inspect: https://Vercel.com/aaaa/bbbb/cccc [2m]
✅  Preview: https://xxxxxxx.Vercel.app [2m]
📝  To deploy to production (xxxxxxx.net +1), run `Vercel --prod`

デプロイ URL とプレビューの URL が表示されるので、デプロイが終わったらアクセスすると、動作確認ができます。

ここで示される デプロイ URL とプレビューの URL は、Github プルリクエスト時に提示されるものと同じ URL です。

プレビュー環境で動作確認を行った後、本番用にビルド&デプロイを実施すれば、ローカル環境から Vercel へデプロイ完了です。

まとめ

ローカル環境から Vercel へ web アプリケーションをデプロイするには、Vercel pull, Vercel build, Vercel deploy の 3 つのコマンドを実行すればデプロイできます。

例えば、環境切り替えも含め、これらのコマンドをまとめたシェルスクリプトなどを作成しておけば、デプロイはかなり簡単になります。

冒頭にも述べた通り、基本的には、Vercel と github を連携させるだけで CD 環境が構築されるため、通常なら使用しない場合も多いと思います。

ローカル環境からデプロイする必要が生じた際には、参考にしてみてください。

XP入門

この記事は個人ブログと同じ内容です


はじめに

今年の6月からスクラムマスターの役割を担うことになりました。 しかし、私自身現在の会社に勤めるまでアジャイル開発の経験が全くなかったので、理解を深めるために XP について学ぶことにしました。 この記事はケント・ベック氏によって著された「エクストリームプログラミング」の7章までの読書メモと感想です。

XP とは

XP (エクストリームプログラミング)は、アジャイル開発の手法の一つです。 エクストリームは日本語で「極限」という意味で、柔軟な変更や継続的な成長により開発プラクティスを極限まで洗練させ、品質の高いソフトウェアを効率的に開発することを目指します。

価値、原則、プラクティス

XP には、価値、原則、プラクティスがあります。 本書では、イチゴ栽培を例に挙げて説明されていました。

イチゴの隣にマリーゴールドを植える = プラクティス
マリーゴールドはイチゴを食べる虫を寄せ付けない = 価値
隣り合った植物がお互いの弱点を補う(共生栽培) = 原則

この例と本書に書かれている他の内容を基にした私の理解は以下です。

プラクティス = 実際の行動
価値 = プラクティスによって生まれる恩恵、目的
原則 = プラクティスと価値の橋渡しをするもの、活動指針

次からそれぞれについて詳しく内容を見ていきたいと思います。

価値

XP では、個人としてではなく、チームや組織の一員としてどのように振る舞うかに重きを置いています。 全員がチームにとって大切なことに集中するとしたら、何をするべきかについて5つの価値が定義されています。

  • コミュニケーション
  • シンプリシティ
  • フィードバック
  • 勇気
  • リスペクト

コミュニケーション

  • 開発中に問題が発生したときには、すでに誰かが解決策を知っていることが多い。だがその情報は変更する権限のある人には伝わらない。
  • 予想外の問題に遭遇した際、コミュニケーションが解決につながる可能性がある。過去に同じような問題を経験した人に話を聞くこともできるし、問題の再発防止についてチームで話し合うこともできる。
  • チーム感覚や効果的な協力関係を生み出すために重要なもの。

シンプリシティ

  • ムダな複雑性を排除するために、何ができるかを考える。

フィードバック

  • 一時的な完成に期待するよりも、常に改善を続けていくことが大事。
  • XP ではできるだけ早く、できるだけ多くのフィードバックを生み出そうとする。
  • フィードバックが早く手に入れば、その分だけ早く適応できる。

勇気

  • 今までのやり方を急激に変えたり、あるいは、今までのやり方を否定するような変化を受け入れることが必要。
  • 勇気のみでは危険だが、他の価値と合わせれば強力。
  • 「勇気」を持って真実を語れば、「コミュニケーション」や信頼が強化されていく。
  • うまくいかない解決策を捨て、「勇気」を持って新しい解決策を見つければ、「シンプリシティ」が促進される。

リスペクト

  • チームメンバーがお互いに関心がなく、何をしているかを気にもとめないようであれば、XPはうまくいかない。
  • チームメンバーがプロジェクトを大切にしないのであれば、何をしたところで開発は成功しない。
  • ソフトウェア開発において人間性と生産性を同時に高めるには、チームに対する個人の貢献をリスペクトする必要がある。

原則

価値は抽象度が高いので、そのままでは振る舞いの指針になりません。 そのため、XP では価値の指針となる原則が定義されています。

  • 人間性
  • 経済性
  • 相互利益
  • 自己相似性
  • 改善
  • 多様性
  • ふりかえり
  • 流れ
  • 機会
  • 冗長性
  • 失敗
  • 品質
  • ベイビーステップ
  • 責任の引き受け

このうち私が興味を持った原則をいくつかご紹介します。

人間性

  • 人間がソフトウェアを開発する。これはシンプルで逃れようのない事実。
  • 優れた開発者になるには、何が必要だろうか?
    • 基本的な安全性 - 空腹、身体的な危害、愛する人を危険にさらすものが存在しないこと。
    • 達成感 - 自分が所属する社会に貢献する機会や能力
    • 帰属意識 - グループに所属して、承認や説明責任を受け取ったり、共通の目標に貢献したりすること。
    • 成長 - スキルや視野を広げる機会。
    • 親密な関係 - 他人を理解して、他人から深く理解されること。
  • チームによるソフトウェア開発で難しいのは、個人の欲求とチームのニーズの両方のバランスを取ること。
  • 優れたチームが素晴らしいのは、メンバーたちが信頼関係を築き、一緒に働くことによって、みんなが自分らしくいられること。

相互利益

  • あらゆる活動は、関係者全員の利益にならなければいけない。
  • 相互利益とは、最も重要であり、最も実行が難しい XP の原則。
  • XP の相互理解は、現在の自分、将来の自分、顧客に対する利益を求める。
  • 将来とのコミュニケーションの問題を相互利益になるやりかたで解決する例。
    • 現時点で設計や実装がうまくできるような自動テストを書く。また、テストは将来のプログラマーにも使えるように残しておく。こうすることで、現在の自分と将来の保守担当者の両方の利益になる。
    • 意図しない複雑性を排除するために、注意深くリファクタリングする。自分の満足感が得られ、欠陥が少なくなり、あとでコードに触れた人が理解しやすくなる。

多様性

  • 問題や落とし穴を見つけたり、問題を解決する方法を複数考えたり、解決策を実現したりするためには、さまざまなスキル、考え方、視点を組み合わせる必要がある。つまりチームには多様性が必要。
  • 多様性には衝突がつきもの。
  • 衝突が発生しないチームなど存在しないので、生産的に衝突を解決できるかどうかを考えてみる。
  • みんなをリスペクトして、自分の言い分を主張すれば、ストレスのかかる状況でもコミュニケーションは円滑になるはず。

品質

  • 品質を犠牲にするのは、効果的なコントロール方法ではない。
  • 品質は制御変数ではない。
  • 低品質を受け入れることで、プロジェクトが早くなることはない。
  • 高品質を要求することで、プロジェクトが遅くなることもない。
  • むしろ品質を高めることで、デリバリーが高速になることが多い。
  • 品質基準を下げてしまうと、デリバリーが遅くなり、予測できなくなってしまう。

ラクティス

ラクティスとは、チームが日常的に行うものです。 単独でも機能しますが、組み合わせた方がより機能します。 すぐに改善につながり、どれからでも始められる「主要プラクティス」と、先に主要プラクティスを習得しておかなければ難しいであろう「導出プラクティス」について書いてありました。 この記事では「主要プラクティス」について紹介します。

チーム全体

  • プロジェクトの成功に必要なスキルや視点を持った人たちをチームに集めること。
  • プロジェクトの健全性のために綿密なやりとりが必要なところでは、機能単位ではなく、チーム単位でやりとりをすること。
  • そのためには、以下のような「チーム」感が必要。
    • 我々は、帰属している。
    • 我々は、一緒の仲間である。
    • 我々は、お互いに仕事、成長、学習を支えている。
  • 「チーム全体」の構成要素は動的に変化する。
    • スキルや考え方が重要なときには、それを身に付けた人をチームに迎え入れればいい。
    • 必要なくなれば、チームから外れてもらえばいい。
    • プロジェクト単位でメンバーを動的に変更してもいい。

いきいきとした仕事

  • 生産的になれる時間だけ働くこと。
  • 無理なく続けられる時間だけ働くこと。
  • 意味もなく燃え尽きてしまい、次の2日間の作業が台無しになることは、自分にとってもチームにとってもいいことではない。
  • ソフトウェア開発は洞察力のゲーム。洞察力は、準備の整った、休息のとれた、リラックスした精神から生み出される。
  • その他の方法では制御不能になったときに、制御を取り戻す手段として、長時間労働することが多い。
  • だが、疲労した状態では、バリューを奪っていることさえ気づかない。
  • 病気のときは休息と回復に努め、自分とチームをリスペクトすること。静養こそがいきいきとした仕事に戻る近道。

ペアプログラミング

  • 同じマシンを前にした2人でプログラムを書くこと。
  • ペアプログラミングでやること。
    • お互いにタスクに集中する。
    • システムの改良について意見を出し合う。
    • イデアを明確にする。
    • パートナーがハマったら主導権を握り、相手の失望感を軽減させる。
    • お互いにチームのプラクティスの説明責任を果たせるようにする。
  • ペアプログラミングは満足感はあるが、実際にやると疲れるプラクティス。
  • 休息を挟めば、新鮮な気持ちを一日中保つことができる。
  • うまくやるには、お互いのパーソナルスペース、個人差をリスペクトすることが重要。

ゆとり

  • どのような計画にも、遅れたときに外せるような重要度の低いタスクを含めること。
  • 少しでもやるべきことを果たせば、人間関係の再構築につながるはず。
  • 明確で正直なコミュニケーションは信頼を高める。

インクリメンタルな設計

  • システムの設計に毎日手を入れること。
  • システムの設計は、その日のシステムのニーズにうまく合致させること。
  • 最適だと思われる設計が理解できなくなってきたら、少しずつだが着実に、自分の理解できる設計に戻していくこと。
  • 欠陥の修正コストは時間の経過によって指数関数的に増加することが示されている。
  • 設計に毎日注意を払わなければ、変更コストは急増する。その結果、設計が貧弱で脆弱な変更しにくいシステムになってしまう。
  • シンプルで役に立つ解決策は、重複を排除すること。
  • 重複のないコードは変更しやすい。
  • インクリメンタルな設計は、使用する直前に設計するのが最も効率的。
  • そうすれば、システムはシンプルになり、進捗は早くなり、テストが書きやすくなる。システムが小さくなるので、チームのコミュニケーションも軽減できる。

おわりに

スクラム開発と比較すると、XP はより技術的内容にフォーカスしているように思いつつも、原則やプラクティスからは人間性を大切にする考え方が根底にあると感じました。 ソフトウェア開発は人間が行うものであり、またそれを使うのも人間です。 良いものを作ってエンドユーザーの幸福に寄与するためにも、まずは私たちがコンディションを整えたり社会的欲求を満たすことで、自分らしくいられる環境づくりが重要だと思います。 本書で学んだことを取り入れ、スクラムマスターとしてより良い環境づくりを目指したいです。