更新日:2025年8月22日
22分で読めます
GitLab CI の多様性とパワーをAWS(S3)を例に学び、デプロイに活かせる開発力を身に着けましょう。
いくつかのシナリオを通じて、[GitLab
CI](https://about.gitlab.com/ja-jp/solutions/continuous-integration/)
が持つ多様性と強みをご紹介します。
この投稿は、ある架空のニュースポータルのサクセスストーリーです。あなたは、そのポータルの所有者かつエディターで、唯一の開発者でもあります。プロジェクトコードは既に GitLab.com でホストされており、GitLab CI/CD でテストを実行する (英語版)こともできます。ここであなたは考えます。これをデプロイに使う (英語版)ことはできないだろうかと。そして、どこまで活用できるのだろう、と。
このサクセスストーリーを技術スタックに依存しないものにするために、このアプリは単なるHTML ファイルを集めたものだと仮定しましょう。サーバー側のコードも、高度な JavaScript のコンパイルもないものとします。
デプロイ先のプラットフォームも単純なものにしましょう。ここでは、「Amazon S3」を使います。
この記事の目的は、コピー&ペースト可能なスニペットを数多く紹介することではありません。GitLab CI の基本原理や各種機能をご紹介し、それを現場の技術スタックに簡単に適用できるようにすることが目的です。
それでは、はじめましょう。このストーリーには、まだ、継続的インテグレーション (CI) は登場しません。
デプロイ:今回、「デプロイ」とは、一連の HTML ファイルが (静的な Web サイトホスティング 用に設定済み) S3 バケット上に表示されることを意味します。
これを実現する方法は無数にあります。ここでは、Amazon から提供されている awscli ライブラリ (英語版) を使います。
コマンドは、全体ではこのようになります。
aws s3 cp ./ s3://yourbucket/ --recursive --exclude "*" --include "*.html"
リポジトリへのコードのプッシュと、デプロイは別々のプロセスです。
重要なポイント:このコマンドは、2 つの環境変数AWS_ACCESS_KEY_ID
と AWS_SECRET_ACCESS_KEY
の指定をコードデベロッパーが行なうものと想定しています。また、
重要なポイント:このコマンドは、2 つの環境変数 AWS_ACCESS_KEY_ID と AWS_SECRET_ACCESS_KEY の指定をコードデベロッパーが行なうものと想定しています。また、AWS_DEFAULT_REGION
も指定する必要があるかもしれません。
では、GitLab CI を使って自動化してみましょう。
GitLab では、どのコマンドを実行しても違いはありません。GitLab CI はユーザーの具体的なニーズに合わせて、あたかもコンピュータ上のローカルターミナルで操作している感覚でセットアップできます。ここで実行するコマンドをGitLabにも実行させるよう、CI に指定可能です。 .gitlab-ci.yml
ファイルにスクリプトを記述し、コードをプッシュするだけです。これで、CI がジョブをトリガーし、指定コマンドが実行されます。
では、ここでストーリーにもう少し肉付けをしましょう。私たちの Web サイトは小規模で、1日あたり 20~30 人の訪問者がいるだけです。コードリポジトリにはmain
という1つのデフォルトブランチ (があるものとします。
それでは、 .gitlab-ci.yml
ファイルに、先程のコマンドを使ったジョブを指定することから始めましょう。
deploy:
script: aws s3 cp ./ s3://yourbucket/ --recursive --exclude "*" --include "*.html"
うまくいかないようです:
実行ファイルにはaws
が必要です。これを確認するのは、私たちのジョブです。awscli
をインストールするには、pipが必要です。これは、Pythonパッケージのインストール用のツールです。では、Pythonが事前にインストール済みのDockerイメージを指定しましょう。これにはpip
が含まれているはずです。
deploy:
image: python:latest
script:
- pip install awscli
- aws s3 cp ./ s3://yourbucket/ --recursive --exclude "*" --include "*.html"
GitLab にコードをプッシュすると、CI により自動的にデプロイされます。awscli のインストールによりジョブの実行時間が伸びますがこの時点ではそれほど問題ではありません。プロセスを高速化する場合、awscliが事前にインストールされた Docker イメージを探す (英語版) ことが可能です。または、イメージをご自身で作成しても構いません。
ここで、AWS コンソール (英語版) から取得した環境変数を忘れないでください。
variables:
AWS_ACCESS_KEY_ID: "AKIAIOSFODNN7EXAMPLE"
AWS_SECRET_ACCESS_KEY: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
deploy:
image: python:latest
script:
- pip install awscli
- aws s3 cp ./ s3://yourbucket/ --recursive --exclude "*" --include "*.html"
今回は上手くいくはずですが、シークレットキーをオープンにしたままにすることは、プライベートリポジトリであってもよくありません。ではどうすればよいのか、もう少し見てみましょう。
GitLab にはシークレット変数用に特別な項目があります: Settings(設定) > CI/CD > Variables(変数)
ここに入力したものすべてが環境変数になります。「Visibility(表示レベル)」の「Masked(マスクする)」ラジオボタンをオンにすると、ジョブログ内で変数がマスクされます。「Protect variable(変数の保護)」チェックボックスにチェックを入れると、変数は保護されているブランチやタグ上で実行されているパイプラインにのみ、エクスポートされます。プロジェクトの「オーナー」や「メンテナー」の権限を持つユーザーが、このセクションにアクセスできます。
CI 設定から、variables セクションを削除することも可能ですが、別の目的で使用してみましょう。
構成が大きくなる場合、初期段階で、いくつかのパラメータを変数として設定しておくと便利です。これは特に、パラメータを複数の場面で使用する場合に便利です。今回のケースではまだそのような状況ではありませんが、デモとして、S3 バケット名を variable に設定してみましょう。
variables:
S3_BUCKET_NAME: "yourbucket"
deploy:
image: python:latest
script:
- pip install awscli
- aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude "*" --include "*.html"
ここまで、順調ですね。
この架空シナリオでは、Web サイトの訪問者数が増えたため、開発者を雇いました。これでチームができました。チームワークにより、GitLab CI ワークフローがどのように変化するのか、見てみましょう。
同一のリポジトリで 2 人が作業をするようになったため、開発に main ブランチを使うのは得策ではなくなりました。そこで、新規機能や新規記事ごとに異なるブランチを使うことにし、準備ができたら、main ブランチにマージする、ということに決めました。
ここで問題があります。現行の CI 設定は、ブランチをまったく考慮していない、という点です。GitLab に何かをプッシュするたびに、S3 にデプロイされてしまいます。
この問題は簡単に回避できます。deploy するジョブに、only: main を追加するだけです。
本番の Web サイトに、すべてのブランチをデプロイしたくありませんが、フィーチャーブランチからの変更点を何らかの方法でプレビューできたら嬉しいですね。
最近雇った人 (ここでは「パトリック」と呼びましょう) が、GitLab には GitLab Pages (英語版) という機能がある、と教えてくれました。作業中のコードをプレビューする場所にもってこいです。
GitLab Pagesで Web サイトをホストする (英語版) には、CI 設定ファイルが 次の3 つのシンプルなルールを満たしている必要があります。
ジョブはpagesと名付けなければならない
artifacts セクションがあり、その中に public フォルダを作成しなければならない
ホストしたいすべてのファイルは、このpublic フォルダ内に置かなければならない
public フォルダの中身は、http://
プレーン HTML Web サイト用の設定例を適用した後は、CI設定全体はこのようになります。
variables:
S3_BUCKET_NAME: "yourbucket"
deploy:
image: python:latest
script:
- pip install awscli
- aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude "*" --include "*.html"
only:
- main
pages:
image: alpine:latest
script:
- mkdir -p ./public
- cp ./*.html ./public/
artifacts:
paths:
- public
except:
- main
2 つのジョブを指定しました。1つは、顧客用に Web サイトを S3 にデプロイします (deploy)。もう1つのジョブ (pages) は、Web サイトを GitLab Pages にデプロイします。2 つのジョブは、それぞれ「本番環境」と「ステージング環境」と呼びます。
マスター以外の全ブランチが GitLab Pages にデプロイされます。
GitLab は 環境へのサポート (英語版) (動的環境および静的環境を含む) を提供しているため、ユーザーは、各デプロイジョブに対応する環境を指定するだけで済みます。
variables:
S3_BUCKET_NAME: "yourbucket"
deploy to production:
environment: production
image: python:latest
script:
- pip install awscli
- aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude "*" --include "*.html"
only:
- main
pages:
image: alpine:latest
environment: staging
script:
- mkdir -p ./public
- cp ./*.html ./public/
artifacts:
paths:
- public
except:
- main
GitLab はユーザーのデプロイを追跡するため、サーバー上で何がデプロイされているのかを常に把握できます。
GitLab は現在の環境のそれぞれについて、デプロイの完全履歴を提供してくれます。
すべてが自動化され、セットアップも完了です。これで、新しい課題にチャレンジする準備が整いました。
また、同じことが起きました。ステージング環境で自分のフィーチャーブランチをプレビューするためにプッシュした 1 分後、パトリックが彼のブランチをプッシュしたのです。ステージング環境はパトリックの作業内容で上書きされてしまいました。大変です。今日で 3 回目です。
そこで、アイデアが浮かびました。Slackを使ってデプロイを通知するようにすれば、デプロイが完了した時に、コンテンツを別の人がプッシュしてしまうことがなくなります。
時は過ぎ、Web サイトの人気は非常に上がり、チームも 2 人から 8 人に増えました。チームメンバーは同時進行で開発を行うため、「ステージング」でプレビューを待ち合うことが多くなってきました。「ステージングに対してすべてのブランチをデプロイする」という方針はうまくいかなくなってしまったのです。
もう一度、プロセスを見直す時がきました。誰かがステージングサーバー上でコードへの変更内容を確認したいときは、まず「ステージング」ブランチに変更内容をマージする、ということにチームで同意しました。
.gitlab-ci.yml への変更は最小限に抑えられます。
except:
- main
を次のように変更します:
only:
- staging
ステージングサーバー上でプレビューを行なう前に、フィーチャーブランチをマージしなければなりません。
もちろん、これによりマージに追加の時間や労力がかかりますが、待機するよりは良い、ということで全員が同意しました。
すべてを制御することは不可能です。そのため、時として何かがうまくいかないこともあります。誰かがブランチを誤った方法でマージしてしまい、あなたのサイトが HackerNews のトップに載ったタイミングで、本番環境に直接プッシュしてしまいました。何千人もの人が、輝かしいメインページの代わりに、完全に崩れたレイアウトを目撃してしまったのです。
しかし幸運なことに、「ロールバック」ボタンを見つけた人がいたため、問題発覚 1 分後に、Web サイトは修正されました。
「ロールバック」により、1つ前のコミットでの1つ前のジョブが再起動されます
とにかく、この問題に対応する必要があると感じたため、本番環境への GitLab 自動デプロイを停止して、手動デプロイに切り替えることにしました。そのためには、ジョブに when: manual を追加する必要があります。
予想通り、その後は「本番環境」への自動デプロイは行なわれなくなりました。手動でデプロイするには、CI/CD > Pipelines(パイプライン) に移動し、下図にあるボタンをクリックします。
時間を早送りしましょう。ついに、あなたの組織は法人化されました。Web サイトに取り組んでいる人も数百人になり、これまでの妥協策はもう通用しません。
次にすべき論理的なステップは、レビュー用に、フィーチャーブランチごとにアプリケーションの一時インスタンスを起動することでしょう。
ここでは、そのために S3 上に別のバケットをセットアップします。ただ一つ違うところは、開発ブランチの名前で Web サイトのコンテンツを「フォルダ」にコピーする点です。URL は次のようになります。
http://<REVIEW_S3_BUCKET_NAME>.s3-website-us-east-1.amazonaws.com/
前に使った pages ジョブを次のコードで置き換えます。
review apps:
variables:
S3_BUCKET_NAME: "reviewbucket"
image: python:latest
environment: review
script:
- pip install awscli
- mkdir -p ./$CI_BUILD_REF_NAME
- cp ./*.html ./$CI_BUILD_REF_NAME/
- aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude "*" --include "*.html"
興味深いのは、$CI_BUILD_REF_NAME という変数がどこからきたか、という点です。GitLab は 多くの環境変数 (英語版) があらかじめ定義されています。そのため、ジョブではすぐ変数が使えます。
ここでご注意いただきたいのは、S3_BUCKET_NAME 変数はジョブ内で定義している、ということです。トップレベルの定義を再定義するとき、これを行ないます。
この構成を視覚的にわかりやすく描くと、次のようになります。
「Review Apps」の実装の詳細は、実際に現場で使っている技術スタックやデプロイプロセスなどにより大きく異なります。そのため、このブログ記事では詳細をカバーできません。
現実は、この静的な HTML Web サイトのように簡単ではないのです。例えば、あるインスタンスを一時インスタンスにして、必要なソフトウェアやサービスを即座に、自動的に起動させることは簡単な作業ではありません。しかし、特に Docker コンテナ、または Chef や Ansible を使えば実現可能です。
Docker を使ったデプロイについては、今後のブログ記事で取り上げます。GitLab デプロイプロセスをシンプルな HTML ファイルのコピーに単純化し、より複雑なシナリオにしなかったことに対して、少々後めたい気持ちがあります。複雑なシナリオを知りたい方は、「Building an Elixir Release into a Docker image using GitLab CI (英語版のみ:GitLab CI を使用して、Elixir リリースを Docker イメージにビルドする)」 をご覧ください。
さて、記事に戻ります。最後の点について、お話しましょう。
現実は、S3 や GitLab Pages だけに限定されるものではありません。GitLab はまざまなサービスにアプリやパッケージをホストし、デプロイしています。
さらに、ある時点で、新しいプラットフォームへの移行を決定し、デプロイスクリプトをすべて書き直す必要が生じるかもしれません。そういった場合のダメージを最小限に抑えるために、dpl という Gem を使えます。
上の例では、サービス (Amazon S3) へのコードのサンプルツールとして awscli を使用しました。しかし、どのツールを使っても、また、デプロイ先のシステムに何を選んでも、基本原則は変わりません。いくつかのパラメータでコマンドを実行し、何らかの方法で認証用にシークレットキーを渡すということです。
dpl のデプロイツールは、この基本原則に従い、このプロバイダーのリストに対して統一されたインターフェイスを提供します。
こちらが、dplを使用した場合の、本番デプロイジョブの例です。
variables:
S3_BUCKET_NAME: "yourbucket"
deploy to production:
environment: production
image: ruby:latest
script:
- gem install dpl
- dpl --provider=s3 --bucket=$S3_BUCKET_NAME
only:
- main
異なるシステムにデプロイする場合、またはデプロイ先のプラットフォームを頻繁に変更する場合には、デプロイ用スクリプトが統一されるように dplを使うことを検討してください。
GitLab での CI デプロイについて、CI/CD AWS を使って説明してきました。GitLab AWS デプロイについて役立つ知識が得られたでしょうか。これまで学んできたポイントをまとめると、次のようになります。
デプロイとは、定期的に実行される、単一の (もしくは一連の) コマンドにすぎません。このため、デプロイは GitLab CI 内で実行できます。
ほとんどの場合、実行するコマンドに対していくつかの (もしくは単一の) シークレットキーを指定する必要があります。指定するシークレットキーは、Settings (設定)> CI/CD > Variables(変数) に格納します。
GitLab CI では、デプロイ先のブランチを柔軟に指定できます。
複数の環境にデプロイする場合、GitLab はデプロイ履歴を保持します。そのため、任意の前バージョンにロールバックできます。
インフラストラクチャの重要な部分については、GitLab 自動デプロイの代わりに、GitLab インターフェイスからの手動デプロイを有効化できます。
監修:小松原 つかさ @tkomatsubara
(GitLab合同会社 ソリューションアーキテクト本部 シニアパートナーソリューションアーキテクト)