こんにちは、近藤(りょう)です!
以前、プライベートサブネットに配置した AWS Cloud9 をセットアップする 記事を書きましたが、Cloud9 の新規利用が制限されたことで、今後 Cloud9 を利用する選択肢を見直す必要があるかもしれません。
blog.serverworks.co.jp
AWS Cloud9 を利用していたハンズオン手順や、日常的に使用していた方々も、代替手法を検討されているのではないでしょうか。例えば、以下のリンクでは AWS IDE Toolkits や AWS CloudShell への移行方法が紹介されています。
aws.amazon.com
AWS IDE Toolkits や AWS CloudShell を利用する方法もありますが、ローカル環境に依存せず、各チームが共通の 統合開発環境 (IDE) を簡単に初期セットアップ&カスタマイズできる仕組みを導入したい場合もあるのではないでしょうか。
そこで代替案の一つとして、今回は code-serverをDocker上にセットアップし、リモート統合開発環境 (IDE)として利用してみることを試してみます。
code-server について
Coder社が提供するOSSのリモート統合開発環境 (IDE) です。ブラウザ上でコードの記述、実行、デバッグが可能です。
VSCodeをベースにしたWebベースのIDEであるため、見た目や操作感はVisual Studio Code (VS Code) とほぼ同じです。そのため、普段からVS Codeを利用している方は、違和感なく使用できます。
code-server のドキュメント
code-serverの詳細はドキュメントを参照ください。 coder.com
Docker用のイメージを利用する場合の制約
Our official image supports amd64 and arm64. For arm32 support, you can use a community-maintained code-server alternative.
code-serverの公式イメージは amd64(一般的な64ビットIntel/AMDアーキテクチャ)およびarm64(64ビットARMアーキテクチャ)に対応しています。 coder.com
今回の構成
Public Subnet 上に EC2(Amazon Linux 2023)をデプロイし、Docker を使用して code-server をインストールします。この構成では、セキュリティグループで利用ユーザーの IP アドレスのみを許可します。また、code-server への接続は、SSL 通信を使用して 443 番ポート経由で行います。
環境構築してみる
東京リージョンに code-server を構築してみます。
CFn 実行
東京リージョンでCFnをぽちっとな。
本code-serverは systemdによる自動起動制御の設定をしているため、サーバーを停止してから再起動しても正常に動作します。
パラメータ は必要に応じて適宜修正してください。
(もしEC2からAWSリソースに対して何か必要な権限が必要な場合はロールの修正をお願いします。)
AWSTemplateFormatVersion: "2010-09-09" Description: "CloudFormation template to create a code-server with specified requirements." Parameters: AllowedIP: Type: String Description: "The IP address allowed to access the instance on port 443 (e.g., 203.0.113.0/32)." ConstraintDescription: "Must be a valid CIDR notation (e.g., 203.0.113.0/32)." AllowedPattern: "^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}/[0-9]{1,2}$" ImageId: Type: String Description: "The AMI ID for the EC2 instance (e.g., ami-12345678)." Default: "ami-023ff3d4ab11b2525" ConstraintDescription: "Must be a valid AMI ID." Resources: # VPC VPC: Type: AWS::EC2::VPC Properties: CidrBlock: "10.0.0.0/16" EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: "CfnVPC" # Internet Gateway InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: "CfnInternetGateway" # Attach Internet Gateway to VPC InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway # Public Subnet PublicSubnet: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: "10.0.0.0/24" MapPublicIpOnLaunch: true AvailabilityZone: !Select [ 0, !GetAZs "" ] Tags: - Key: Name Value: "CfnPublicSubnet" # Route Table RouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: "CfnRouteTable" # Route for Internet Access PublicRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref RouteTable DestinationCidrBlock: "0.0.0.0/0" GatewayId: !Ref InternetGateway # Associate Route Table with Subnet RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet RouteTableId: !Ref RouteTable # Security Group SecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: "Allow HTTPS access from specified IP" VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: !Ref AllowedIP Tags: - Key: Name Value: "CfnSecurityGroup" # IAM Role for SSM Access IAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore Path: "/" Tags: - Key: Name Value: "CfnSSMRole" Policies: - PolicyName: "SSMPutParameterPolicy" PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - ssm:PutParameter Resource: Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/code-server/login-password # Instance Profile IAMInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref IAMRole InstanceProfileName: "CfnSSMInstanceProfile" # EC2 Instance EC2Instance: Type: AWS::EC2::Instance Properties: InstanceType: t2.micro SubnetId: !Ref PublicSubnet SecurityGroupIds: - !Ref SecurityGroup IamInstanceProfile: !Ref IAMInstanceProfile ImageId: !Ref ImageId Tags: - Key: Name Value: "code-server-docker" UserData: Fn::Base64: | #!/bin/bash # ------------------------------------------------ # 変数セット # ------------------------------------------------ USER_NAME="ec2-user" USER_HOME="/home/ec2-user" CODE_SERVER_PASSWORD=$(head /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 25) USER_ID=1000 GROUP_ID=1000 DOCKER_USER=${USER_NAME} TEMP_DIR="/tmp" # ------------------------------------------------ # インストール # ------------------------------------------------ sudo dnf install -y git docker sudo usermod -a -G docker ${USER_NAME} DOCKER_COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | jq -r .tag_name) sudo curl -L "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose sudo systemctl restart docker.service # ------------------------------------------------ # Docker定義 # ------------------------------------------------ # Dockerfileの作成 sudo cat <<'EOF' > ${TEMP_DIR}/Dockerfile # ベースとなるイメージを指定(codercom/code-serverの最新バージョン) FROM codercom/code-server:latest # コンパイル時に使用する変数を定義 # USER_ID: ユーザーのID(uid) # GROUP_ID: ユーザーが所属するグループのID(gid) # DOCKER_USER: 使用するユーザー名 ARG USER_ID ARG GROUP_ID ARG DOCKER_USER # rootユーザーで実行(初期セットアップ時に必要なパーミッションを取得するため) USER root # 必要なパッケージ(libcap2-bin)をインストール # setcapを使用するために必要 RUN apt-get update && apt-get install -y libcap2-bin # 'coder'ユーザーのIDとグループIDをARGで渡された値に変更 # これにより、ホストとコンテナ内で一致するUID/GIDを使用 RUN usermod -u $USER_ID coder && \ groupmod -g $GROUP_ID coder # ノードのサービスバインディングに必要な権限(cap_net_bind_service)を付与 # これにより、ポート番号が1024未満のポートにサービスをバインドする権限を付与 RUN setcap 'cap_net_bind_service=+ep' /usr/lib/code-server/lib/node # 作業ディレクトリを設定 WORKDIR /home/coder/project # coderユーザーに切り替え USER coder # コンテナ起動時にdumb-initとcode-serverをエントリーポイントとして設定 ENTRYPOINT ["dumb-init", "code-server"] EOF sudo chown ${USER_ID}:${GROUP_ID} ${TEMP_DIR}/Dockerfile sudo mv ${TEMP_DIR}/Dockerfile ${USER_HOME}/Dockerfile # docker-compose.ymlの作成 cat <<'EOF' > ${TEMP_DIR}/docker-compose.yml services: code-server: # code-serverサービスの設定 build: # Dockerfileのビルド設定 context: . args: # Dockerfileに渡す引数(ユーザーID、グループID、ユーザー名) USER_ID: ${USER_ID} GROUP_ID: ${GROUP_ID} DOCKER_USER: ${DOCKER_USER} # ポートの公開設定 ports: - "443:443" # ホストの443ポートをコンテナの443ポートにマッピング(HTTPSポート) # ボリュームのマウント設定 volumes: - "$HOME/.config:/home/coder/.config" - "$PWD:/home/coder/project" # 環境変数の設定 environment: - DOCKER_USER=${DOCKER_USER} # ユーザー設定 user: "${USER_ID}:${GROUP_ID}" # コンテナの再起動設定 restart: always EOF sudo chown ${USER_ID}:${GROUP_ID} ${TEMP_DIR}/docker-compose.yml sudo mv ${TEMP_DIR}/docker-compose.yml ${USER_HOME}/docker-compose.yml # ------------------------------------------------ # code-server定義 # ------------------------------------------------ # code-serverのディレクトリ作成 sudo mkdir -p ${USER_HOME}/.config/code-server sudo chown ${USER_ID}:${GROUP_ID} ${USER_HOME}/.config # config.yamlの作成 cat <<EOF > ${TEMP_DIR}/config.yaml bind-addr: 0.0.0.0:443 auth: password password: ${CODE_SERVER_PASSWORD} cert: true EOF sudo chown ${USER_ID}:${GROUP_ID} ${TEMP_DIR}/config.yaml sudo mv ${TEMP_DIR}/config.yaml ${USER_HOME}/.config/code-server/config.yaml # ------------------------------------------------ # systemd定義 # ------------------------------------------------ # /etc/systemd/system/docker-compose-app.serviceの作成 cat <<EOF > ${TEMP_DIR}/docker-compose-app.service [Unit] Description=Docker Compose Application Requires=docker.service After=docker.service [Service] Type=oneshot RemainAfterExit=yes WorkingDirectory=/home/ec2-user Environment=USER_ID=${USER_ID} Environment=GROUP_ID=${GROUP_ID} Environment=DOCKER_USER=${DOCKER_USER} Environment=PWD=/home/ec2-user Environment=HOME=${USER_HOME} ExecStart=/usr/local/bin/docker-compose up -d ExecStop=/usr/local/bin/docker-compose down [Install] WantedBy=multi-user.target EOF sudo chown ${USER_ID}:${GROUP_ID} ${TEMP_DIR}/docker-compose-app.service sudo mv ${TEMP_DIR}/docker-compose-app.service /etc/systemd/system/docker-compose-app.service sudo systemctl daemon-reload # ------------------------------------------------ # code-server起動 # ------------------------------------------------ sudo chown -R ${USER_ID}:${GROUP_ID} ${USER_HOME}/.docker sudo systemctl enable docker-compose-app.service sudo systemctl start docker-compose-app.service ; sudo systemctl status docker-compose-app.service aws ssm put-parameter --name "/code-server/login-password" --value ${CODE_SERVER_PASSWORD} --type SecureString --key-id "alias/aws/ssm" --overwrite Outputs: PublicIP: Description: "code-server URL" Value: !Join - "" - - "https://" - !GetAtt EC2Instance.PublicIp - "/" UserDataSSMParameter: Description: "SSM Parameter code-server login password output" Value: "/code-server/login-password"
CFn で指定する Parameters について
先ほどのCFnを実行する時に Parameters の設定が必要になります。
- AllowIP
- 接続を許可するIP アドレスを
/32
指定で入力してください。
確認くん 等を利用し、ご自身のグローバルIPの確認してください。
- 接続を許可するIP アドレスを
- ImageId
- 任意の Amazon Linux 2023 の AMI ID を指定してください。(本記事では ami-023ff3d4ab11b2525 を使用して検証しています。)
CFn 状態確認
作成したスタックが完了していれば、code-serverのデプロイは完了です。
CFn 実行が完了すると、スタックの「出力」セクションに以下の内容が確認できます。
- PublicIP
- code-serverの接続URL
- UserDataSSMParameter
- code-serverのログインパスワードを格納している AWS Systems Manager (SSM) パラメータストアの情報
"/code-server/login-password" に保存されます。
- code-serverのログインパスワードを格納している AWS Systems Manager (SSM) パラメータストアの情報
AWS Systems Manager (SSM) パラメータストアの "/code-server/login-password" に code-server のログインパスワード情報が保存されています。
(復号化された値を表示にチェックを入れると文字列が参照できます。)
code-serverへの接続
code-server の接続 URL にアクセスします。(スタックの「出力」セクションのPublicIPがURLとなります。)
証明書を設定していないため、「この接続ではプライバシーが保護されません」と表示されますがそのまま接続を進めてください。
(「詳細設定」-「xxxxxにアクセスする(安全ではありません)」を押下する。)
SSM パラメータストアに保存されたパスワードを入力し、「SUBMIT」を押下します。
code-server にログインできたら、必要な設定を行い、利用を開始してください。
エラーメッセージについて
An SSL certificate error occurred when fetching the script.
サーバー証明書を設定していないため出力されていますので現在の設定の場合は問題ありません。 以下、参考となりますが適切なサーバー証明書を導入することで回避できます。
code-serverの削除
作成したCFnのスタックを削除してください。
パラメータストアの情報は自動で削除されないため、"/code-server/login-password" の削除もお願いします。
まとめ
個人的ですがCoder社はRe: invent 2024で「The enterprise development environment (sponsored by Coder)」(訳:エンタープライズ開発環境 (Coder 提供))でセッションをしているようなので気になっています。
参考:Join Coder at AWS re:Invent 2024
私は VS Code を利用しているため、違和感なく使用できております。 ハンズオン等のAWS Cloud9 の代替として code-server を試すのはいかがでしょうか。