最近、AWS を学んでいるのですが、個人的に最高なのが JAWS-UG CLI専門支部 です。波田野さん の説明聴きながら、Cloud9 コンソール上で AWS CLI 叩くの楽しいです。
個人的に AWS Cloud9 に興味を持ちましたので、最初のセットアップの手順を自分なりに追ってみました。これはその自分メモです。
今回の対象
JAWS-UG CLI専門支部 イベントのどれか、例えば VPCの回 の「AWS CLIハンズオンの環境構築手順」欄で紹介されている以下の手順資料が元ネタです。
この手順自体はすごく判り易いので、このなかでメインの部分である以下のページを主に調べます。
1.1. Cloud9環境の構築 (handson-cloud9-env)
この手順で何がどう準備されているのか、それを理解するための自分メモです。
AWS Cloud9 環境の構築
手順のなかで Cloud9 を作成する際、入力した値は以下の3つだけでした。他はデフォルト値のまま。
Name: handson-cloud9-env
Network (VPC): (Cloud9用に作成したVPCを選択する)
Subnet: (Cloud9用に作成したVPCのサブネットを選択する)
こんな簡単にあの便利な環境が作成され、EC2 への SSH 接続や AWS CLI のログインまで自動実行されるなんて、素晴らしいです。内部ではどんな作業が実施されているのでしょうか?
CloudFormation を使っていた
AWS マネージメントコンソールを眺めていたところ、CloudFormation に見慣れないスタックが作成されているのを見つけました。名前からして、これ Cloud9 環境のセットアップ用のスタックではないでしょうか?
さっそくテンプレートを眺めてみます。JSON 形式だと読みにくいので、「デザイナーで表示」して YAML 形式に変換したものが以下です。
Resources:
Instance:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: ami-0884ffec9680b66a1
InstanceType: t2.micro
UserData: >-
XXXX 内容省略 XXXX
Tags:
- Key: Name
Value: aws-cloud9-handson-cloud9-env-84xxxxd2
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeviceIndex: 0
SubnetId: subnet-0fxxxx5d
GroupSet:
- !Ref InstanceSecurityGroup
InstanceSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: >-
Security group for AWS Cloud9 environment
aws-cloud9-handson-cloud9-env-84xxxxd2
VpcId: vpc-03xxxx14
SecurityGroupIngress:
- FromPort: 22
ToPort: 22
IpProtocol: tcp
CidrIp: 18.179.48.128/27
- FromPort: 22
ToPort: 22
IpProtocol: tcp
CidrIp: 18.179.48.96/27
Tags:
- Key: Name
Value: aws-cloud9-handson-cloud9-env-84xxxxd2
EC2 インスタンスとセキュリティグループを作成しているのがわかります。EC2 で使用している AMI の ami-0884ffec9680b66a1 は、「Cloud9Default-2020-06-02T07-52」というAMI名のようです。
セキュリティグループで追加している2つの IP Address が謎です。EC2 の Public IP とも違うようだし…
ググってみたら AWS IP Address Ranges というサイトがあって、どうやら東京リージョンの Cloud9 用に決まっているアドレスみたいです。
関係ないですが、コンソール上で aws コマンドを使ったら、何故かエラーになったので aws config コマンドでリージョンを ap-northeast-1 を設定しました。
ユーザーデータの bash スクリプト
EC2 インスタンスを作成するうえで大事なのは AMI イメージの選択、そしてそれをカスタマイズするためのユーザーデータですよね。上記の CloudFormation テンプレートでは UserData 欄が Base64 エンコードされていて読めなかったので、デコードして内容を確認してみます。
まず最初のほうはこんな感じ。
#!/bin/bash
UNIX_USER="ec2-user"
UNIX_USER_HOME="/home/ec2-user"
ENVIRONMENT_PATH="/home/ec2-user/environment"
UNIX_GROUP=$(id -g -n "$UNIX_USER")
# add SSH key
install -g "$UNIX_GROUP" -o "$UNIX_USER" -m 755 -d "$UNIX_USER_HOME"/.ssh
cat <<'EOF' >> "$UNIX_USER_HOME"/.ssh/authorized_keys
# Important
# ---------
# The following public key is required by Cloud9 IDE
# Removing this key will make this EC2 instance inaccessible by the IDE
#
cert-authority ssh-rsa AAXXXX 内容省略 XXXXbw== 84xxxxd2@cloud9.amazon.com
#
# Add any additional keys below this line
#
EOF
最初は基本的な環境変数の設定をして、それから SSH 用のパブリックキーを格納したファイルを追加していますね。作成されたファイルをコンソール上で確認しておきましょう。
その先は特に変わったところは無いようにおもわれます。起動関係のコマンドは sudo パスワード不要にしているあたりがポイントですかね。
# allow automatic shutdown
echo "$UNIX_USER ALL=(ALL) NOPASSWD: /sbin/poweroff, /sbin/reboot, /sbin/shutdown" >> /etc/sudoers
ln -s /opt/c9 "$UNIX_USER_HOME"/.c9
chown -R "$UNIX_USER":"$UNIX_GROUP" "$UNIX_USER_HOME"/.c9 /opt/c9
install -g "$UNIX_GROUP" -o "$UNIX_USER" -m 755 -d "$ENVIRONMENT_PATH"
そして ~/.bashrc の定義部分は長いですねー。ただヒアドキュメント形式なので、そのまま内容を読めるのが救いではあります。
if [ "$ENVIRONMENT_PATH" == "/home/ec2-user/environment" ] && grep "alias python=python27" "$UNIX_USER_HOME"/.bashrc; then
cat <<'EOF' > "$UNIX_USER_HOME"/.bashrc
# .bashrc
export PATH=$PATH:$HOME/.local/bin:$HOME/bin
# load nvm
export NVM_DIR="$HOME/.nvm"
[ "$BASH_VERSION" ] && npm() {
# hack: avoid slow npm sanity check in nvm
if [ "$*" == "config get prefix" ]; then which node | sed "s/bin\/node//";
else $(which npm) "$@"; fi
}
# [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm
rvm_silence_path_mismatch_check_flag=1 # prevent rvm complaints that nvm is first in PATH
unset npm # end hack
# User specific aliases and functions
alias python=python27
# modifications needed only in interactive mode
if [ "$PS1" != "" ]; then
# Set default editor for git
git config --global core.editor /usr/bin/nano
# Turn on checkwinsize
shopt -s checkwinsize
# keep more history
shopt -s histappend
export HISTSIZE=100000
export HISTFILESIZE=100000
export PROMPT_COMMAND="history -a;"
# Source for Git PS1 function
if ! type -t __git_ps1 && [ -e "/usr/share/git-core/contrib/completion/git-prompt.sh" ]; then
. /usr/share/git-core/contrib/completion/git-prompt.sh
fi
# Cloud9 default prompt
_cloud9_prompt_user() {
if [ "$C9_USER" = root ]; then
echo "$USER"
else
echo "$C9_USER"
fi
}
PS1='\[\033[01;32m\]$(_cloud9_prompt_user)\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]$(__git_ps1 " (%s)" 2>/dev/null) $ '
fi
EOF
chown "$UNIX_USER":"$UNIX_GROUP" "$UNIX_USER_HOME"/.bashrc
fi
~/.local/bin と ~/bin を PATH に含めているのがポイントでしょうか。
その後の load nvm ハックのあたりは NVM getting very slow on startup in Bash あたりとか、ネットにいろいろ情報があるので、有名な問題っぽいですが、今回はスルーで。
その後はプロンプト設定してくれたり、環境整えてくれているようですね。Python がまだ ver2 なんだー、とか思ってみたり。
続いて、同じくヒアドキュメントによる ~/environment/README.md ファイルの作成ですね。シェル起動時に表示されるアレ?ですかね。
if [ "$ENVIRONMENT_PATH" == "/home/ec2-user/environment" ] && [ ! -f "$ENVIRONMENT_PATH"/README.md ]; then
cat <<'EOF' >> "$ENVIRONMENT_PATH"/README.md
___ ______ ____ _ _ ___
/ \ \ / / ___| / ___| | ___ _ _ __| |/ _ \
/ _ \ \ /\ / /\___ \ | | | |/ _ \| | | |/ _` | (_) |
/ ___ \ V V / ___) | | |___| | (_) | |_| | (_| |\__, |
/_/ \_\_/\_/ |____/ \____|_|\___/ \__,_|\__,_| /_/
-----------------------------------------------------------------
Hi there! Welcome to AWS Cloud9!
To get started, create some files, play with the terminal,
or visit https://docs.aws.amazon.com/console/cloud9/ for our documentation.
Happy coding!
EOF
chown "$UNIX_USER":"$UNIX_GROUP" "$UNIX_USER_HOME"/environment/README.md
fi
そして最後は、Cloud9 が自動終了される際に実行されるスクリプトを作成して登録しているようです。
# Fix for permission error when trying to call `gem install`
chown "$UNIX_USER" -R /usr/local/rvm/gems
#This script is appended to another bash script, so it does not need a bash script file header.
UNIX_USER_HOME="/home/ec2-user"
C9_DIR=$UNIX_USER_HOME/.c9
CONFIG_FILE_PATH="$C9_DIR"/autoshutdown-configuration
VFS_CHECK_FILE_PATH="$C9_DIR"/stop-if-inactive.sh
echo "SHUTDOWN_TIMEOUT=30" > "$CONFIG_FILE_PATH"
chmod a+w "$CONFIG_FILE_PATH"
echo -e '#!/bin/bash
set -euo pipefail
CONFIG=$(cat '$CONFIG_FILE_PATH')
SHUTDOWN_TIMEOUT=${CONFIG#*=}
if ! [[ $SHUTDOWN_TIMEOUT =~ ^[0-9]*$ ]]; then
echo "shutdown timeout is invalid"
exit 1
fi
is_shutting_down() {
is_shutting_down_system_d &> /dev/null || is_shutting_down_init_d &> /dev/null
}
is_shutting_down_system_d() {
local TIMEOUT
TIMEOUT=$(busctl get-property org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager ScheduledShutdown)
if [ "$?" -ne "0" ]; then
return 1
fi
if [ "$(echo $TIMEOUT | awk "{print \$3}")" == "0" ]; then
return 1
else
return 0
fi
}
is_shutting_down_init_d() {
pgrep shutdown
}
is_vfs_connected() {
pgrep vfs-worker >/dev/null
}
if is_shutting_down; then
if [[ ! $SHUTDOWN_TIMEOUT =~ ^[0-9]+$ ]] || is_vfs_connected; then
sudo shutdown -c
fi
else
if [[ $SHUTDOWN_TIMEOUT =~ ^[0-9]+$ ]] && ! is_vfs_connected; then
sudo shutdown -h $SHUTDOWN_TIMEOUT
fi
fi' > "$VFS_CHECK_FILE_PATH"
chmod +x "$VFS_CHECK_FILE_PATH"
echo "* * * * * root $VFS_CHECK_FILE_PATH" > /etc/cron.d/c9-automatic-shutdown
いやぁ、思ったより長かったですね。
README.md は更新が多そうだからユーザーデータで生成・更新するのはわかるのですが、最後のスクリプトはどうしてここで作成しているのか謎ですね… いろいろ改善中なのですかねー。
というわけで
今回は AWS Cloud9 サービス作成時の CloudFormation テンプレートと、その中に設定されていたユーザーデータ、bash スクリプトを眺めてみました。
本当は AMI イメージのほうも眺めて、インストール済みのサービスや起動設定など眺めたいところですが。それはまた時間のあるときに。
というわけで、本当に自分しか見ないメモになった気もしますが… 一応公開しておきますね。いつか誰かのお役に立てば幸いです。
それではまた!