こんにちは。SI部の r_maeda です。
私の所属するSI部では、普段 Ruby on Rails を利用して Webアプリケーションを開発しています。
そんな中で「複数のシステムをまたがって共有できる、共有したほうがよい」コードを書くこともあり、そういったコードについては gem として切り出し、独立したリポジトリで管理することがあります。
例えば、RuboCop の設定ファイルなどはその一つですね。
RuboCop の設定ファイルであれば秘匿情報などは含まれないため、公開リポジトリで管理しても問題はないのですが、中には非公開リポジトリで管理したほうがよい gem もあります。
私は普段の開発に Docker を利用しているのですが、非公開リポジトリで管理されている gem を Docker image / Docker container から利用するためには、いくつかの工夫が必要となります。
今回は Docker image / Docker container から、非公開の Git リポジトリに置かれた gem を利用するために必要な設定・手順について、紹介していきたいと思います。
今回紹介する方法では、Docker ホストの SSH 鍵を利用して、Docker image / Docker container から非公開リポジトリへの接続を行います。
そのため、Docker ホスト側での事前準備が必要になります。
Docker ホストから、非公開リポジトリへのアクセスができるようにしておいてください。具体的には以下の手順を行います。
Docker ホストから非公開リポジトリの git clone
ができれば、問題ありません。
ssh-agent
とは、SSHの公開鍵認証が必要な場面において、秘密鍵を直接指定する代わりに利用できるアプリケーションです。
あらかじめ秘密鍵とそのパスフレーズを与えて ssh-agent
を起動しておくことで、「公開鍵認証を行うたびにパスフレーズを要求される」ことがなくなります。
また、ssh-agent
は「秘密鍵を保持しているホスト」以外にも共有できるため、多段 SSH などを行う際にも活躍します。踏み台となる SSH サーバに秘密鍵を置いておく必要がなくなるため、セキュリティの面でもより安全な通信を行うことが出来ます。
Linux, macOS それぞれでの ssh-agent
の起動方法は、以下の通りです。
# Linux
ssh-add path/to/private_key # 鍵の追加 e.g. `ssh-add ~/.ssh/id_ed25519`
eval $(ssh-agent -s) # ssh-agent が起動し、SSH_AUTH_SOCK 環境変数が設定される
# macOS
ssh-add path/to/private_key # ssh-add をきっかけに ssh-agent が起動するため、ssh-add のみを実行すればよい
Docker image / Docker container から利用したい gem の情報を、Gemfile に書いておきます。
gem "some-private-gem", git: "git@github.com:timedia/some-private-gem.git"
本番環境で利用する Docker image などでは、アプリで利用する gem を Docker image に含めておく必要があります。この場合、Docker image のビルド中に、非公開リポジトリにアクセスするための設定が必要となります。
まずは Dockerfile
を作成しましょう。以下に簡単な例を出します。
# syntax=docker/dockerfile:1.2
FROM ruby:3.2-slim
RUN apt-get install -y build-essential git
RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
COPY Gemfile Gemfile.lock .
RUN --mount=type=ssh bundle install
この Dockerfile
で注目してほしいのは次の2行です。
RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
RUN --mount=type=ssh bundle install
まず ssh-keyscan
の行では、ssh-keyscan
を実行することで github.com
の SSH ホストキーを取得し、取得したホストキーを ~/.ssh/known_hosts
に記録しています。
この行を省略した場合、「ホストキーの検証に失敗しました(=信用していいホストか分からなかったよ)」といった警告が出力され、Gitリポジトリからソースコードを取得できません。
#9 13.48 Git error: command `git clone ... has failed.
#9 13.48 Host key verification failed.
#9 13.48 fatal: Could not read from remote repository.
次に RUN --mount=type=ssh
の行では、bundle install
を実行し gem を取得するにあたって、「ホストの ssh-agent
を利用する」ように指示しています。
この --mount=type=ssh
は「ホストの ssh-agent を利用したい RUN
ステップ」には毎回記載する必要があります。
1行目の # syntax=docker/dockerfile:1.2
は、Dockerfile の構文バージョンを指定するための特別なコメントです。
今回利用した RUN --mount=type=ssh
は、Dockerfile 1.2 から導入された構文なので、それ以降のバージョンを指定する必要がありました。
この記事を編集している現在(2023/06/26)、最新の構文バージョンは 1.5.2 です。そのため、十分に新しい Docker engine を使っていれば、このコメント行は省略しても動きます。しかし、Dockerfile をチームで共有するのであれば、全員の engine のバージョンが十分新しいとは限らないため、書いておくのが無難かと思います。
先の Dockerfile
で定義したイメージをビルドするためには、 docker build
の実行時に --ssh
オプションを指定する必要があります。docker build
の実行時にこのオプションが指定されていないと、Dockerfile
に RUN --mount=type=ssh
と記述されていたとしても、「ホストの ssh-agent を利用する」ことはできません。
docker build --ssh default .
docker compose build
を利用する場合でも、同様に --ssh
オプションを指定することで、イメージのビルド中にホストの ssh-agent を利用できるようになります。
docker compose build --ssh default
開発環境で利用する Docker image / Docker container の場合、コンテナの実行中にアプリが利用している gem を取得、更新したい場面があります。
先ほど紹介した方法は「Docker image のビルド中に非公開リポジトリにアクセスする」ためのものでしたが、次は「実行中の Docker container から非公開リポジトリにアクセスする」ために必要な設定を見てみましょう。
必要な設定は以下の2つです。
ssh-agent
のソケットファイルをマウントするSSH_AUTH_SOCK
環境変数を設定し、マウントしたソケットファイルを参照させるホストの ssh-agent ソケットファイルは、OS によって「どこに存在するか」が異なるため、Linux と macOS では異なる path をコンテナにマウントしてあげる必要があります。
# ホストが Linux の場合
# ssh-agent のソケットファイルはホストの環境変数 $SSH_AUTH_SOCK に格納されている
docker run --rm -it \
-e "SSH_AUTH_SOCK=/tmp/ssh-auth.sock" \
-v "${SSH_AUTH_SOCK}:/tmp/ssh-auth.sock" \
app-image bundle update
# ホストが macOS の場合
# ssh-agent のソケットファイルは固定パス `/run/host-services/ssh-auth.sock` に存在する
docker run --rm -it \
-e "SSH_AUTH_SOCK=/tmp/ssh-auth.sock" \
-v "/run/host-services/ssh-auth.sock:/tmp/ssh-auth.sock" \
app-image bundle update
これは docker compose 環境からでも利用できます。
version: '3.8'
services:
app:
build: .
environment:
SSH_AUTH_SOCK: /tmp/ssh-auth.sock
volumes:
- "${SSH_AUTH_SOCK:-/dev/null}:/tmp/ssh-auth.sock"
# ホストが Linux の場合
# シェルセッションに設定された `$SSH_AUTH_SOCK` に任せる
docker compose run --rm app bundle update
# ホストが macOS の場合
# `docker compose up` `docker compose run` に合わせて `SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock` を指定すればよい
SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock docker compose run --rm app bundle update
今回は Docker image のビルド中に、または実行中の Docker container 内から、非公開 Git リポジトリに対して SSH 接続を行う方法を紹介しました。
ssh-agent
は少しクセのあるアプリケーションでもありますが、「Docker コンテナにホストの秘密鍵をマウントする」などセキュリティ的に今一つな方法を採らなくても、コンテナ内から SSH 接続ができるようになる点は非常に便利だと思います。
この記事が、ここまで読んで頂いた皆様のお役に立ちますと幸いです。