Rapid7がDockerを使用してセットアップ時間を数日から数分に短縮した方法

この投稿は、Rapid7のプリンシパルソフトウェアエンジニアであるKris Riveraが共同執筆しました

ラピッド7 1

Rapid7 は、ボストンを拠点とするセキュリティ分析および自動化ソリューションのプロバイダーであり、組織がサイバーセキュリティへの積極的なアプローチを実装できるようにします。 10,000社以上のお客様がRapid7のテクノロジー、サービス、リサーチを利用して、セキュリティの成果を向上させ、組織を安全に発展させています。

セキュリティ空間は絶えず変化しており、 毎日新しい脅威 が発生しています。 顧客のニーズを満たすために、Rapid7は、品質を維持しながら、ソフトウェアビルドの信頼性と速度を向上させることに重点を置いています。

そのため、Rapid7はDockerに目を向けました。 チームは Docker を使用して、開発を支援し、販売パイプラインをサポートし、テスト環境を提供し、自動化された信頼性の高い方法で本番環境にデプロイします。 

Dockerを使用することで、Rapid7は手動プロセスを自動化することでオンボーディングプロセスを変革しました。 新しい開発環境のセットアップに数日ではなく数分で済むようになりました。 開発者は、定期的なコードリリースで変化する要件をサポートできるようにする、より高速なビルドを作成できます。

オンボーディングプロセスの自動化

開発者がRapid7に初めて参加したとき、時間とエラーが発生しやすい静的な手動プロセスに遭遇しました。 開発環境の構成は、ほとんどの開発者にとってエキサイティングではありません。 彼らはほとんどの時間を創造に費やしたいと思っています! そして、環境のセットアップは、プロセスの中で最も魅力的でない部分です。

Dockerは、この面倒なプロセスの自動化を支援しました。 Dockerを使用することで、Rapid7は適切なOSと開発者ツールで事前構成されたコンテナ化されたシステムを作成することができました。 Docker Compose を使用すると、複数のコンテナーが相互に通信でき、カスタム スクリプトおよびデバッグ ツールを組み込むために必要なフックがありました。

オンボーディングのセットアップが Docker を介して構成されると、他の開発者が複製するプロセスは簡単でした。 かつては数日かかっていたものが、今では数分で完了します。

コンテナの本番環境への拡張

Rapid7チームは、 Dockerfileを使用して開発環境のセットアップを合理化しました。 これにより、必要なすべての依存関係とソフトウェアパッケージを含むイメージを作成することができました。

しかし、彼らはそこで止まりませんでした。 この単一のDockerイメージがより複雑なシステムに進化するにつれて、より多くのDockerイメージとコンテナオーケストレーションが必要になることに気付きました。 そのとき、彼らはDocker Composeをセットアップに統合しました。

Docker Composeは、Rapid7の各環境向けに簡素化されたDockerイメージビルドです。 また、さまざまな初期化手順を個別の境界付きコンテキストに分割する高レベルのサービス分離も促進されました。 さらに、コンテナ間通信、プライベートネットワーク、Dockerボリューム、アンカーを使用した環境変数の定義、通信とエイリアシングのためのコンテナのリンクにDocker Composeを活用できます。

これはRapid7にとって真のゲームチェンジャーでした、なぜならDocker Composeは本当に彼らに前例のない柔軟性を与えたからです。 その後、Teams は、トリガー イベントが発生したとき (サービスの完了時など) にコンテナー間の通信を調整するスクリプトを追加しました。

Rapid7は、Docker、Docker Compose、およびスクリプトを使用して、完全な開発環境を確実に複製できる開発チーム向けのソリューションを作成することができました。 初期化を最適化するために、Rapid7は、Dockerがすぐに使用できる時間よりも起動時間を短縮したいと考えていました。

ビルド時間のさらなる最適化

Docker ベース イメージを作成した後、最下位レイヤーを変更する必要はほとんどありません。 基本的に、その初期ビルドは 1 回限りのコストです。 画像が変更されても、キャッシュされたレイヤーを使用すると、そのプロセスをすばやく簡単に実行できます。 ただし、すべてのソフトウェアの依存関係を最初から再インストールする必要があり、これは Docker イメージの更新ごとに 1 回限りのコストです。

インストールされているソフトウェアの依存関係を基本イメージにコミットすることで、単純で増分的な、多くの場合スキップ可能なステージが可能になります。 Docker イメージは、開発用コンピューター上で、開発用コンピューター上で常に使用できます。

これらすべての効率性により、すでに高速だった15分のプロセスが5分に合理化され、開発者は生産性をより迅速に高めることが容易になりました。

自分で構築する方法

この設定を自分で複製する方法に関するコード例と説明を確認してください 。次に、開始するために必要な重要な手順に取り組みます。

ドッカーのダウンロード

最新バージョンの Docker をダウンロードしてインストール し、Docker-in-Docker を実行できるようにします。Docker-in-Docker を使用すると、Docker 環境でコンテナー内に Docker をインストールできます。 これにより、コンテナーで他のコンテナーを実行したり、イメージをプルしたりできます。

Docker-in-Dockerを有効にするには、 apt install docker.io ディストリビューションを最初のコマンド Dockerfileの1つとして使用します。コンテナーを構成したら、ホストのインストールから Docker ソケットをマウントします。

# Dockerfile

FROM ubuntu:20.04

# Install dependencies

RUN apt update &&  \
           apt install -y docker.io

次に、CLI またはシェル スクリプト ファイルで次のコマンドを実行して、Docker イメージをビルドします。

docker build -t <docker-image-name>

次に、次のコマンドを使用して Docker コンテナーを起動します。

docker run -v /var/run/docker.sock:/var/run/docker.sock -ti <docker-image-name>

ドッカーコミットスクリプトの使用

基本イメージに階層化された変更をコミットすることは、Docker の開発環境の コアを駆動するものです。 Docker はサービス名に基づいてコンテナー ID をフェッチし、実行中のコンテナーに加えた変更は目的のイメージにコミットされます。 

コマンドの実行時に docker commit ホスト Docker ソケットがコンテナーにマウントされるため、コンテナーはホスト Docker インストールにある基本イメージに変更を適用します。

# ! /bin/bash

SERVICE=${1}
IMAGE=${2}

# Commit changes to image
CONTAINER_ID=$(docker ps -aqf “name=${SERVICE}”)

if [ ! -z “$CONTAINER_ID”]; then
	Echo “--- Committing changes from $SERVICE to $IMAGE --- ”
	docker commit $CONTAINER_ID $IMAGE
fi

環境の更新

ホストのインストールから Docker ソケットをマウントします。 ソース コードのマウントは、コンテンツがコンテナー間で共有されることを Docker に伝えるプロパティがないと :z 不十分です。

ホスト マシンの Docker ソケットをコンテナーにマウントする必要があります。 これにより、コンテナー内で実行されるすべての Docker 操作で、ホスト Docker イメージとインストールが実際に変更されます。 これがないと、コンテナーに加えられた変更は、コンテナーが停止されて削除されるまで、コンテナーにのみ保持されます。

次のコードを Docker 作成ファイルに追加します。

# docker-compose.yaml

services:
  service-name:
    image: image-with-docker:latest
    volumes:
        - /host/code/path:/container/code/path:z
        - /var/run/docker.sock:/var/run/docker.sock

コンポーネントのオーケストレーション

Docker Compose で適切なサービスが構成されたら、2 つの異なる方法で環境を開始できます。 docker-compose up コマンドを使用するか、次のコマンドを使用して、リンクされたサービスで個々のサービスを実行して環境を開始します。

docker compose start webserver

メイン コンテナーは、リンクされた名前を使用してリンクされたサービスを参照します。 これにより、指定された名前で環境変数を非常に簡単にオーバーライドできます。 以下のYAMLファイルをチェックしてください:

services:
  webserver:
  mysql:
    ports:
      - '3306:3306'
    volume
	 - dbdata:var/lib/mysql
  redis:
    ports:
      - 6379:6379
    volumes:
	 - redisdata:/data

volumes:
  dbdata:
  redisdata:

筆記: サービスごとに、希望する Docker 公式イメージのバージョンを選択して指定する必要があります。 さらに、 MySQL Docker 公式イメージ には、デフォルトで設定された重要な環境変数が付属していますが、必要に応じてここで指定できます。

個別のサービスの管理

スタックの小さな部分を開始することは、開発者がその特定の部分のみを必要とする場合にも役立ちます。 たとえば、MySQL サービスを開始するだけの場合は、次のコマンドを実行します。

docker compose start mysql

次のコマンドを使用すると、このサービスを簡単に停止できます。

docker compose stop mysql

環境の構成

ボリュームをデータベースサービスにマウントすると 、コンテナはそれぞれのデータベースに変更を適用し、それらのデータベースは一時的なコンテナとして残すことができます。

メイン エントリ ポイントとスクリプト オーケストレーターで、環境変数を設定する PROD_BUILD ための属性 ./start.sh を指定します -p 。ビルドは、エントリ ポイント内の変数を読み取り、必要に応じて開発環境の運用バージョンまたは開発バージョンをビルドします。  

まず、そのスクリプトは次のようになります。

# start.sh

while [ "$1" != ""];
do
	case $1 in
		-p |  --prod) PROD_BUILD="true";;

	esac
		shift
done

次に、シェルスクリプトのサンプルを次に示します。

export PROD_BUILD=$PROD_BUILD

3 番目に、サンプルの Docker 作成ファイルを次に示します。

# docker-compose.yaml

services:
  build-frontend:
    entrypoint:
      - bash
      - -c
      - "[[ \"$PROD_BUILD\" == \"true\" ]] && make fe-prod || make fe-dev"

手記: 完全に機能するDocker作成ファイルの作成を目指している場合は、下に好みの画像 build-frontend を追加することを忘れないでください。

発生した問題のトラブルシューティングが必要な場合はどうなりますか? 実行中のコンテナー内でのデバッグに必要なのは、マウントされたソース コード内の適切なデバッグ ライブラリと、デバッガーをマウントするための開いているポートだけです。 これが私たちのYAMLファイルです:

# docker-compose.yaml

services:
  webserver:
    ports:
	- '5678:5678'
    links:
	- mysql
	 - redis
	entrypoint:
      - bash
      - -c
      - ./start-webserver.sh

手記: 前の例と同様に、機能的なDocker作成ファイルを作成するときに、その下に webserver 画像を指定することを忘れないでください。

選択したエディターで、指定したポートを使用してデバッガーをアタッチするための起動構成を指定します。 コンテナーが実行されたら、構成を実行すると、デバッガーがアタッチされます。

#launch-setting.json
{
	"configurations" : [
		{
			"name":  "Python: Remote Attach",
			"type": "python",
			"request": "attach",
			"port": 5678,
			"host": "localhost",
			"pathMappings": [
				{
					"localRoot": "${workspaceFolder}",
					"remoteRoot": "."
				}
			]
		}
	]
}

すべてが機能することを確認する

フルスタックが実行されると、定義された webserver ポートのブラウザを介してメインエントリポイントWebサーバーに簡単にアクセスできます。

このコマンドを実行すると docker ps 、実行中のコンテナーが表示されます。 Docker はコンテナー間の通信を管理しています。 

これで、ソフトウェアサービス全体がDockerで完全に実行されました。 すべてのコードは、Docker コンテナー内のホスト コンピューターに存在します。 開発環境は、Docker のみを使用して完全に移植できるようになりました。

重要なトレードオフを記憶する

この方法にはいくつかの制限があります。 まず、Docker で開発者環境を実行すると、追加のリソース オーバーヘッドが発生します。 Dockerを実行する必要があり、その結果、追加のコンピューティングリソースが必要になります。 また、複数のステージを含めるには、コンテナー間の通信を可能にするための最終的なオーケストレーション レイヤーとしてスクリプトを作成する必要があります。

まとめ

Rapid7の開発チームは、Dockerを使用して開発環境を迅速に作成しています。 Docker CLI、Docker Desktop、Docker Compose、およびシェルスクリプトを使用して、非常にユニークで堅牢なDockerフレンドリーな環境を作成します。これを使用して、開発環境の任意の部分をスピンアップできます。

このセットアップは、Rapid7がフロントエンドアセットをコンパイルしたり、キャッシュサーバーとデータベースサーバーを起動したり、さまざまなパラメーターでバックエンドサービスを実行したり、アプリケーションスタック全体を起動したりするのにも役立ちます。 実行中のコンテナー内に Docker ソケットをマウントする "Docker-in-Docker" アプローチを使用すると、これが可能になります。 依存関係が更新またはインストールされた後にレイヤーを基本イメージにコミットする Docker の機能も重要です。 

シェルスクリプトは、必要な環境変数をエクスポートし、特定のプロセスを特定の順序で実行します。 最後に、Docker Compose は、適切なサービス コンテナーと依存関係が実行されていることを確認します。

将来の開発目標の達成

Dockerツールチェーンに依存することは、アプリケーションスタックのあらゆる部分と互換性のある一貫した環境を作成するのに役立つため、Rapid7にとって本当に有益です。 この統合により、Rapid7は次のことを行うことができました。 

  • 非常に信頼性の高いソフトウェアを高度な顧客環境に導入
  • 開発中にマージする前にコードを分析する
  • より安定したコードを提供
  • オンボーディングの簡素化 
  • 非常に柔軟で構成可能な開発環境を形成

Dockerを使用することで、Rapid7はプロセスを継続的に改良し、可能なことの限界を超えています。 彼らの次の目標は、本番グレードの安定したビルドを毎日提供することであり、Dockerがそこに到達するのに役立つと確信しています。