GCP上にVS Code OnlineのようなCloud IDEを作ってみる


## はじめに

最近、Chromebookを買ったり複数端末で作業することが多いので、開発環境をクラウド化できないかと思案しています。

「[完全にクラウドで完結する無料の Web 開発環境 2020 年春](https://qiita.com/otchy/items/11a2ce054339c3e1a62d)」に紹介されている通り、VS Code Onlineを使うとクラウドな開発環境がさくっと構築できます。他にも[Gitpod](https://gitpod.io/workspaces/)とか[Eclipse Che](https://www.eclipse.org/che/)とかもありますね。ただ、どれもSaaS版は使い勝手が微妙と言うかある程度お作法を守る必要があったり、フルカスタマイズ出来るようにCheを自前で構築しようとすると結構面倒だったりしたのですが、[Eclipse Theia](https://theia-ide.org/)が良さそうだったので、GCPにインストールしてみました。

この記事自体はタイトル的にはGCPにCloud IDEを立てる話なのですが、なんだかんだでGCPにDockerコンテナを使って簡単なIAPによる認証付きアプリをデプロイする方法のチュートリアル、としても使えると思うので良ければその観点でもご参考ください。

## Eclipse Theia

Eclipse TheiaはVS Codeをよりvendor-neutralな形で開発/利用できるようにしたEclipse Fundationによるクローンです。
VS CodeはBSDライセンスでGithubにありますが、MSが出しているVS Codeバイナリはマイクロソフトの商用ライセンスに従ってるとかあったり開発の意思決定プロセスとかたぶんそう言うのを気にしてよりベンダーニュートラルなクローンを作った感じだと。

まあ、細かなは背景はさておきTheiaの特徴はElectonベースのデスクトップ版だけではなくWebアプリケーション版も配布されていると言うことです。
実際、Eclipse CheやGitpodもTheiaがエディタ部分に利用されています。見た目だけではなく、VS Codeともプラグイン(LSP)レベルで互換性があるので、既存のVS Codeエコシステムがそのまま利用できるのが最大の利点です。

## GCPのネットワーク設定

まず、クラウド環境の構築に先立ってネットワークの設定を行います。クラウドというと作成したサーバがどこからでもアクセスできるイメージもありますが、実際はVPCと呼ばれるネットワークの中でFWに守られています。
なので、インストール前にネットワークの設定をしておきます。

### Cloud NATの作成

GCPのようなクラウド環境では各端末にGlobal IPを振って直接アクセスすることはできます。ただ、Cloud IAPと相性が悪かったりもするので通常はオンプレミスと同様にprivate IPだけをふる運用にするのが一般的です。
今回もそのポリシーで作りますが、その場合は直接インターネットに繋がらないので`docker pull`などをするためにNATが必要になります。

GCPにはフルマネージドのCloud NATがあるのでそれを下記コマンドで作成します。

```bash
$ gcloud compute routers create nat-router-jp \
    --network default \
    --region asia-northeast1

$ gcloud compute routers nats create nat-jp \
    --router=nat-router-jp  \
    --auto-allocate-nat-external-ips \
    --nat-all-subnet-ip-ranges \
    --enable-logging \
    --region asia-northeast1
```

## GCPのCOS(Container-Optimized OS)にインストール。

続いてTheiaをGCEにインストールします。[インストールマニュアル](https://theia-ide.org/docs/composing_applications/)に従ってNode.jsから入れても良いのですが、[コンテナがある](https://hub.docker.com/r/theiaide/theia/)のでコンテナを使います。

GKEを使っても良いですが開発環境を動かすだけには大仰なので、COSにインストールします。

COSは`/var`配下とか特定のディレクトリしかデータが永続化しないのでコンテナにマウントする用のディレクトリを起動時スクリプトで作成しています。
権限もTheiaが動くコンテナからも書き込めるように777にする必要があります。真面目に作るときはPDを使う方が良いかもです。


```config.sh
#!/bin/bash

mkdir -p /var/theia
chmod 777 /var/theia
```

次に`gcloud`コマンドでGCEにインストールします。`create-with-container`でコンテナを指定して自動起動にします。

```bash
$ gcloud beta compute instances create-with-container \
    ins-theia \
    --zone=asia-northeast1-b \
    --machine-type=e2-standard-2 \
    --preemptible \
    --subnet=default --no-address \
    --tags=theia \
    --image-family=cos-stable \
    --image-project=cos-cloud \
    --container-image=theiaide/theia:next \
    --container-restart-policy=always --container-privileged \
    --container-mount-host-path=mount-path=/home/project,host-path=/var/theia,mode=rw \
    --metadata-from-file startup-script=config.sh

NAME       ZONE               MACHINE_TYPE   PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP  STATUS
ins-theia  asia-northeast1-b  e2-standard-2               10.146.0.23               RUNNING
```

オプションに特筆することは無いですが、前述の通りグローバルIPはふってません。また、nested containerをしたいのでprivilegedを付けています。開発環境なので普通にDockerを動かしたいですからね。

また、Dockerに対してポートバインディングの設定もしていません。COSではコンテナはホストネットワークで動いてるので単にVPCに穴を開けるなどすれば直接見れるからです。詳しくは[こちら](https://cloud.google.com/compute/docs/containers/configuring-options-to-run-containers?hl=ja#publishing_container_ports)。

これで構築は完了です。正常にインストールが出来たか動作確認をしてみます。
Private IPだけなのでIAPを使ってポートフォーワードをします。まずはIAPから先ほど作ったサーバにアクセスできるようにタグに対してFirewallに穴を開けます。

```bash
$ gcloud compute firewall-rules create allow-iap-theia \
  --network=default \
  --target-tags=theia \
  --allow=tcp:3000 \
  --source-ranges=35.235.240.0/20
```

続いて、[IAPでポートフォーワード](https://cloud.google.com/iap/docs/using-tcp-forwarding?hl=ja#general_tcp_tunneling)をします。

```bash
$ gcloud compute start-iap-tunnel ins-theia 3000 --local-host-port=localhost:3000 --zone asia-northeast1-b
Testing if tunnel connection works.
Listening on port [3000]
```

無事構築が出来ていれば以下のようにTheiaに繋ぐことができます。
<img width="480" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54904/940bb279-ed77-1808-effe-37a2ea364d8c.png />

## ロードバランサの設定をする

毎回ポートフォーワードをするのは面倒なので、[Cloud Load Balancing](https://cloud.google.com/load-balancing?hl=ja)の設定をします。これはGoogleの誇る実質無限のキャパシティを持つL7のロードバランサ/リバースプロキシです。

Cloud Load Balancingには下記の作業が必要です。

* バックエンドの作成
    * インスタンスグループの作成
    * 名前付きポートの作成
    * ヘルスチェックの作成
    * バックエンドサービスの作成
    * バックエンドサービスへの登録
* ホストとパスのルールの作成
* フロントエンドの作成
    * 証明書の作成
    * Proxyの作成
    * フォーワードルールの作成

ちょっと多いですね。またフロントエンドはHTTPで作ることもできますがセキュリティを考えるとHTTPSにするべきです。
ただ、その場合は証明書の発行にドメインが必要です。[nip.io](https://nip.io/)のようなワイルドカードDNSを使うか、お名前.comやGoogle Domainsで独自ドメインを取得する必要があります。

個人的にはドメインを持っておくとCloud Identityが使えたりと便利な事が多いので、年間で1500円から2000円くらいなので、これを機に買っておくのも良いと思います。


### バックエンド サービスの作成

まずバランシング対象であるバックエンドサービスを作ります。
バックエンドサービスはインスタンスではなくインスタンスグループにする必要があります。
今回はすでにインスタンスを作っているので、非マネージドなインスタンスグループを作って参加させます。
またこのインスタンスグールプではtheiaがポート3000で待ちつけているので名前付きポートを作成します。

```bash
$ gcloud compute instance-groups unmanaged create ins-group-theia --zone=asia-northeast1-b
$ gcloud compute instance-groups unmanaged add-instances ins-group-theia --zone=asia-northeast1-b  \
--instances=ins-theia
$ gcloud compute instance-groups unmanaged set-named-ports ins-group-theia --zone=asia-northeast1-b  \
  --named-ports=port-theia:3000
```

続いてバックエンドサービスの死活監視を行うHealth Checkを作ります。

```bash
$ gcloud beta compute health-checks create http http-check-theia --port 3000 --request-path '/'  --global --enable-logging
```

ただこの状態ではHealth Checkリクエスト自体がFirewallで止められてるのでGFEからtheiaへ穴を開けます。GFEからロードバランシングの実施やヘルスチェックが行われます。

```bash
$ gcloud compute firewall-rules create allow-gfe-theia \
  --network=default \
  --target-tags=theia \
  --allow=tcp:3000 \
  --source-ranges=35.191.0.0/16,130.211.0.0/22
```

最後にバックエンドサービスを作成してインスタンスグループを追加します。

```bash
$ gcloud compute backend-services create backend-theia \
    --protocol HTTP \
    --health-checks http-check-theia \
    --port-name port-theia \
    --global

$ gcloud compute backend-services add-backend backend-theia \
    --capacity-scaler=1 \
    --instance-group=ins-group-theia \
    --instance-group-zone=asia-northeast1-b \
    --global
```

### URLマップとフロントエンドの作成

まず、SSL証明書を作ります。特にこだわりが無いのでGoogleが無料で発行/管理してくれる[Google マネージド SSL 証明書](https://cloud.google.com/load-balancing/docs/ssl-certificates/google-managed-certs?hl=ja)を使います。

```bash
$ gcloud beta compute ssl-certificates create cert-theia \
        --description="certificates for theia" \
        --domains=${YOUR_DOMAIN} \
        --global
$ gcloud beta compute ssl-certificates list --filter="name=cert-theia"
```

statusが`PROVISIONING`から`ACTIVE`なればOKです。これは少し時間がかかります。

次にURLマップを作成します。以下のコマンドで全てのリクエストをbackend-theiaに飛ばすマップを作ります。

```bash
$ gcloud compute url-maps create lb-theia --default-service backend-theia
```


アクセスを受けるエンドポイントとなるProxyを作成してリクエストを流すフォーワードルール(フロントエンドサービス)を作ります。

```bash
$ gcloud compute target-https-proxies create proxy-theia \
         --url-map lb-theia --ssl-certificates cert-theia
$ gcloud compute forwarding-rules create frontend-theia \
            --global \
            --target-https-proxy=proxy-theia \
            --ports=443
```

今回IPアドレスは自動作成にしたので以下のコマンドで確認して、DNSに登録します。

```bash
$ gcloud compute forwarding-rules list
NAME            REGION  IP_ADDRESS     IP_PROTOCOL  TARGET
frontend-theia          xxx.xxx.xxx.xxx  TCP          proxy-theia
```

これで、`https://${YOUR_DOMAIN}/`にアクセスできれば完了です。DNSの反映などは少し時間がかかると思います。

## Cloud IAPの設定

最後にIAPの設定です。

現状は誰でもアクセスできてしまうので認証を掛けたいです。
ただ、ネットワークACLだとカフェとかからアクセスできませんし、Basic認証だとちょっと弱すぎます。Theia自体に認証の機能もありません。こういう時には[Identity-Aware Proxy（IAP)](https://cloud.google.com/iap/docs/concepts-overview?hl=ja)が有効です。

IAPはその名の通りコンテキストを判定するProxyです。コンテキストとはユーザ、ロール、ロケーション、ターゲットアプリケーションを指します。RBACより高度なCAACを実現しGoogleのゼロトラストセキュリティ/ByondCorpの中核です。ゼロトラストセキュリティ自体の詳しい話は[以前書いたブログ](https://koduki.hatenablog.com/entry/2020/03/02/131415)があるのでそちらを参考ください。

今回はIAPをURLに適用する事で自分だけがアクセスできるように設定します。

### OAuth同意画面の作成

まず、[OAuth同意画面](https://console.cloud.google.com/apis/credentials/consent?hl=ja&_ga=2.262030925.1302269205.1586778047-19221729.1586669102)の設定をします。サポートメールは適当なGoogle Groupを作っておくと自分のgmailを晒さなくて良いと思います。

### OAuth認証情報の作成

次に[OAuth認証ページ](https://console.cloud.google.com/apis/credentials?hl=ja&_ga=2.28600316.1302269205.1586778047-19221729.1586669102)に移動してOAuth 認証情報の作成を作成します。

Googleの公式ページを参考に以下の手順を実施します。

1. 認証情報の作成] プルダウンリストを開き、[OAuth クライアントID] を選択します。
2. [アプリケーションの種類] で、[ウェブ アプリケーション] を選択します。
3. OAuth クライアント ID の名前を追加します。
4. [作成] をクリックします。OAuth クライアント ID とクライアント シークレットが生成され、OAuth クライアント ウィンドウに表示されますのでメモします。
5. [OK] をクリックします。

### IAP アクセス権の設定

IAPがバックエンドサービスにアクセスできるようにFirewallの設定を変更します。

```bash
$ gcloud compute firewall-rules update allow-gfe-theia \
  --target-tags=theia \
  --allow=tcp:3000,tcp:80 \
  --source-ranges=35.191.0.0/16,130.211.0.0/22
```

以下のコマンドでバックエンドサービスに対してIAPを有効にします。

```bash
$ export OAUTH2_CLIENT_ID=xxxxx.apps.googleusercontent.com
$ export OAUTH2_CLIENT_SECRET=xxx
$ gcloud compute backend-services update backend-theia --global --iap enabled,oauth2-client-id=${OAUTH2_CLIENT_ID},oauth2-client-secret=${OAUTH2_CLIENT_SECRET}
```

以下のように、アクセスができなくなれば成功です。

<img width="480" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54904/cf0c3d3b-287e-c458-6d10-39b566a627e6.png />


最後に自分のアカウントに閲覧権限を付けます。

```bash
$ export YOUR_GMAIL=xxxx@gmail.com
$ gcloud iap web add-iam-policy-binding --resource-type=backend-services \
  --service=backend-theia  \
  --member=user:${YOUR_GMAIL} \
  --role=roles/iap.httpsResourceAccessor
```

これでTheiaがIAPで保護されるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54904/a10b5c63-02d2-d70d-fdb4-82429553723e.png)

## まとめ

GCPにTheiaを入れて簡単なCloud IDEを作ってみました。
もちろんSaaSを使う方が何かと手っ取り早いとは思うのですが、自分でこうして環境を作るのも楽しいですよね。
実際にDokcerビルドや実行したりとかの開発をこの環境を使っていって、後世のカスタマイズもしていきたいと思います。

あと、COSはk8s使うよりも手軽なのでIAPと組み合わせてOSSツールをさくっと入れるのにやはり便利ですね。

それではHappy Hacking!


## 参考

- [Eclipse Theia](https://theia-ide.org/)
- [GCP - Container-Optimized OS](https://cloud.google.com/container-optimized-os/docs?hl=ja)
- [GCE - VM と MIG へのコンテナのデプロイ](https://cloud.google.com/compute/docs/containers/deploying-containers?hl=ja)
- [GCE - コンテナを実行する際のオプションの構成](https://cloud.google.com/compute/docs/containers/configuring-options-to-run-containers?hl=ja)
- [GCP - Cloud Load Balancing ](https://cloud.google.com/load-balancing/docs?hl=ja)
- [GCP - Compute Engine での IAP の有効化](https://cloud.google.com/iap/docs/enabling-compute-howto?hl=ja)