2023-10-18

「AWSではじめるインフラ構築入門」をGCPに読み替えてやってみる その4

前回の続き
勉強メモなので間違ったことが書かれている可能性があります 🙏

IAP(Identity-AwareProxy)を試してみる

前回、踏み台サーバーを立てて、同じサブネットにある外部 IP を持たない VM へ gcloud コマンドでログインできることを確認したが
その調査の際、この記事を見つけ、実は GCP においては踏み台サーバーはなくてもよいということを知ったので試してみたい

↑ の記事では以下 2 つの設定を行えば IAP が使えるらしい

  • ファイアウォールルールの追加
    • これは前回もやっていたが、 35.235.240.0/20 からの 22/TCPIngress を許可する
      • 前回追加した ssh の設定は消しておく
  • IAM 権限の付与
    • compute.instances.get 権限
    • IAP で保護された トンネル ユーザー (roles/iap.tunnelResourceAccessor)

まずは踏み台サーバーへログインして、そこから同じゾーンにあるプライベートサブネットの VM へログインするルートを試してみる
上記 IAM 権限をアタッチする必要があるのは、踏み台サーバーにアタッチされているサービスアカウントなので、権限を追加して実行してみる

gcloud compute ssh 別VMの名前

Enabling service [networkmanagement.googleapis.com] on project [XXX] が出た後、ERROR: (gcloud.compute.ssh) PERMISSION_DENIED: Request had insufficient authentication scopes. と言われて失敗した
エラーログに、トラブルシュートするコマンドがあると書かれているので実行してみる

gcloud compute ssh 別VMの名前 --project=XXX --zone=asia-northeast1-b --troubleshoot

するとどうやら google.api.serviceusage.v1.ServiceUsage.EnableService の権限がないらしい

これは最初「API とサービス」にある API のことかと思ったけど、VM にも権限があることを思い出したので、VM の「Identity and API access」にある「アクセススコープ」をみてみる
今は「API ごとにアクセス権を設定」になっていて、前回編集した「Compute Engine」だけが読み書きできるようになっている

が、どれを有効化したら google.api.serviceusage.v1.ServiceUsage.EnableService のエラーを解消できるのかわからなかったので、一旦「すべての Cloud API に完全アクセス権を許可」したところ、無事踏み台サーバーから別の VM へ ssh することができた!

VM インスタンス一覧にある「SSH」ボタンから、外部 IP がある VM にも外部 IP がない VM にもブラウザ経由でログインできたし、「gcloud コマンドを表示」から取得したコマンドでもログインできた
(外部 IP がない VM のコマンドには --tunnel-turough-iap というオプションがついていた)

ということで、確かに踏み台サーバーを用意せずとも、外部 IP がない VM へ ssh できた
これは便利だ

第 7 章

IAP の動作確認ができたので、本の第 7 章へと入っていく
この章ではロードバランサを用意している

ロードバランサの主な役割は以下の三つとなる

  • リクエストの分散
    • イメージとしてはマックのレジのような感じ
    • お客さん(リクエスト)が並んでいて、先頭の人が空いたレジ(Web サーバー)に進んでいくのを制御する
    • ロードバランサは Web サーバーの動作状況を常に確認しているので、ダウンした Web サーバーにはリクエストを割り振らないようにする
  • SSL 処理
    • HTTPS で通信を行うアレ
    • SSL での通信はブラウザとサービス間を流れるデータは暗号化されているので、どこかで暗号化したり復号化したりする必要がある
    • 暗号化/復号化の処理は複雑な計算が行われるため、Web サーバーで行うと負荷がかかり、性能の悪化を招く
    • ロードバランサには暗号に関する処理を高速に行う専用の仕組みがあるので、ロードバランサに任せるのがよい
  • 不正リクエスト対策
    • ロードバランサを挟まないで、Web サーバーが直接ブラウザなどとやりとりできる状態の場合、不正なリクエストが送られる可能性がある
    • これまたロードバランサにはこのような不正アクセスに対応するための仕組みがあるので、ロードバランサを立てるのがよい
      • Web サーバーごとに対策をするよりもロードバランサで対策する方が効率的という理由もある

GCP ではここを見てもらえるとわかるように、レイヤ 7 とレイヤ 4 のロードバランサを作成する入り口がそれぞれ存在している模様(2023 年 10 月現在も大きく変わりはない)

ロードバランサによるリクエストのルーティングについてまとめると以下の通り

  • Web アプリを公開する場合、通常ポートは 80(HTTP)または 443(HTTPS)を使って公開するので、これをロードバランサに設定する
  • ロードバランサの内側にいるサーバーはこの設定に合わせる必要はなく、1024より大きいポート番号 でロードバランサからの「HTTP リクエスト」を待ち受ける
    • HTTPS ではなく HTTP なのは、HTTPS の暗号化/復号化をサーバーではなくロードバランサにやってもらうため
    • 前述したサーバーの演算負荷を減らしたり、証明書の管理コストなどを削減することができる
  • サーバーのポート番号を 1024 より大きい値を使う理由は、サーバーのセキュリティを高めるのが狙い
    • Linux などの OS では 0〜1023 までのポートでリクエストを待ち受けるためには、root 権限を持ったユーザーでアプリケーション(nginx とか)を動作させる必要がある
    • もし悪意のあるユーザーに乗っ取られてしまった場合、root 権限まで掌握されてしまうので、root 権限ではなく一般的な権限を持つユーザーを使って 1024 以上のポートでアプリケーションを動かすのが良い
  • ロードバランサには外部に公開しているプロトコル(HTTP/HTTPS など)とポート番号(80/443 など)の組み合わせを、内部の Web サーバーが待ち受けているプロトコルとポート番号に変換する機能を持っている
    • これを リクエストのルーティング と呼ぶ

以上を踏まえてロードバランサを用意してみる
説明はやっぱりG-gen さんの記事がとてもわかりやすくて勉強になる

バックエンドサービスのインスタンスグループ

ロードバランサのバックエンドサービスには、インスタンスグループや XXX ネットワークエンドポイントグループなど、様々なものが指定できる
今回は基本となる インスタンスグループ について見ていこうと思う

  • インスタンスグループ
    • ここまで VM は 1 つ 1 つインスタンスを立ち上げてサーバーとして使うことを想定していたが、GCP ではインスタンスグループという形で複数のインスタンスを構築することができる
    • インスタンスグループを使うと何が嬉しいのかというと、1 つのサーバーに障害が発生した際、インスタンスグループに設定した「インスタンステンプレート」というテンプレートを元に、別のインスタンスが自動的に立ち上がり、そちらにリクエストが割り振られるようになる(要はサービスが停止しない)
      • AWS で例えると、ELB 配下の EC2 に障害が発生しても、同じスペックで自動的に EC2 が立ち上がり、ELB は新しく立ち上がった EC2 へリクエストを割り振ることで、サービスが継続できるという感じ
    • さらに、サーバーのアップデートなどを行う際はメンテナンスに入れる必要がなく、アップデートした VM が立ち上がっていき、徐々に古いバージョンの VM と入れ替わっていくということもやってくれる(便利)
    • ただし、インスタンスグループで構築した場合、設定によってはサーバーを停止することができない模様

ロードバランサの動作確認

動作確認をやっていく
やったことを以下にまとめた

  • インスタンスグループ作成
    • New unmanaged instance group で作る
      • New managed instance group だとインスタンステンプレートを使う必要があり、ここでは既存の VM を使いたいのでこちらを選択
    • ロケーションは VM があるリージョンとゾーンを指定
    • VM では 3000 ポートでリクエストを待ち受けるので、ポートマッピングは http の 3000 番ポートを指定
  • ヘルスチェック
    • TCP の 3000 番ポートを指定して、その他の項目はデフォルトにした
  • ロードバランサを追加
    • フロントエンド
      • http の 80 番ポートで待ち受け
      • IP はエフェメラル
    • バックエンド
      • バックエンドサービスを作成し、↑ のインスタンスグループを指定する
        • ポートは http の 3000 番ポートを指定
    • ルーティングルール
      • 単純なホストとパスのルールを指定して、バックエンドには ↑ で用意したバックエンドサービスを指定する
  • ファイアウォールルールの追加
    • ロードバランサから VM までのルートを許可するルールがないので追加する
      • VM へのアクセスになるので Igress(上り)を許可
      • ソースフィルタはロードバランサの IP を指定(エフェメラルなのでよしなに指定する)
      • プロトコルとポートは tcp の 3000 番ポートを指定
  • VM 内でサーバーを立ち上げる
    • 色々インストールするのダルいので、VM に入っている python でサクッと立ち上げる python -m SimpleHTTPServer 3000
      • index.html の用意を忘れずに

ここまで設定してロードバランサの IP にアクセスしてみると、ブラウザの画面には no healthy upstream と表示された
ググるとまだファイアウォールルールの追加が足りていないっぽいので足す

  • ファイアウォールルールをさらに追加する
    • ↑ の記事にある通り 35.191.0.0/16130.211.0.0/22 の上りを許可する(プロトコルとポートは tcp の 3000)

ブラウザで改めてロードバランサへアクセスしたところ、用意した index.html の内容が表示された!

サーバーを 2 台立ててロードバランサの動作を確認する

ロードバランサを通して、1 台のサーバーへの疎通確認はできた
それではロードバランサの真骨頂である、複数台のサーバーを立てて、片方が死んだらもう片方へリクエストが割り振られるかどうかの確認をしていく
以下のようにリソースを構成した(めちゃながいぞ)

  • VPC
    • サブネット作成モード: カスタムサブネット
    • 動的ルーティングモード: リージョン
  • サブネット
    • パブリックサブネット 1
      • リージョン: asia-northeast1
      • スタックタイプ: IPv4
      • 内部 IP 範囲: 10.0.0.0/20
      • 外部 IP 範囲: なし
    • パブリックサブネット 2
      • リージョン: asia-northeast1
      • スタックタイプ: IPv4
      • 内部 IP 範囲: 10.0.16.0/20
      • 外部 IP 範囲: なし
    • プライベートサブネット 1
      • リージョン: asia-northeast1
      • スタックタイプ: IPv4
      • 内部 IP 範囲: 10.0.64.0/20
      • 外部 IP 範囲: なし
    • プライベートサブネット 2
      • リージョン: asia-northeast1
      • スタックタイプ: IPv4
      • 内部 IP 範囲: 10.0.80.0/20
      • 外部 IP 範囲: なし
  • ファイアウォール
    • CloudIAP のためのルール
      • 方向: 上り
      • 一致した時のアクション: 許可
      • ターゲット: web サーバーに割り当てたサービスアカウント
      • ソースフィルタ: 35.235.240.0/20
      • プロトコルとポート: TCP:22
    • ロードバランサから web サーバーへ通信するためのルール
      • 方向: 上り
      • 一致した時のアクション: 許可
      • ターゲット: web サーバーに割り当てたサービスアカウント
      • ソースフィルタ: ロードバランサのパブリック IP を指定
      • プロトコルとポート: TCP:3000
    • web サーバーのヘルスチェックのためのルール
      • 方向: 上り
      • 一致した時のアクション: 許可
      • ターゲット: web サーバーに割り当てたサービスアカウント
      • ソースフィルタ: 35.191.0.0/16, 130.211.0.0./22
      • プロトコルとポート: TCP:3000
  • サービスアカウント
    • web サーバーへ割り当てるアカウントなので、それがわかる名前をつける
    • アクセス許可: ComputeOS ログイン, IAP で保護されたトンネルユーザー
  • VM インスタンス
    • web サーバー 1
      • ゾーン: asia-northeast1-b
      • ネットワーク: 作成した VPC
      • サブネットワーク: プライベートサブネット 1
      • サーバーの内部 IP: 10.0.64.2
      • サービスアカウント: web サーバーのために作ったサービスアカウント
    • web サーバー 1
      • ゾーン: asia-northeast1-c
      • ネットワーク: 作成した VPC
      • サブネットワーク: プライベートサブネット 2
      • サーバーの内部 IP: 10.0.80.2
      • サービスアカウント: web サーバーのために作ったサービスアカウント
  • インスタンスグループ
    • instance-group-private01
      • Unmanaged instance group
      • リージョン: asia-northeast1
      • ゾーン: asia-northeast1-b
      • ネットワーク: 作成した VPC
      • サブネット: プライベートサブネット 1
      • VM インスタンス: web サーバー 1
      • ポートマッピング: http, 3000
    • instance-group-private02
      • Unmanaged instance group
      • リージョン: asia-northeast1
      • ゾーン: asia-northeast1-c
      • ネットワーク: 作成した VPC
      • サブネット: プライベートサブネット 2
      • VM インスタンス: web サーバー 2
      • ポートマッピング: http, 3000
  • ヘルスチェック
    • プロトコル: TCP
    • ポート: 3000
    • 他はデフォルト
  • バックエンドサービス
    • 名前: private-server
    • バックエンドタイプ: インスタンスグループ
    • プロトコル: HTTP
    • 名前付きポート: http
    • タイムアウト: 30
    • バックエンド(2 個のインスタンスグループを指定)
      • リージョン: asia-northeast1
      • instance-group-private01, ポート番号: 3000
      • instance-group-private02, ポート番号: 3000
    • ヘルスチェック: ↑ で用意したヘルスチェックを指定
  • ロードバランサ
    • フロントエンド
      • プロトコル: HTTP
      • IP アドレス: エフェメラル
      • ポート 80
    • バックエンド
      • private-server(↑ で用意したバックエンドサービス)
    • ルーティングルール
      • モード: 単純なホストとパスのルール
      • ホストとパスのルール: バックエンド 1 に private-server を指定

↑ の設定を行い、以下を実施する

  • web サーバー 1 と 2 に、どのサーバーのリソースなのかわかるような内容を書いた index.html を配置する
  • web サーバー 1 と 2 にそれぞれ ssh し、 python -m SimpleHTTPServer 3000 を実行してリクエストを待ち受ける

そしてロードバランサのパブリック IP へブラウザからアクセスすると、僕の場合は最初 web サーバー 1 へアクセスが繋がった
そして web サーバー 1 の python -m SimpleHTTPServer 3000 を終了すると、数秒の間 upstream connect error or disconnect/reset before headers. retried and the latest reset reason: remote connection failure, transport failure reason: delayed connect error: 111 というエラーが表示されたのち、web サーバー 2 へとリクエストが繋がるようになった

ということで、シングルリージョンのマルチゾーンのインフラ構成に成功した