AWS にて固定 IP で GitLab を動かすなら NLB の利用を検討したほうがいい

あいさつ

こんにちは、市橋です。今回はオンプレ GitLab を AWS 移行しようとしたら、いろいろあった末 NLB を使うしかなくなった話をします。 また、最後にちょっとだけ苦労話もします。

概要

主張

以下の三要件を満たす場合、 NLB を使うしか無い。

  1. https アクセスと sshアクセス (git 操作時) で同じドメインを使う
  2. 固定 IP 必須
  3. インスタンスに直接アクセスさせたくない

どうしてそうなった

  • AWS にオンプレの GitLab を移行していた
  • 同じドメインで https アクセスも ssh での git clone もしたい
  • VPN のシステムの関係でどうしても固定 IP が必要
  • 比較検討の結果 NLB を利用するしかなかった

話の経緯: AWS 移行後の GitLab で利用するロードバランサー選定

弊社は現在 AWS へ様々なサービスを移行中です。オンプレミスサーバーで動く様々なシステムが移行対象ですが、 その中にはコードリポジトリとして利用している GitLab も含まれています。

現在の構成

弊社は GitLab Community Edition を Omnibus で利用しています。1 台の仮想マシンにて Nginx, Rails, puma, Gitaly, PostgreSQL, Redis, Docker Registry といったものがすべて動いているということです。

現状処理能力は足りており、移行の際も 1 台のインスタンスで運用していく予定です。 (ただし、DB や Redis, S3 へのデータの分離は可能な限り行おうと思っています)

使われ方

オンプレ GitLab へのアクセスは主に以下の方法で行なわれています。

  • https による Web UI へのアクセス
  • https による git 操作
  • ssh による git 操作

さて、git 操作についてですが弊社は (というか殆どの場合そうだと思いますが) ssh による操作がほとんどです。 Web アクセスする際と同じドメインにて、 ssh 経由での git 操作を様々な人およびシステムが行っています。

また、アクセス経路として以下のものがあります。

  • 社内ネットワークからのアクセス
  • 社外から VPN 接続した上でのアクセス (実質社内ネットワーク経由)
  • 認められた社外エンドポイント (元から AWS で運用しているサービスなど) からのアクセス

要約すると、インターネットに公開しているが IP 制限を行っているということです。 社外エンドポイントの制御はアプライアンスのロードバランサーにて行っています。 事前に登録されている IP, ポートならばアクセスを許可しています。

以上を AWS 環境でも実現する必要があります。

AWS におけるアクセス

AWS でのアクセス制御にはいくつかの戦略が考えられます。

  1. GitLab のインスタンスをパブリックサブネットに配置して、セキュリティグループと Nginx によりアクセス制御を行う。
  2. GitLab のインスタンスをプライベートサブネットに配置し、 https アクセスは ALB 経由、ssh アクセスは 踏み台経由で受け付け、アクセス制御はそれらのセキュリティグループにて行う。
  3. GitLab のインスタンスをプライベートサブネットに配置し、 https も ssh も CLB 経由で受け付け、アクセス制御は CLB のセキュリティグループにて行う。
  4. GitLab のインスタンスをプライベートサブネットに配置し、 https も ssh も NLB 経由で受け付け、アクセス制御はインスタンスのセキュリティグループで行う。

1 については、利用可能ですが採用したくありません。できればインスタンスはプライベートに置いておきたいです。 あと、証明書更新作業が発生します。正直なところ、採用したくない案ですが最終手段はこれになりそうです。

2 については、 https アクセスと ssh アクセスを同じドメインで行う、という前提が満たせないため採用できません。 踏み台にアクセスするための別ドメインが必要になります。 各クライアントが踏み台経由 ssh の設定を書かなければならないのもマイナスポイントです。 これから GitLab を新たに構築するのならありかもしれません。

3 は良い案に見えます。 CLB を使わなければならないところが欠点とはいえそうですが、さしたる問題がここでは見当たりません。 さらにいえば、 GitLab 公式ドキュメントでは CLB を利用した構築が解説されています。もっとも無難な案といえそうです。

4 は 3 と同じくらい良い案に見えますが、セキュリティグループをインスタンス側に持たせなければなりません。 ロードバランサーでアクセス元を選別し、インスタンス側は認められたアクセスだけを受け取る、という構図になりません。 また、性能などが要求されるわけではないのに、癖のある NLB をあまり利用したくはないです。 3 がだめなら検討する、といったところでしょうか。 (なんだか悪いサービスかのような書き方になってしまいましたが、単に今回の要件には過剰な高機能であるというだけです。 さらにいえば結局採用します :P)

NLB と CLB を比較する

案3と4のどちらを採用すべきかは、 NLB と CLB の機能の違いによって決まります。 公式の比較資料 とこれまでの議論を元に、今回関係がある部分を比較すると、以下の表ができます。

機能 NLB CLB
固定の Elastic IP アドレス O X (Global Accelerator も利用不可)
送信元 IP アドレスの保持 O X
ターゲット障害時の動作 すべてが unhealthy でない限り、healthy なターゲットにだけ流れる (記載なしだが常に InService なインスタンスにしか流れない)
セキュリティグループ O X
今後のアップデートの見込み O X
弊社 GitLab で利用した場合の予想価格 若干高い見込み 若干安い見込み

VPN の関係で固定 IP がどうしても必要

弊社において、この中でどうしても致命的になってしまう条件がありました。それは固定 IP です。 

弊社 VPN はアクセス過多による負荷を未然に防ぐため、許可された通信のみを VPN 経由でのアクセスとしています。 (そのため、リモートワーク開始直後に世間で見られた、負荷増大による VPN のダウンはまったくありませんでした。すばらしい!) この通信の許可が、通信先の IP によって行なわれています。

オンプレミス時代、社内ネットワークからの接続はインターネット経由ではなく、社内ネットワーク経由でした。 そのため、 VPN にさえ繋げば GitLab にアクセス可能でした。 しかし、クラウド移行後の GitLab は VPN に接続していてもインターネット経由です。そのため、GitLab のロードバランサーの IP を VPN 経由の通信先として許可しなければなりません。 すると、どうしても固定 IP が必要になります。

結論: NLB を使う

結果的に、NLB を使うこととなりました。

案1もありえなくはないですが、インスタンスをプライベートサブネットに配置できること、証明書管理の負担が軽減されることから不採用となりました。

構築時の注意点

TLS offload 時の gitlab.rb

TLS 終端をロードバランサーにて行う場合、external_url には https スキームの URL を書くが、Nginx では https アクセスを受け付けない、 という非直感的な設定を行う必要があります。すなわち以下のような設定です。

letsencrypt['enable'] = false

external_url = "https://your.gitlab.jp"
nginx['listen_port'] = 80
nginx['listen_https'] = false

registry_url = "https://your.registry.jp"
registry_nginx['listen_port'] = 80
registry_nginx['listen_https'] = false

GitLab は external_url のスキームが http か https かで動作が変わります。 https の場合は以下のことを行います。

  • 証明書を Let's Encrypt をつかって生成
  • 80 ではなく 443 をリッスンする

これらを防ぐため、スキームを http にしてしまうと、 https クローン用の URL が http 始まりになったり、 docker login ができない、などの問題が発生します。

そのため、external_url のスキームを https にしつつ、https をつかわないという設定が必要になります。 (ドキュメントをよく読めという話ではありますが)

インスタンスが一台の場合、クロスゾーン負荷分散が無効だと繋がったり繋がらなくなったりする

今回 GitLab は omnibus 構成のため、インスタンスを一台しか作成しないと思います。 その状況で、NLB のクロスゾーン負荷分散が無効の場合、インスタンスの無いサブネットにバランシングされた際になんのレスポンスも返さなくなります。 (例えば Chrome でアクセスすると ERR_EMPTY_RESPONSE と表示されます。)

この現象はインスタンスが healthy であっても発生します。 healthy なインスタンスのみのサブネットに必ずバランシングされる、ということはありません。

NLB は コンソール、CLI、API いずれから作成してもクロスゾーン負荷分散はデフォルト無効なので、明示的に有効にする必要があります。

(GitLab というより NLB 設定ミスの問題ですが、前述した gitlab.rb の設定がうまくいっていない状態でこの問題にハマると、原因究明に 非常に時間がかかります……。アクセスがうまくいくこともある、というのが厄介です)

あとがき

実は最初は特に何も考えず、公式ドキュメントと同じく CLB を使っていたのですが、固定 IP が使えない→ GA が使えない→仕方ないので NLB を使う、 という流れで NLB を使うことになりました。

GitLab のカスタマイズされた構築方法に関する情報は案外インターネット上になく、色々探り探りで構築していったのを覚えています。 特に、 gitlab.rb の件は最初設定項目だけ見てなんとなく書き換えれば動くだろうと思っていたら全く駄目で、よくよく調べたら 公式ドキュメントに書いてあった、というオチでした。

最終的に言いたいのは、別に NLB でも GitLab 普通に動いたよということです。ありがとうございました。