【超初心者向け】Cloud9にEC2へのデプロイ環境を構築し、共有する

Agency事業部にてアプリケーションエンジニアをしている山﨑です

今回は、EC2へのデプロイ環境を一元化するため、Cloud9上に環境構築を行った話です
AWSサービスやインフラにあまり触れたことのない方向けに、一連の手順を丁寧に記述してみました

背景

背景は、先日公開された松井さんの記事と同一です

定期的に発生する認証基盤やVPNの変更、PC交換によるマシンアーキテクチャの変更などによって、ローカル環境からのデプロイ作業が大きな影響を受けていました
そこで、とりあえず「ローカル環境からのデプロイ」をなくすことにしました

要件は下記です

  • AWS上の本番環境・検証環境に手動デプロイするための共有マシン環境を構築する
    • GitLab/GitHubをSSH経由で参照する
    • デプロイにDocker, Docker Composeを使う
    • 異なるVPCに存在するEC2インスタンスにSSH接続する
    • チーム内の複数のエンジニアにマシン環境を共有する

(余談) なぜCloud9にデプロイ環境を構築することにしたか

Docker環境の作成とGit管理は既に行っており、マシン・ネットワークの問題を解消すれば当面の課題は解決する状況でした
CD(継続的デプロイ)導入への繋ぎとして、簡単に課題を解決できるCloud9環境の構築を行うことにしました
事実、作業は1時間ほどで完了しました

おおまかな手順

  1. Cloud9環境を作成する
  2. GitLab/GitHub上のリポジトリをClone, Pullする
  3. ディスク容量を拡張する
  4. Docker, Docker Composeにてコンテナを立ち上げる
  5. デプロイ先のEC2インスタンスへのSSH通信を設定する
  6. 作成したCloud9環境を複数のユーザに共有する
  7. おまけ: Cloud9環境からAWSリソースへのアクセスを制限する

前提となる環境

AWS

  • ネットワーク環境*1は作成済み
  • デプロイ先のEC2インスタンスにパブリックIPを割り当て済み
  • AWS Single Sign-On (AWS SSO)が導入されている
  • 開発者はフェデレーションユーザとしてAWSマネジメントコンソールにアクセスする

その他

  • コードはGitリポジトリで保守してGitLab/GitHubにて管理している
  • Dockerfile, docker-compose.ymlにて実行環境作成済み

実装

【1】 Cloud9環境を作成する

Cloud9環境を作成します

AWS Cloud9 を開き、[環境を作成] を押下します

画面に従ってポチポチと作成していきます

Cloud9環境の[名前]、[説明]を設定します ※後から変更可能
環境タイプは「新しいEC2インスタンス」を選択します

インスタンスタイプを選択
必要なメモリ、CPUのサイズを選択します
小さめに選んでおいて、後で変更するのも有り

インスタンスタイプは t(汎用・安価), m(汎用), c(コンピューティング最適化) から選択可能です
常時稼働しなくて良いならtを選ぶのが無難です
参考) インスタンスタイプ - Amazon EC2 | AWS


プラットフォームを選択
Amazon Linux 2, Amazon Linux 2023, Ubuntu Server 22.04 LTSから選択可能です
Amazon Linux 2は2025年6月でサポートが終了するのでAmazon Linux 2023がおすすめです

ネットワーク設定 - 接続タイプを選択
どちらを選択しても動作は大差ありません
今回は社内統制の都合上SSHを選択しているが、AWS System Manager (SSM) の方がセキュアでおすすめです

AWS System Manager (SSM)の注意

  • 「既存のインスタンス」を用いてCloud9環境を構築する場合は選択不可
  • 特定のサブネットを選択したい場合は選択不可
  • サブネット設定の[パブリック IPv4 アドレスを自動割り当て]が「いいえ」の場合、パブリックIPアドレスが自動で割り当てられないことに注意
    (インターネットに接続できなくなる)


ネットワーク設定 - VPC設定
パブリックサブネット を選択
「パブリックサブネット」を選択すると、サブネット設定に関わらずパブリックIPアドレスは自動割り当てされます
サブネットを選択した場合、[接続タイプ]に「SSM」は選択できなくなることに注意してください

ちなみに プライベートサブネット を選択した場合
一部機能が制限されたり、アウトバウンドインターネットを有効にする必要*2が発生します

[作成] を押下します

[AWS Cloud9]コンソール > [自分の環境] > 作成した環境の名前から Cloud9 IDE [開く] を押下します

別ブラウザで Cloud9 統合開発環境(IDE) に遷移します
以降、このCloud9 IDE 画面上で作業を行います

最後に EC2 コンソールに遷移し、作成したCloud9環境の名前で検索してみると
aws-cloud9-のプリフィクスが付与された名前のEC2インスタンスが起動しているのが確認できます

Cloud9環境が作成できました!


 

【2】 GitLab/GitHub上のリポジトリをClone, Pullする

GitLab/GihubからリポジトリをCloneしたりPullしたりできるようにします
Pushは行いません

0. Gitインストール

Gitはデフォルトでインストールされています

1. SSH鍵の生成

SSH鍵を生成し、パーミッションを設定します

▶︎ SSH鍵を生成。「Ed25519」を使って作成するのがオススメ (クリックで開く)

# SSH鍵を生成。「Ed25519」を使って作成するのがオススメ
$ ssh-keygen -t ed25519
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/ec2-user/.ssh/id_ed25519): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/ec2-user/.ssh/id_ed25519.
Your public key has been saved in /home/ec2-user/.ssh/id_ed25519.pub.
The key fingerprint is:
SHA256:E8LjBBny+hoge+\fuga+\hoge+\fuga+\hoge+\fuga+\sample ec2-user@ip-10-99-0-99.ap-northeast-1.compute.internal
The key's randomart image is:
+--[ED25519 256]--+
|  . +oo...+o.    |
|   =.= +.+  ..   |
|  . * X o ...   .|
|   o B = ..o . o.|
|  . . + S .=. + .|
|   + o   .Eoo. . |
|  + +     ... .  |
|   =         o   |
| o+         .    |
+----[SHA256]-----+

▶︎ 生成した秘密鍵のパーミッションを適切に設定 (クリックで開く)

# 生成した秘密鍵のパーミッションを適切に設定
$ chmod 400 ~/.ssh/id_ed25519

# 確認
$ ll ~/.ssh
total 12
-rw------- 1 ec2-user ec2-user 991 Jul  3 12:03 authorized_keys
-r-------- 1 ec2-user ec2-user 464 Jul  3 13:26 id_ed25519
-rw-r--r-- 1 ec2-user ec2-user 137 Jul  3 13:26 id_ed25519.pub

2. 生成した公開鍵をGitLab/GitHubに登録

生成した公開鍵を取得し、GitLab/GitHubに登録することで git clonegit pull ができるようにします

▶︎ 生成した公開鍵を取得 (クリックで開く)

# 生成した公開鍵を取得
$ cat ~/.ssh/id_ed25519.pub
# => 生成した公開鍵が表示されるのでコピー


GitLabに登録
当該リポジトリ [Settings] > [Repository] > [Deploy Keys]

Pushは行わない予定なので、Grant write permissions to this key はオフのままにしておきます

GitHubに登録
当該リポジトリ [Settings] > [Deploy Keys] > [Add deploy key]

Pushは行わない予定なので、Allow write access はオフのままにしておきます

3. リポジトリをClone

$ git clone git@gitlab.com:sample-project/sample-repo
# => リポジトリがCloneされる

GitLab/GitHub上のリポジトリをClone, Pullすることができるようになりました!


 

【3】 ディスク容量を拡張する

Gitリポジトリに含まれたDockerfileからイメージをビルドする前に、ディスク容量を拡張します
デフォルトで用意されているのは 10GiB ですが、初期状態で空き容量は 3.2GB しかないので、拡張をおすすめします
※無料枠がある場合があるため拡張の上限を先に調べておきましょう

ちなみにこのディスク容量の拡張をやっておかないと、
次章 【4】 Docker, Docker Composeにてコンテナを立ち上げる にて容量不足で失敗することがあります

ディスク容量が不足すると、下記エラーが発生します

No space left on device

0. 現在のディスク容量を確認する

# 現在のディスク容量を確認
$ df -h
Filesystem        Size  Used Avail Use% Mounted on
devtmpfs          4.0M     0  4.0M   0% /dev
tmpfs             951M     0  951M   0% /dev/shm
tmpfs             381M  5.3M  376M   2% /run
/dev/nvme0n1p1     10G  6.9G  3.2G  69% /     <-- コレ
tmpfs             951M     0  951M   0% /tmp
/dev/nvme0n1p128   10M  1.3M  8.7M  13% /boot/efi
tmpfs             191M     0  191M   0% /run/user/1000

/dev/nvme0n1p1 がユーザが利用できるディスク。既に 69% 使用済み

1. EBSボリュームを拡張する

EBSボリュームの拡張はEC2コンソールにて行います
下記記事がとても詳しいです
Cloud9が容量不足? EBSボリュームを拡張する方法を解説します! | プログラミング入門ナビ by Proglus(プログラス)

ざっくり手順をまとめると下記です

  1. Cloud9を起動しているEC2インスタンスを停止
  2. 当該EC2インスタンスにアタッチされているボリュームの容量を変更
  3. Cloud9を起動しているEC2インスタンスを再起動

ディスク容量は増やすことしかできないため、慎重にサイズを決めましょう

今回は 20GiB に増やしました

2. ファイルシステムを拡張する

EBSボリュームを拡張しただけではディスク容量は増えません
増やしたいディスクを増やしていきます
公式ドキュメント | ボリュームサイズ変更後の Linux ファイルシステムの拡張 - Amazon Elastic Compute Cloud に沿って作業を進めます

(1) インスタンスが Xen ベースか Nitro ベースかを判断する

Xen インスタンスと Nitro インスタンスでは、デバイスとパーティションの命名が異なることに注意してください。(公式より)

下記コマンドを実行して確認します

$ aws ec2 describe-instance-types --instance-type t3.small --query "InstanceTypes[].Hypervisor"
[
    "nitro"
]

今回はNitroベースの場合を記述します

Xenベースのケースは、下記記事が詳しいです(再掲)
Cloud9が容量不足? EBSボリュームを拡張する方法を解説します! | プログラミング入門ナビ by Proglus(プログラス)

(2) パーティション拡張
Cloud9環境にはデフォルトでパーティションが存在するため、パーティションを拡張します

▶︎ パーティション拡張 (クリックで開く)

# ボリュームにパーティションがあるかどうかを確認
$ sudo lsblk
NAME          MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
nvme0n1       259:0    0  20G  0 disk 
├─nvme0n1p1   259:1    0  10G  0 part /          <-- コレ
├─nvme0n1p127 259:2    0   1M  0 part 
└─nvme0n1p128 259:3    0  10M  0 part /boot/efi
# => rootディレクトリの容量を拡張します
# => MOUNTPOINTSが "/" のパーティションが、拡張したいパーティション

# パーティション拡張
$ sudo growpart /dev/nvme0n1 1
CHANGED: partition=1 start=24576 old: size=20946911 end=20971487 new: size=41918431 end=41943007

# 確認
$ sudo lsblk
NAME          MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
nvme0n1       259:0    0  20G  0 disk 
├─nvme0n1p1   259:1    0  20G  0 part /          <-- コレ
├─nvme0n1p127 259:2    0   1M  0 part 
└─nvme0n1p128 259:3    0  10M  0 part /boot/efi
# => rootディレクトリがマウントされた "nvme0n1p1" が拡張されていることを確認


(3) ファイルシステム拡張
ファイルシステムを拡張します

▶︎ ファイルシステム拡張 (クリックで開く)

# ファイルシステムの確認
$ df -hT
Filesystem       Type      Size  Used Avail Use% Mounted on
devtmpfs         devtmpfs  4.0M     0  4.0M   0% /dev
tmpfs            tmpfs     951M     0  951M   0% /dev/shm
tmpfs            tmpfs     381M  5.3M  376M   2% /run
/dev/nvme0n1p1   xfs        10G  6.9G  3.2G  69% /     <-- コレ
tmpfs            tmpfs     951M     0  951M   0% /tmp
/dev/nvme0n1p128 vfat       10M  1.3M  8.7M  13% /boot/efi
tmpfs            tmpfs     191M     0  191M   0% /run/user/1000

# ファイルシステム拡張
$ sudo xfs_growfs -d /
meta-data=/dev/nvme0n1p1         isize=512    agcount=3, agsize=1047040 blks
         =                       sectsz=4096  attr=2, projid32bit=1
         =                       crc=1        finobt=1, sparse=1, rmapbt=0
         =                       reflink=1    bigtime=1 inobtcount=1
data     =                       bsize=4096   blocks=2618363, imaxpct=25
         =                       sunit=128    swidth=128 blks
naming   =version 2              bsize=16384  ascii-ci=0, ftype=1
log      =internal log           bsize=4096   blocks=16384, version=2
         =                       sectsz=4096  sunit=4 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0
data blocks changed from 2618363 to 5239803

# ファイルシステムが拡張されたことを確認
$ df -hT
Filesystem       Type      Size  Used Avail Use% Mounted on
devtmpfs         devtmpfs  4.0M     0  4.0M   0% /dev
tmpfs            tmpfs     951M     0  951M   0% /dev/shm
tmpfs            tmpfs     381M  5.3M  376M   2% /run
/dev/nvme0n1p1   xfs        20G  6.9G   14G  35% /     <-- コレ
tmpfs            tmpfs     951M     0  951M   0% /tmp
/dev/nvme0n1p128 vfat       10M  1.3M  8.7M  13% /boot/efi
tmpfs            tmpfs     191M     0  191M   0% /run/user/1000

ディスクが拡張できました!


 

【4】 Docker, Docker Composeにてコンテナを立ち上げる

Docker, Docker Compose環境を構築し、イメージをビルドしてコンテナを立ち上げます

1. Docker インストール, 自動起動の設定

(1) docker インストール Amazon Linux 2023 の場合
2023年12月現在、AL2023にはデフォルトで docker-24.0.5 がインストールされてます
インストールされていない場合は下記を参照してください

▶︎ Docker インストール(Amazon Linux 2023) (クリックで開く)

# リポジトリアップデート
$ sudo dnf update

# docker インストール
$ sudo dnf install -y docker

Amazon Linux 2 の場合

▶︎ Docker インストール(Amazon Linux 2) (クリックで開く)

# Amazon Linux Extras を使ってインストール
$ sudo amazon-linux-extras install -y docker


# バージョン確認
$ docker -v
Docker version 24.0.5, build ced0996

(2) dockerデーモン起動 & 自動起動設定

▶︎ Dockerデーモン起動, および自動起動を設定 (クリックで開く)

# docker デーモン起動
 $ sudo systemctl enable --now docker

# ステータス確認
$ systemctl status docker
● docker.service - Docker Application Container Engine
     Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; preset: disabled)
     Active: active (running) since Mon 2023-12-18 07:39:42 UTC; 11min ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
......
# => Activeが "active", Loadedが "enabled;" であることを確認

# 自動起動の設定を確認
$ systemctl is-enabled docker
enabled
# => "enalbed" ならOK


(3) dockerコマンドをsudoなしで叩けるようにする

▶︎ dockerコマンドをsudoなしで叩けるようにする (クリックで開く)

# dockerグループにec2-userを追加
$ sudo usermod -aG docker ec2-user

# dockerグループにec2-userが追加されていることを確認
$ grep docker /etc/group
docker:x:990:ec2-user

2. Docker Compose インストール

GitHub docker compose | リリース履歴 を確認して、適宜バージョンを選択してください
dockerと同じバージョンが無難です

▶︎ Docker Compose インストール (クリックで開く)

# docker-compose インストール
$ version=2.24.0
$ DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
$ mkdir -p ${DOCKER_CONFIG}/cli-plugins
$ curl -SL https://github.com/docker/compose/releases/download/v${version}/docker-compose-linux-x86_64 -o ${DOCKER_CONFIG}/cli-plugins/docker-compose

# 実行権限を追加
$ chmod +x ${DOCKER_CONFIG}/cli-plugins/docker-compose


# バージョン確認
$ docker compose -v
Docker version 24.0.5, build ced0996

3. Dockerイメージをビルド、コンテナ起動

CloneしたGitリポジトリに遷移し、有効なdocker-compose.ymlが存在することを確認して、下記コマンドを実行します

# イメージをビルド
$ docker compose build

# コンテナ起動
$ docker compose up -d

# コンテナに入る
$ docker compose exec app bash

Docker, Docker Compose環境の構築、およびイメージのビルドが完了しました!


 

【5】 デプロイ先のEC2インスタンスへのSSH通信を設定する

異なるVPC上に存在するEC2インスタンスへのSSH通信ができるようにします

1. ElasticIP(EIP)を作成

EC2 コンソールを開き、[インスタンス] 画面にてCloud9インスタンスを選択し、「パブリックIPv4アドレス」が設定されていることを確認します
※割り当てられていない場合、Cloud9インスタンスがパブリックサブネット上に配置されていることを確認してください

左ペインから [ネットワーク&セキュリティ] > [ElasticIP] 画面に遷移します

[Elastic IP アドレスを割り当てる] を選択します

[割り当て] 押下します

EIPが作成されたことを確認します

2. 作成したEIPをCloud9インスタンスにアタッチ

[ElasticIP] 画面にて
作成したEIPを選択し、 [アクション] > [Elastic IP アドレスの関連付け] を押下します

Cloud9インスタンス、プライベートIPアドレスを選択します

[関連づけられたインスタンス]列にCloud9インスタンスが表示されることを確認します

3. 作成したEIPを、接続先のセキュリティグループのインバウンドに追加

EC2 コンソール > [インスタンス] 画面にて、接続先のEC2インスタンスを選択し、[セキュリティ] タブ内 [セキュリティグループ] を押下します

[セキュリティグループ詳細画面]にて、[インバウンドのルールを編集] を押下します

左下にある [ルールを追加] を押下し、下記を設定して、[ルールを保存] を押下します - タイプ: SSH - ソース: 作成したEIP /32 - 説明: (説明を記載)

4. 対象のEC2インスタンスにSSH接続できることを確認

  1. 対象EC2インスタンスのプライベートキーファイル*3 をローカルに保存
  2. ローカルに保存したプライベートキーファイルをCloud9にアップロード

ローカルのエクスプローラを開き、ドラッグ&ドロップでアップロードできます

パーミッションを変更します

$ chmod 400 <アップロードしたプライベートキーファイル>
  1. 接続確認
$ ssh ec2-user@<当該EC2のパブリックIPアドレス> -i <アップロードしたプライベートキーファイル>

接続できたら完了!

アップロードしたプライベートキーファイルは ~/.ssh 配下に移動しておくと無難です


 

【6】 作成したCloud9環境を複数のユーザに共有する

環境が作成できたので、他のユーザに共有します

弊社ではAWS Single Sign-On (AWS SSO)が導入されています
今回この環境を共有したいのは、AWSCloud9Administrator権限を持つフェデレーションユーザ(フェデレーティッドユーザ)です
公式ドキュメント | AWS Cloud9 で共有環境を使用する - AWS Cloud9を参考に、ユーザを招待していきます

1. フェデレーションユーザのARNを生成

公式ドキュメントによると、フェデレーションユーザを招待するには下記ARNの生成が必要です

arn:aws:sts::123456789012:assumed-role/MyAssumedRole/MyAssumedRoleSession

123456789012 を AWS アカウント ID に、MyAssumedRole を引き受けたロールの名前に置き換えます。
MyAssumedRoleSession を引き受けたロールのセッション名に置き換えます。

AWSアカウントID、および MyAssumedRole/MyAssumedRoleSession は、AWSマネジメントコンソールの右上、アカウントメニューのプルダウンから確認できます

確認したAWSアカウントID、フェデレーティッドユーザを元にARNを生成します

2. 権限追加

  1. Cloud9 IDE 右上にある [share]押下
  2. [Invite Members] のボックスにフェデレーションユーザのARNを入力、権限は「RW」を指定して[Invite]押下

  3. 下記WARNINGが表示されるので[OK]押下

  4. 登録完了
    登録した権限を確認します

3. URL共有

環境を作成したユーザ以外のユーザは、AWSマネジメントコンソール経由ではなく、共有用URLからのアクセスになります
URLは、[Share]押下で表示されるモーダルからコピーできます

作成したCloud9環境を複数のユーザに共有しました!


 

おまけ: 【7】 Cloud9環境からAWSサービスへのアクセスを制限する

Cloud9には、 AWSマネージド一時認証情報(AWS Managed Temporary Credentials: AMTC) という認証サービスがあります
公式ドキュメント | AWS Cloud9 での Identity and Access Management - AWS Cloud9

AWSサービスへのアクセス時に利用できる一時認証情報で、Cloud9を起動するたびに、~/.aws/credentials に認証情報を自動生成・更新してくれます
この認証では、(ごく一部を除き)すべてのAWSリソースのすべてのAWSアクションに許可が与えられます

Cloud9 IDEを起動するたびに下記モーダルが表示される場合、この認証サービスが有効になっています
Force updateを押下すると、~/.aws/credentialsが強制的に上書きされてしまいます

この広すぎる権限を抑制する方法を記述します

1. IAMロールをアタッチする

適切な権限を付与したIAMロールをCloud9インスタンスにアタッチすることで、権限を抑制することができます

詳細は、松井さんが書いた下記の記事を参考にしてください(再掲)

2. AWS Managed Temporary Credentials をオフにする

※この手順は、Cloud9環境作成者のみが実行できます

  1. Cloud9 IDE にて、左上の雲マーク > [Preferences] を押下

  2. [AWS Settings] を押下
    [AWS management temporary credentials]がONであることを確認

  3. [AWS management temporary credentials]をOFFにする

3. ~/.aws/credentials 削除

AWS認証には優先順位があり、~/.aws/credentials はIAMロールより優先して参照されてしまいます
公式ドキュメント | 認証とアクセス認証情報 - AWS Command Line Interface

邪魔な ~/.aws/credentials を削除します

$ rm ~/.aws/credentials

以上で、AWSサービスへのアクセス時にAMTCではなく、IAMロールを参照するようになります

下記記事も大変参考にさせていただきました
AWS Cloud9環境で利用できる一時クレデンシャル『AWS Managed Temporary Credentials』について調べてみた | DevelopersIO


まとめ

Cloud9上に共有環境を構築したことで、個別のネットワーク環境はマシン環境に依存することなく、安定的にアプリケーションをデプロイすることができるようになりました

Cloud9上に基礎的な環境を構築する手段を知っておくことで、ペアプロ環境や検証環境などの構築が気軽に行えるようになります
ぜひ試してみてください

ここまでお読みいただいてありがとうございました

*1:VPC, パブリックサブネット, セキュリティグループ

*2:公式ドキュメント | AWS Cloud9 開発環境の VPC 設定 - AWS Cloud9

*3:プライベートキーファイルを紛失した場合、再発行の必要あり 手順