WEB版Visual Studio CodeをKubernetesで立ち上げてみた

July 16, 2022

VSCodeをk8sにインストールしてリモートで使いたくなり、こちらの手順でインストールしてみました。ただ、これだけだとデバッグができないため、少しチャート(ci/helm-chart/)を変更してみましたので備忘録としてメモします。docker in dockerコンテナをサイドカーコンテナとして起動してcode-serverコンテナからデバッグ用のコンテナを起動できるようにする、がゴールです。

まず、デバッグ用のコンテナをdocker in dockerで立ち上げるため、以下のように少しvalue.yamlをいじりました。serviceにはデバッグ用コンテナ内で起動するアプリケーションサーバのポート及び外部からアクセスするためのポートを設定できるようにしています。また、postStartExecでcode-serverコンテナ内でdockerクライアントとdocker-composeをインストールするためのコマンドを定義しています。さらに、code-serverコンテナからdindコンテナにdockerコマンドでアクセスするためにDOCKER_HOST環境変数を定義しています。

service:
  type: ClusterIP
  port: 8080
  debugTargetPort: 18000
  debugPort: 18000

image:
  repository: codercom/code-server
  tag: '4.5.0'
  pullPolicy: Always
  postStartExec: "sudo curl -L https://github.com/docker/compose/releases/download/v2.6.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose \
                    && sudo chmod +x /usr/local/bin/docker-compose \
                    && curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-20.10.17.tgz \
                    | sudo tar -xzC /usr/local/bin --strip=1 docker/docker"

extraVars:
  - name: DOCKER_HOST
    value: "tcp://localhost:2375"

あと、templates/deployment.yamlにはdindコンテナをサイドカーコンテナとして起動するための定義を追加するとともにcode-serverのコンテナ起動後にさきほどvalues.yamlで定義したコマンドを実行するようにlifecycleのpostStartを定義しています。ちなみに、code-serverコンテナ内でdockerコマンドを実行する際にフォルダをマウントすると、dindのコンテナ内のフォルダがマウントされる(関連ページ)ため、dindコンテナでも/home/coderにdataをマウントしています。

      containers:
{{- if .Values.extraContainers }}
{{ tpl .Values.extraContainers . | indent 8}}
{{- end }}
        - name: docker-dind
          image: docker:20.10.17-dind
          imagePullPolicy: IfNotPresent
          volumeMounts:
          - name: data
            mountPath: /home/coder
          resources:
            requests:
              cpu: 250m
              memory: 256M
          securityContext:
            privileged: true
            procMount: Default
          env:
          - name: DOCKER_TLS_CERTDIR
            value: ""
          - name: DOCKER_DRIVER
            value: "overlay2"
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          lifecycle:
            postStart:
              exec:
                command:
                  - sh
                  - -c
                  - {{ .Values.image.postStartExec }}

templates/service.yamlには前述したデバッグコンテナにアクセスするためのポートの定義を追加します。

spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: http
      protocol: TCP
      name: http
    - port: {{ .Values.service.debugPort }}
      targetPort: {{ .Values.service.debugTargetPort }}
      protocol: TCP
      name: http-for-debug

これでターミナルからdockerコマンドが使えるようになりますので、例えばPHPならばソースフォルダをマウントしてアプリのコンテナを起動すれば動作を確認しながらソースを編集できます。ただ、dindで立ちあげたデバッグコンテナ内ではなぜかパッケージのインストールなど外部にアクセスするときにタイムアウトが頻発してしまう問題があった(こちらによると類似した現象がGitLab CIでも発生している)ため、コンテナのイメージビルド及びコンテナの実行時にはホストネットワークモードを利用するようにしました。具体的には、以下のようなdocker-compose.yamlを作成してビルド、コンテナ実行をしました。

version: '2'
services:
  devserver:
    container_name: 'devserver'
    build:
      context: .
      network: host

    command: tail -f /dev/null
    expose:
      - "18000"
    network_mode: host
    volumes:
      - .:/workspace
    user: "1000:1000"

例えば、ElixirのPhoenixで作成したWEBアプリのデバッグ用コンテナを使う場合のDockerfileの例としては以下のようになります。

FROM elixir:1.13.4-slim

WORKDIR /workspace

RUN apt-get update -y  && apt-get install -y \
    inotify-tools \
    make \
    build-essential \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

RUN adduser --gecos '' --disabled-password coder
USER coder

RUN mix local.hex --force
RUN mix local.rebar --force
RUN mix archive.install --force hex phx_new

デバッグコンテナ内でPhoenixアプリを起動する際にはこちらのページのようにポートの設定を変更する必要があります。今回は18000番を指定したのでdev.exsのポート設定を以下のようにしました。

config :chat, ChatWeb.Endpoint,
  # Binding to loopback ipv4 address prevents access from other machines.
  # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
  http: [ip: {0, 0, 0, 0}, port: 18000],
  check_origin: false,
  code_reloader: true,
  debug_errors: true,
  secret_key_base: "xxxx",
  watchers: [
    # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
    esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}
  ]