SageMaker StudioのCode Editorを魔改造してWebアプリのプロキシ機能を追加する

AWS上のWeb IDEをカスタマイズするのにハマっています。これまで色々記事にしてきました。

https://qiita.com/moritalous/items/82fae0eb1820593101d3

https://qiita.com/moritalous/items/95c927834496de3c9686

https://qiita.com/moritalous/items/708d56d5c10c5832d169

https://qiita.com/moritalous/items/656793cd97e1e967f045

今回は、Proxyしてみた系の第３弾で、 **SageMaker StudioのCode EditorでProxyさせる** です。

## Amazon SageMaker Studio Code Editorのカスタムイメージ

SageMaker StudioのエディターはDockerコンテナイメージを作成してカスタマイズすることができます。

https://aws.amazon.com/jp/about-aws/whats-new/2024/04/amazon-sagemaker-studio-code-editor-custom-images/

https://docs.aws.amazon.com/sagemaker/latest/dg/code-editor-custom-images.html

SageMaker Studioの環境構築時に指定する「SageMaker Distribution」がコンテナイメージとしてECR Publicで[一般公開](https://gallery.ecr.aws/sagemaker/sagemaker-distribution)されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/e807e64f-c9c7-9f1e-6579-2f1ca324e596.png)

[ドキュメント](https://docs.aws.amazon.com/sagemaker/latest/dg/code-editor-custom-images-specifications.html)では以下のようにVSCodeの拡張をインストールする例が紹介されていました。

```Dockerfile
FROM public.ecr.aws/sagemaker/sagemaker-distribution:latest-cpu
ARG NB_USER="sagemaker-user"
ARG NB_UID=1000
ARG NB_GID=100
ENV MAMBA_USER=$NB_USER

USER root

 # install scrapy in the base environment
RUN micromamba install -y --name base -c conda-forge scrapy

 # download VSCodeVim
RUN \
  wget https://github.com/VSCodeVim/Vim/releases/download/v1.27.2/vim-1.27.2.vsix \
  -P /tmp/exts/ --no-check-certificate

 # Install the extension
RUN \
  extensionloc=/opt/amazon/sagemaker/sagemaker-code-editor-server-data/extensions \
  && sagemaker-code-editor \
    --install-extension "/tmp/exts/vim-1.27.2.vsix" \
    --extensions-dir "${extensionloc}"

USER $MAMBA_USER
ENTRYPOINT ["entrypoint-code-editor"]
```

## カスタム内容を考える

[AWSさん提供のワークショップ環境で「Webアプリのプレビュー」を行う](https://qiita.com/moritalous/items/95c927834496de3c9686)で触れた以下の構成を再現しようと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/252a3476-fdec-7e3c-3ae2-4b2e98b95391.png)

以下のことができれば実現できそうです。

- Code Editorが使用するポート番号を`18888`ポートに変更
- Nginxをインストール
- Nginxを`8888`ポートで起動し、`8080`ポートで起動したWebアプリにProxyする設定を行う

:::note
本投稿の最後に、8888ポートで起動していることを見つけた方法を残してますので、ご興味ございましたらご参照ください。
:::

## カスタムする

DockerfileとNginxの設定ファイルを用意します。

```dockerfile:Dockerfile
# Reference: https://docs.aws.amazon.com/sagemaker/latest/dg/code-editor-custom-images-specifications.html

FROM public.ecr.aws/sagemaker/sagemaker-distribution:latest-cpu

ARG NB_USER="sagemaker-user"
ARG NB_UID=1000
ARG NB_GID=100
ENV MAMBA_USER=$NB_USER

USER root

RUN apt-get update && apt-get install -y nginx

ADD nginx-proxy.conf /etc/nginx/sites-enabled/

RUN sed -i 's/8888/18888/g' /usr/local/bin/start-code-editor

RUN sed -i '/^user www-data;/d' /etc/nginx/nginx.conf 
RUN sed -i 's|/run/nginx.pid|/tmp/nginx.pid|g' /etc/nginx/nginx.conf 
RUN rm /etc/nginx/sites-enabled/default

RUN echo '' >> "/etc/supervisor/conf.d/supervisord-code-editor.conf" && \
    echo '[program:nginx]' >> "/etc/supervisor/conf.d/supervisord-code-editor.conf" && \
    echo 'command=nginx -g "daemon off;"' >> "/etc/supervisor/conf.d/supervisord-code-editor.conf" && \
    echo 'autostart=true' >> "/etc/supervisor/conf.d/supervisord-code-editor.conf" && \
    echo 'autorestart=true' >> "/etc/supervisor/conf.d/supervisord-code-editor.conf"

RUN chown -R 1000:100 /var/lib/nginx
RUN chmod -R 750 /var/lib/nginx
RUN chown -R 1000:100 /var/log/nginx
RUN chmod -R 750 /var/log/nginx

USER $MAMBA_USER
ENTRYPOINT ["entrypoint-code-editor"]
```

```nginx:nginx-proxy.conf
server {
    listen 8888;
    listen [::]:8888;
    
    # Code Editor
    location / {
        proxy_pass http://localhost:18888/;
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection upgrade;
        proxy_set_header Accept-Encoding gzip;
        proxy_http_version 1.1;        
    }

    location /8080 {
        proxy_pass http://localhost:8080/;
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection upgrade;
        proxy_set_header Accept-Encoding gzip;
        proxy_http_version 1.1;
    }

    location /absolute/8080 {
        proxy_pass http://localhost:8080/codeeditor/default/absolute/8080;
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection upgrade;
        proxy_set_header Accept-Encoding gzip;
        proxy_http_version 1.1;
    }    
}
```

コンテナイメージをビルドしてECRに登録します。

```shell
# ログイン
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com

# ビルド
docker build -t code-editor-custom-image .

# タグ付け
docker tag code-editor-custom-image:latest 123456789012.dkr.ecr.us-east-1.amazonaws.com/code-editor-custom-image:latest

# プッシュ
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/code-editor-custom-image:latest
```

## カスタムイメージで環境を起動

SageMakerの画面の左メニューの「管理者設定」セクションの「ドメイン」をクリックします。
ドメインの詳細画面で「環境」タブを選択し、パーソナルStudioアプリ用のカスタムイメージのところの「イメージをアタッチ」をクリックします。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/3ea39611-afbc-0fb0-ea11-5ea7fb6d77c6.png)

ECRのイメージのURIを入力し、「次へ」をクリックします。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/7495ad8b-d7c8-6d8f-f216-6a421e6b0eaf.png)

設定項目を埋め、アプリケーションタイプとして「Code Editorイメージ」を選択して「送信」をクリックします。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/3ff9bf49-3361-0258-4f59-46b67a4f42fc.png)

SageMaker Studioの画面に遷移し、Code Editorの環境を作成します。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/1ff47268-5390-6841-d874-6825a8c01235.png)

Custom imagesとして先ほど登録したイメージがあるので選択します。

![studio-d-2brf3p4q4fsn.studio.us-east-1.sagemaker.aws_code-editor (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/9993507e-5a47-5247-b045-788159c6ceaf.png)

起動します。AWS提供のイメージよりも起動に時間がかかるイメージです。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/76086594-818f-1a0b-9b07-7e11853d8ae7.png)

無事Code Editorが起動しました！

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/9ad3ae1c-2eb9-8162-8eb2-cea609051950.png)

## Proxyの動作確認

[こちら](https://qiita.com/moritalous/items/95c927834496de3c9686)と同様、Viteで簡単なWebアプリを作成しました。

```shell
npm -y create vite@latest my-app -- --template react

cd my-app
npm install
```

vite.config.jsをいじります。

```diff_javascript:vite.config.js
  import { defineConfig } from 'vite'
  import react from '@vitejs/plugin-react'
  
  // https://vite.dev/config/
  export default defineConfig({
    plugins: [react()],
    base: '/codeeditor/default/absolute/8080',
+   server: {
+     port: 8080,
+     allowedHosts: ["**********.studio.us-east-1.sagemaker.aws"]
+   }
  })
```

package.jsonもちょっとだけ修正します。

```diff_json:package.json
- "dev": "vite",
+ "dev": "vite --host",
```

起動します。

```shell
npm run dev
```

以下のURLにアクセスします。

**********.studio.us-east-1.sagemaker.aws/codeeditor/default/absolute/8080

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/51be880e-92be-f3b6-6359-a7acc68df60c.png)

OKです！

---

つづいてStreamlitです。

```shell
pip install streamlit
```

Streamlitを起動します。

```shell
streamlit hello --server.port 8080 --server.baseUrlPath /codeeditor/default/absolute/8080
```

以下のURLにアクセスします。

**********.studio.us-east-1.sagemaker.aws/codeeditor/default/absolute/8080

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/6eef42e2-face-929d-3656-a14c76529a0e.png)

OKです！
WebSocketも大丈夫です！！


## おまけ：SageMaker Distributionの中身を探った奇跡

試行錯誤のメモを残しておきます。

---

ENTORYPOINTとして指定されている`entrypoint-code-editor`の中身を調べてみましょう。

```shell
docker run -it --rm public.ecr.aws/sagemaker/sagemaker-distribution:latest-cpu
```

まずは在り処を調べます。

```shell
which entrypoint-code-editor
```
```
/usr/local/bin/entrypoint-code-editor
```

スクリプトだと信じで内容を確認します。

```shell
cat /usr/local/bin/entrypoint-code-editor
```
```bash
#!/bin/bash

set -e

# Generate and execute the shell code to modifies shell variables to include
# micromamba commands (e.g. using `micromamba activate` to activate environments)
eval "$(micromamba shell hook --shell=bash)"

# Activate conda environment 'base', where supervisord is installed
micromamba activate base

# Set up SAGEMAKER_APP_TYPE_LOWERCASE based on SAGEMAKER_APP_TYPE
export SAGEMAKER_APP_TYPE_LOWERCASE=$(echo $SAGEMAKER_APP_TYPE | tr '[:upper:]' '[:lower:]')

mkdir -p $STUDIO_LOGGING_DIR/$SAGEMAKER_APP_TYPE_LOWERCASE/supervisord
exec supervisord -c /etc/supervisor/conf.d/supervisord-code-editor.conf -n
```

スクリプトでした。supervisordで何かを起動しているようです。

```shell
cat /etc/supervisor/conf.d/supervisord-code-editor.conf
```
```
[include]
files = supervisord-common.conf

[program:codeeditorserver]
directory=%(ENV_HOME)s
command=start-code-editor
autostart=true
autorestart=true
stdout_logfile=/dev/fd/1   ; Redirect web server logs to stdout
stdout_logfile_maxbytes = 0 ; Fix: https://github.com/Supervisor/supervisor/issues/935
stderr_logfile_maxbytes = 0 ; Fix: https://github.com/Supervisor/supervisor/issues/935
```

次はstart-code-editorを追います。

```shell
which start-code-editor
```
```
/usr/local/bin/start-code-editor
```

中身は？

```shell
cat /usr/local/bin/start-code-editor
```
```bash
#!/bin/bash
set -e

eval "$(micromamba shell hook --shell=bash)"

# Activate conda environment 'base', which is the default environment for sagemaker-distribution
micromamba activate base

# Start code-editor server
if [ -n "$SAGEMAKER_APP_TYPE_LOWERCASE" ]; then
  # SAGEMAKER_APP_TYPE is set, indicating the server is running within a SageMaker
  # app. Configure the base url to be `/<app-type-in-lower-case>/default`.
  sagemaker-code-editor --host 0.0.0.0 --port 8888 \
    --without-connection-token \
    --base-path "/$SAGEMAKER_APP_TYPE_LOWERCASE/default" \
    --server-data-dir /opt/amazon/sagemaker/sagemaker-code-editor-server-data \
    --extensions-dir /opt/amazon/sagemaker/sagemaker-code-editor-server-data/extensions \
    --user-data-dir /opt/amazon/sagemaker/sagemaker-code-editor-user-data
else
  sagemaker-code-editor --host 0.0.0.0 --port 8888 \
    --without-connection-token \
    --server-data-dir /opt/amazon/sagemaker/sagemaker-code-editor-server-data \
    --extension-dir /opt/amazon/sagemaker/sagemaker-code-editor-server-data/extensions \
    --user-data-dir /opt/amazon/sagemaker/sagemaker-code-editor-user-data
fi
```

Code Editorが8888ポートで起動してるっぽいですね！