TECH PLAY

電通総研

電通総研 の技術ブログ

822

電通国際情報サービス 、クロス イノベーション 本部、オープン イノベーション ラボの比嘉です。 ブロックチェーン 週報3/1いってみようか。今週から基本、火曜日に出すことにしました。 Web3 Web3のニュース PoWへの批判 PoWへの批判のニュース その他の記事 Web3 前回 、Web3とは、サービスの運営者(中央管理者)がサービスを独占せず、ユーザーもサービスの運営に関われるようなサービスのことだという話をしました。 これだとまだ抽象的なので、イメージがわかないでしょう。 DeSciの記事がWeb3の流れそのものなので、記事を見た上で僕の説明を読むとWeb3が理解できるんじゃないかと思います。 DeSciを求める科学者の背景 ver 1.0 科学者は、自分の研究や論文が多くの人たちに役立てられることを望んでいます。しかし、力の強い出版社がいるので、それがかないません。 研究にはお金がかかりますがその調達も難しくなっています。 だから、自分たちの研究が多くの人に役立てられるようなプラットフォームを望んでいます。資金調達がしやすくなることも望んでいます。 Web3は、音楽がわかりやすいので、音楽にたとえます。 アーティストは、自分の音楽を多くの人に聞いてほしいと思っています。 しかし、力の強いレーベルがいるのでそれがかないません。 レーベルが売れると思った曲しか世に出ないので、自分の自由に音楽を世に出すこともできないのです。 音楽業界では、売上のうちアーティストに払われる割合は、わずか12%です。プラットフォームやレーベルが残りを全部持っていきます。 アーティストは自由に曲を発表でき、ファンに聞いてもらえるようなプラットフォームを望んでいます。そして、そのプラットフォームから活動資金を得ることを望んでいます。プラットフォームやレーベルが過剰にお金をとっていくことは望んでいません。 ほとんど構造は一緒です。科学者やアーティストが自分のやりたいことをできないのは、強い出版社や強いレーベル/ストリーミングプラットフォームがあるからです。 「そこを排除して、サービスを自分たちの手に取り戻し、活動資金も調達しやすくしたい」この動きがWeb3なんじゃないかと思います。 Web3のニュース 「YouTubeの将来を語るうえでメタバースに触れないわけにはいきません」YouTubeがメタバース参入検討、Web3も示唆 カニエ・ウエストの最新アルバム『Donda 2』は専用プレイヤーのみで販売。配信はされません! 技術的にはWeb3とは無関係ですが、アーティストが音楽を自分で管理しようとするWeb3的な動きです。 PoWへの批判 PoWとは、Proof of Workの略で、 Bitcoin やEthereumで使われている仕組みで、ブロックを生成するときに解くのに時間がかかる暗号問題を解く必要があります。 PoWにおける電気消費量が多く、環境に優しくないと批判されています。この批判の声がより大きくなってきました。 PoWへの批判のニュース テスラ取締役のキンバル・マスク氏、同社がビットコインを購入した際の環境影響について「無知だった」と発言 EUがビットコインを禁止へ! 法案では、2025年初から、EU内ではビットコインに関するすべてのサービスは違法となる。PoWで電力を浪費し、「環境にやさしくない」のが禁止の理由のよう。 その他の記事 三菱 UFJ 信託銀行: 優待等を対象とした NFT「Progmat UT」及びデジタルアセット用ウォレットサービスの開発について NFTを破棄するとネガフィルムがもらえる。コロナ禍の東京を写したメタバース写真展 MUFG、ブロックチェーン活用の次世代決済事業を1年で停止へ 執筆: @higa 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
こんにちは、XI本部 クラウド イノベーション センターの柴田です。 本記事では Cloud Native Buildpacks の主要なBuilderの調査を行います。 Cloud Native Buildpacksとは 主要なBuilderの紹介3 gcr.io/buildpacks/builder 概要 GitHubリポジトリ Stack Build image Run image Buildpack heroku/buildpacks 概要 GitHubリポジトリ Stack Build image Run image Buildpack paketobuildpacks/builder 概要 Base (aka "bionic") Full Tiny GitHubリポジトリ Stack Build image Run image Buildpack 各Builderを評価する なぜBuilderを評価するのか 評価の観点 プログラミング言語やビルドツールへの対応状況 生成されるコンテナイメージのattack surfaceの小ささ 開発の活発さ ベンダーニュートラル 考察 今回調査しきれなかった点 おわりに Cloud Native Buildpacksとは Cloud Native Buildpacks は、HerokuとPivotalによってCNCFのincubating projectの1つとして2018年に発足しました。Herokuが開発していたBuildpacksというツールをベースにしています。 1 Cloud Native Buildpacksは簡単に言うと「Dockerfileを書かずにコンテナイメージを生成してくれるツール」です。アプリケーションの構成( プログラミング言語 やビルドツールなど)を自動的に検知し、それに適したビルドプロセスを自動的に選択して、アプリケーションのコンテナイメージをビルドします。これにより、アプリケーション開発者がDockerfileのベストプ ラク ティスを学習したり、Dockerfileを実際に記述したりする必要がなくなります。 Cloud Native Buildpacksの主要な要素にはBuildpack、Stack、Builderの3つがあります。 Buildpack :特定の構成( プログラミング言語 やビルドツールなど)のアプリケーションに対するビルドプロセスです。具体的には Detect と Build という2つのプロセスから構成されます。 Detect :ビルド対象のアプリケーションがBuildpackの対象構成に該当するか判定するロジックです。 Build :Detectが該当すると判定した場合、実際にアプリケーションのコンテナイメージをビルドするプロセスです。 Stack :Buildpackが使用するコンテナイメージです。マルチステージビルドを前提としており、 Build image と Run image という2つのイメージから構成されます。 Build image :アプリケーションのビルドに使用するコンテナイメージです。 Run image :アプリケーションの実行に使用するコンテナイメージです。 Builder :「Stack」と「Buildpackの集合」の組み合わせです。 2 主要なBuilderの紹介 3 Cloud Native Buildpacksは、コンテナイメージをビルドするための仕組みや、そのためのツールを提供します。一方で、実際にコンテナイメージのビルドを行うBuilderの実装は、Cloud Native Buildpacksから提供されていません。代わりに Google やHerokuなど3rd partyが実装したものを使用します。 pack builder suggest コマンドを実行すると、推奨されているBuilderを確認できます。なお pack はCloud Native Buildpacksによって提供される CLI ツールです。 Builder 開発者 説明 gcr.io/buildpacks/builder:v1 Google Ubuntu 18 base image with buildpacks for .NET , Go, Java , Node.js, and Python heroku/buildpacks:18 Heroku Base builder for Heroku-18 stack, based on ubuntu :18.04 base image heroku/buildpacks:20 Heroku Base builder for Heroku-20 stack, based on ubuntu :20.04 base image paketobuildpacks/builder:base Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , Ruby , NGINX and Procfile paketobuildpacks/builder:full Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , PHP , Ruby , Apache HTTPD , NGINX and Procfile paketobuildpacks/builder:tiny Paketo Buildpacks Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java , Java Native Image and Go それぞれ詳細を見ていきましょう。 gcr.io/buildpacks/builder 概要 Google が開発しているBuilderです。 Builder 開発者 説明 gcr.io/buildpacks/builder:v1 Google Ubuntu 18 base image with buildpacks for .NET , Go, Java , Node.js, and Python GitHub リポジトリ Builder、Stack、Buildpackはすべて以下の GitHub リポジトリ で管理されています。 https://github.com/GoogleCloudPlatform/buildpacks Stack Build imageとRun imageの概要は以下のとおりです。 Build image gcr.io/buildpacks/builder:v1 イメージ gcr.io/buildpacks/gcp/build:v1 Dockerfile build.Dockerfile ベースイメージ 1 gcr.io/gcp-runtimes/ubuntu_18_0_4 パッケージリスト parent.Dockerfile#L24-L31 + build.Dockerfile#L24-L37 パッケージ数 1 190 イメージサイズ 482MB Run image gcr.io/buildpacks/builder:v1 イメージ gcr.io/buildpacks/gcp/run:v1 Dockerfile run.Dockerfile ベースイメージ gcr.io/gcp-runtimes/ubuntu_18_0_4 パッケージリスト parent.Dockerfile#L24-L31 パッケージ数 123 イメージサイズ 120MB Buildpack プログラミング言語 やビルドツールのサポートは以下のとおりです。 gcr.io/buildpacks/builder:v1 C++ o .NET o Java o (AdoptOpenJDK + Maven /Gradle または GraalVM + Maven ) Go o Node.js o (npm/Yarn) Python o Ruby x PHP x heroku/buildpacks 概要 Heroku社が開発しているBuilderです。 Builder 開発者 説明 heroku/buildpacks:18 Heroku Base builder for Heroku-18 stack, based on ubuntu :18.04 base image heroku/buildpacks:20 Heroku Base builder for Heroku-20 stack, based on ubuntu :20.04 base image GitHub リポジトリ Builder、Stack、Buildpackはそれぞれ以下の GitHub リポジトリ で管理されています。 heroku/buildpacks:18 heroku/buildpacks:20 Builder https://github.com/heroku/pack-images 同左 Stack https://github.com/heroku/pack-images https://github.com/heroku/stack-images 同左 Buildpack https://github.com/heroku 配下の各 リポジトリ 同左 Stack Build imageとRun imageの概要は以下のとおりです。 Build image heroku/buildpacks:18 heroku/buildpacks:20 イメージ heroku/pack:18-build heroku/pack:20-build Dockerfile Dockerfile.build 同左 ベースイメージ ubuntu:18.04 ubuntu:20.04 パッケージリスト installed-packages.txt installed-packages.txt パッケージ数 578 590 イメージサイズ 1.19GB 1.35GB Run image heroku/buildpacks:18 heroku/buildpacks:20 イメージ heroku/pack:18 heroku/pack:20 Dockerfile Dockerfile.run 同左 ベースイメージ ubuntu:18.04 ubuntu:20.04 パッケージリスト installed-packages.txt installed-packages.txt パッケージ数 354 354 イメージサイズ 510MB 566MB Buildpack プログラミング言語 やビルドツールのサポートは以下のとおりです。 heroku/buildpacks:18 heroku/buildpacks:20 C++ x x .NET x x Java o (OpenJDK + Maven /Gradle) o (OpenJDK + Maven /Gradle) Go o o Node.js o (npm/Yarn) o (npm/Yarn) Python o o Ruby o o PHP o o paketobuildpacks/builder 概要 Paketo Buildpacks はBuildpackを開発するCloud Foundryの OSS プロジェクトの1つです。 以下はPaketo Buildpacksが開発しているBuilderです。 Builder 開発者 説明 paketobuildpacks/builder:base Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , Ruby , NGINX and Procfile paketobuildpacks/builder:full Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , PHP , Ruby , Apache HTTPD , NGINX and Procfile paketobuildpacks/builder:tiny Paketo Buildpacks Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java , Java Native Image and Go https://github.com/paketo-buildpacks/stacks をみると各Builderの ユースケース とStackについて以下のように書かれています。 Base (aka "bionic") Ideal for: - .NET Core apps - Java apps and Go apps that require some C libraries - Node.js/ Python / Ruby /etc. apps without many native extensions Contains: - Build: ubuntu :bionic + openssl + CA certs + compilers + shell utilities - Run: ubuntu :bionic + openssl + CA certs Full Ideal for: - PHP /Node.js/ Python / Ruby /etc. apps with many native extensions Contains: - Build: ubuntu :bionic + many common C libraries and utilities - Run: ubuntu :bionic + many common libraries and utilities Tiny Ideal for: - Most Go apps - Java apps and Java GraalVM Native Images Contains: - Build: ubuntu :bionic + openssl + CA certs + compilers + shell utilities - Run: distroless-like bionic + glibc + openssl + CA certs GitHub リポジトリ Builder、Stack、Buildpackはそれぞれ以下の GitHub リポジトリ で管理されています。 paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny Builder https://github.com/paketo-buildpacks/base-builder https://github.com/paketo-buildpacks/full-builder https://github.com/paketo-buildpacks/tiny-builder Stack https://github.com/paketo-buildpacks/stacks 同左 同左 Buildpack https://github.com/paketo-buildpacks 配下の各 リポジトリ 同左 同左 Stack Build imageとRun imageの概要は以下のとおりです。 Build image paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny イメージ paketobuildpacks/build:base-cnb paketobuildpacks/build:full-cnb paketobuildpacks/build:tiny-cnb ( base-cnb と同じ) Dockerfile Dockerfile 同左 ( base-cnb と同じ) ベースイメージ ubuntu:bionic 同左 ( base-cnb と同じ) パッケージリスト build build ( base-cnb と同じ) パッケージ数 172 703 ( base-cnb と同じ) イメージサイズ 327MB 1.02GB ( base-cnb と同じ) Run image paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny イメージ paketobuildpacks/run:base-cnb paketobuildpacks/run:full-cnb paketobuildpacks/run:tiny-cnb Dockerfile Dockerfile 同左 Dockerfile ベースイメージ ubuntu:bionic 同左 scratch パッケージリスト run run packagelist パッケージ数 96 586 不明 1 イメージサイズ 88.9MB 691MB 17.4MB Buildpack プログラミング言語 やビルドツールのサポートは以下のとおりです。 nginxや HTTPD のコンテナイメージのビルドにも対応している点が特徴的ですね。 paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny C++ x x x .NET o o x Java o (Liberica JDK + Maven /Gradle) o (Liberica JDK + Maven /Gradle) o (Liberica JDK + Maven /Gradle) Go o o o Node.js o (npm/Yarn) o (npm/Yarn) x Python o o x Ruby o o x PHP x o x nginx o o x HTTPD x o x 各Builderを評価する ここからは、前述した主要なBuilderをいくつかの観点で評価します。 なぜBuilderを評価するのか 前述したように、実際にコンテナイメージのビルドを行うのは、 Google やHerokuなど3rd partyが実装したBuilderです。Builderによってビルドプロセスやベースとなるコンテナイメージに差異があるため、生成されるコンテナイメージの品質は使用したBuilderやBuildpackに大きく依存します。 よって本記事では、より品質の高いコンテナイメージを生成するために、前述した主要なBuilderの評価を行います。 評価の観点 以下の観点で各Builderを評価します。 プログラミング言語 やビルドツールへの対応状況 生成されるコンテナイメージのattack surface の小ささ 開発の活発さ ベンダー ニュートラ ル なお、attack surface とは、攻撃を試みることができる対象範囲のことです。例えば、アプリケーションに不必要なパッケージやファイルがコンテナイメージに含まれていると、それらに 脆弱性 があった場合、攻撃されるリスクが高まります。そのため、コンテナイメージには必要なパッケージやファイルのみを含めるべきです。 プログラミング言語 やビルドツールへの対応状況 Builderは自分たちが使用する プログラミング言語 やビルドツールに対応しているものを選ぶ必要があります。 各Builderの プログラミング言語 やビルドツールへの対応状況を改めてまとめます。 C++ .NET Java Go Node.js Python Ruby PHP nginx HTTPD gcr.io/buildpacks/builder:v1 o o o (AdoptOpenJDK + Maven /Gradle または GraalVM + Maven ) o o (npm/Yarn) o x x x x heroku/buildpacks:18 x x o (OpenJDK + Maven /Gradle) o o (npm/Yarn) o o o x x heroku/buildpacks:20 x x o (OpenJDK + Maven /Gradle) o o (npm/Yarn) o o o x x paketobuildpacks/builder:base x o o (Liberica JDK + Maven /Gradle) o o (npm/Yarn) o o x o x paketobuildpacks/builder:full x o o (Liberica JDK + Maven /Gradle) o o (npm/Yarn) o o o o o paketobuildpacks/builder:tiny x x o (Liberica JDK + Maven /Gradle) o x x x x x x どのBuilderもGoと Java には対応しています。それ以外の プログラミング言語 やビルドツールはBuilderによって対応有無が異なります。 生成されるコンテナイメージのattack surface の小ささ 生成されるコンテナイメージは、余計なファイルやパッケージを含んでおらず、attack surface は小さいほうが望ましいです。 各BuilderのRun imageの情報を改めてまとめます。 ベースイメージ パッケージ数 イメージサイズ gcr.io/buildpacks/builder:v1 gcr.io/gcp-runtimes/ubuntu_18_0_4 123 120MB heroku/buildpacks:18 ubuntu:18.04 354 510MB heroku/buildpacks:20 ubuntu:20.04 354 566MB paketobuildpacks/builder:base ubuntu:bionic 96 88.9MB paketobuildpacks/builder:full ubuntu:bionic 586 691MB paketobuildpacks/builder:tiny scratch 不明 17.4MB Builder paketobuildpacks/builder:tiny のRun imageは scratch に必要なパッケージのみを追加する形で作成しており、イメージサイズが最も小さいです。具体的なパッケージ数は確認できていませんが最も少ないと推測されます。 それ以外のBuilderのRun imageは ubuntu:bionic やそれに近いイメージをベースとしており、 bash などアプリケーションの実行に必須でないパッケージを含んでいます。 開発の活発さ Builderは、開発が活発で、機能改善や 脆弱性 の修正が積極的に行われているものが望ましいです。 各Builderを管理している GitHub リポジトリ のStar数と直近1ヶ月のコミット数 4 を見てみましょう。 URL Star数 コミット数 gcr.io/buildpacks/builder:v1 https://github.com/GoogleCloudPlatform/buildpacks 699 40 heroku/buildpacks:18 https://github.com/heroku/pack-images 26 8 heroku/buildpacks:20 https://github.com/heroku/pack-images 26 8 paketobuildpacks/builder:base https://github.com/paketo-buildpacks/base-builder 13 20 paketobuildpacks/builder:full https://github.com/paketo-buildpacks/full-builder 16 24 paketobuildpacks/builder:tiny https://github.com/paketo-buildpacks/tiny-builder 13 15 HerokuとPaketo BuildpacksはBuilder・Stack・Buildpackを個別の リポジトリ で管理していますが、 Google はそれらを1つの リポジトリ で管理しています。そのため単純な比較は出来ませんが、どのBuilderも継続的に更新されていることがわかります。 また、 Google CloudではBuildpacksによるコンテナイメージのビルドをサポートしています。 お知らせ: Google Cloud の Buildpacks でコンテナ イメージ作成が簡単に | Google Cloud Blog このサポートが続く限り、Builder gcr.io/buildpacks/builder:v1 は、 Google Cloud上で実行するのに適したコンテナイメージを生成できるよう、 Google によってメンテナンスされることが期待できます。 ベンダー ニュートラ ル Paketo Buildpacksはベンダー ニュートラ ルを謳っています。 公式ドキュメント には以下のように記述されています。 The project has vendor-neutral governance through the Cloud Foundry Foundation 一方で、 Google やHerokuのBuilderは、それぞれのベンダーの クラウド プラットフォーム上で実行するコンテナイメージの作成を主な対象としています。例えばBuilder gcr.io/buildpacks/builder:v1 の GitHub リポジトリ https://github.com/GoogleCloudPlatform/buildpacks には、 Google Cloud向けである旨が以下のように記述されています。 This repository contains a set of builders and buildpacks designed to run on Google Cloud's container platforms: Cloud Run , GKE , Anthos , and Compute Engine running Container-Optimized OS . They are also used as the build system for App Engine and Cloud Functions . ただし現時点ではこれらのBuilderを使ってベンダーに依存しないコンテナイメージを生成することも可能です。 考察 Goや Java で記述されたアプリケーションであれば、Run imageのattack surface の小さい paketobuildpacks/builder:tiny を使用するのがよいでしょう。それ以外のBuilderは、Run imageが bash などアプリケーションの実行に必須でないパッケージを含んでいるため、仕事で使用するにはあまり適さないでしょう。 また、仕事で使用するコンテナイメージは十分な品質が求められます。そのため、既存のBuilderを使用する場合は、使用するRun imageの構成やBuildpackの実装を確認するのがよいでしょう。また、各Builderは積極的にメンテナンスされているため、確認は1回だけでなく継続的に行うのがよいでしょう。 ただ本記事の趣旨を否定するようですが、それを行うぐらいならば、自分たちのサービスに適したBuilderを自作して使用するほうがコンテナイメージの品質をより制御できてよいと私は考えています。もちろん、Builderを自作する分の手間はかかります。そのため、もし1つのアプリケーションでしかそのBuilderを使わないなら、Dockerfileを書いたほうが手間は少ないでしょう。一方で社内に同じ プログラミング言語 やビルドツールを使うプロジェクトが複数あるなら、Builderを自作して使用すると、それらのコンテナイメージの品質を横断的に保証できます。 本記事では説明しませんが、Builderを自作するのはそれほど難しくありません。詳しくは公式ドキュメントや Buildpacksのビルダーをスクラッチから作ってみる | フューチャー技術ブログ などを参照ください。 また、近年のソフトウェアは早いスピードで進化しています。今後、より高い品質のコンテナイメージを生成できるBuilderが登場し、それが デファクトスタンダード となる可能性もあります。 今回調査しきれなかった点 生成されるコンテナイメージの品質は主にRun imageとBuildpackによって決まります。今回、Run imageについてはある程度調査できましたが、Builderに含まれる各Buildpackのビルドプロセスの詳細(実装やその品質など)までは調査できませんでした。これは今後の課題にしたいと思います。 おわりに 本記事では Cloud Native Buildpacks の主要なBuilderの調査を行いました。 Cloud Native Buildpacksはコンテナイメージのビルドプロセスを標準化・自動化してくれます。これにより、アプリケーション開発者はDockerfileを意識する必要がなくなり、アプリケーション開発に集中できます。これはとても価値のあることです。今後もCloud Native Buildpacksの進化に注目していきます。 執筆: @shibata.takao 、レビュー: @sato.taichi ( Shodo で執筆されました ) 図は https://buildpacks.io/ のものを引用しています。 ↩ 直接のベースイメージではなく「ベースイメージのベースイメージ」のようにいくつか遡った先で使用されているベースイメージです。他の表についても同様です。 ↩ dpkg -l コマンドで出力されたパッケージの数です。他の表についても同様です。 ↩ dpkg コマンドがインストールされておらず dpkg -l コマンドでパッケージ数を確認できなかったため不明としています。 ↩ 図は https://buildpacks.io/docs/concepts/components/builder/ のものを引用しています。 ↩ 全て2022年1月30日時点の情報です。 ↩ 2021年12月30日から2022年1月30日までのコミット数です。 ↩
アバター
こんにちは、XI本部 クラウド イノベーション センターの柴田です。 本記事では Cloud Native Buildpacks の主要なBuilderの調査を行います。 Cloud Native Buildpacksとは 主要なBuilderの紹介3 gcr.io/buildpacks/builder 概要 GitHubリポジトリ Stack Build image Run image Buildpack heroku/buildpacks 概要 GitHubリポジトリ Stack Build image Run image Buildpack paketobuildpacks/builder 概要 Base (aka "bionic") Full Tiny GitHubリポジトリ Stack Build image Run image Buildpack 各Builderを評価する なぜBuilderを評価するのか 評価の観点 プログラミング言語やビルドツールへの対応状況 生成されるコンテナイメージのattack surfaceの小ささ 開発の活発さ ベンダーニュートラル 考察 今回調査しきれなかった点 おわりに Cloud Native Buildpacksとは Cloud Native Buildpacks は、HerokuとPivotalによってCNCFのincubating projectの1つとして2018年に発足しました。Herokuが開発していたBuildpacksというツールをベースにしています。 1 Cloud Native Buildpacksは簡単に言うと「Dockerfileを書かずにコンテナイメージを生成してくれるツール」です。アプリケーションの構成( プログラミング言語 やビルドツールなど)を自動的に検知し、それに適したビルドプロセスを自動的に選択して、アプリケーションのコンテナイメージをビルドします。これにより、アプリケーション開発者がDockerfileのベストプ ラク ティスを学習したり、Dockerfileを実際に記述したりする必要がなくなります。 Cloud Native Buildpacksの主要な要素にはBuildpack、Stack、Builderの3つがあります。 Buildpack :特定の構成( プログラミング言語 やビルドツールなど)のアプリケーションに対するビルドプロセスです。具体的には Detect と Build という2つのプロセスから構成されます。 Detect :ビルド対象のアプリケーションがBuildpackの対象構成に該当するか判定するロジックです。 Build :Detectが該当すると判定した場合、実際にアプリケーションのコンテナイメージをビルドするプロセスです。 Stack :Buildpackが使用するコンテナイメージです。マルチステージビルドを前提としており、 Build image と Run image という2つのイメージから構成されます。 Build image :アプリケーションのビルドに使用するコンテナイメージです。 Run image :アプリケーションの実行に使用するコンテナイメージです。 Builder :「Stack」と「Buildpackの集合」の組み合わせです。 2 主要なBuilderの紹介 3 Cloud Native Buildpacksは、コンテナイメージをビルドするための仕組みや、そのためのツールを提供します。一方で、実際にコンテナイメージのビルドを行うBuilderの実装は、Cloud Native Buildpacksから提供されていません。代わりに Google やHerokuなど3rd partyが実装したものを使用します。 pack builder suggest コマンドを実行すると、推奨されているBuilderを確認できます。なお pack はCloud Native Buildpacksによって提供される CLI ツールです。 Builder 開発者 説明 gcr.io/buildpacks/builder:v1 Google Ubuntu 18 base image with buildpacks for .NET , Go, Java , Node.js, and Python heroku/buildpacks:18 Heroku Base builder for Heroku-18 stack, based on ubuntu :18.04 base image heroku/buildpacks:20 Heroku Base builder for Heroku-20 stack, based on ubuntu :20.04 base image paketobuildpacks/builder:base Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , Ruby , NGINX and Procfile paketobuildpacks/builder:full Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , PHP , Ruby , Apache HTTPD , NGINX and Procfile paketobuildpacks/builder:tiny Paketo Buildpacks Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java , Java Native Image and Go それぞれ詳細を見ていきましょう。 gcr.io/buildpacks/builder 概要 Google が開発しているBuilderです。 Builder 開発者 説明 gcr.io/buildpacks/builder:v1 Google Ubuntu 18 base image with buildpacks for .NET , Go, Java , Node.js, and Python GitHub リポジトリ Builder、Stack、Buildpackはすべて以下の GitHub リポジトリ で管理されています。 https://github.com/GoogleCloudPlatform/buildpacks Stack Build imageとRun imageの概要は以下のとおりです。 Build image gcr.io/buildpacks/builder:v1 イメージ gcr.io/buildpacks/gcp/build:v1 Dockerfile build.Dockerfile ベースイメージ 1 gcr.io/gcp-runtimes/ubuntu_18_0_4 パッケージリスト parent.Dockerfile#L24-L31 + build.Dockerfile#L24-L37 パッケージ数 1 190 イメージサイズ 482MB Run image gcr.io/buildpacks/builder:v1 イメージ gcr.io/buildpacks/gcp/run:v1 Dockerfile run.Dockerfile ベースイメージ gcr.io/gcp-runtimes/ubuntu_18_0_4 パッケージリスト parent.Dockerfile#L24-L31 パッケージ数 123 イメージサイズ 120MB Buildpack プログラミング言語 やビルドツールのサポートは以下のとおりです。 gcr.io/buildpacks/builder:v1 C++ o .NET o Java o (AdoptOpenJDK + Maven /Gradle または GraalVM + Maven ) Go o Node.js o (npm/Yarn) Python o Ruby x PHP x heroku/buildpacks 概要 Heroku社が開発しているBuilderです。 Builder 開発者 説明 heroku/buildpacks:18 Heroku Base builder for Heroku-18 stack, based on ubuntu :18.04 base image heroku/buildpacks:20 Heroku Base builder for Heroku-20 stack, based on ubuntu :20.04 base image GitHub リポジトリ Builder、Stack、Buildpackはそれぞれ以下の GitHub リポジトリ で管理されています。 heroku/buildpacks:18 heroku/buildpacks:20 Builder https://github.com/heroku/pack-images 同左 Stack https://github.com/heroku/pack-images https://github.com/heroku/stack-images 同左 Buildpack https://github.com/heroku 配下の各 リポジトリ 同左 Stack Build imageとRun imageの概要は以下のとおりです。 Build image heroku/buildpacks:18 heroku/buildpacks:20 イメージ heroku/pack:18-build heroku/pack:20-build Dockerfile Dockerfile.build 同左 ベースイメージ ubuntu:18.04 ubuntu:20.04 パッケージリスト installed-packages.txt installed-packages.txt パッケージ数 578 590 イメージサイズ 1.19GB 1.35GB Run image heroku/buildpacks:18 heroku/buildpacks:20 イメージ heroku/pack:18 heroku/pack:20 Dockerfile Dockerfile.run 同左 ベースイメージ ubuntu:18.04 ubuntu:20.04 パッケージリスト installed-packages.txt installed-packages.txt パッケージ数 354 354 イメージサイズ 510MB 566MB Buildpack プログラミング言語 やビルドツールのサポートは以下のとおりです。 heroku/buildpacks:18 heroku/buildpacks:20 C++ x x .NET x x Java o (OpenJDK + Maven /Gradle) o (OpenJDK + Maven /Gradle) Go o o Node.js o (npm/Yarn) o (npm/Yarn) Python o o Ruby o o PHP o o paketobuildpacks/builder 概要 Paketo Buildpacks はBuildpackを開発するCloud Foundryの OSS プロジェクトの1つです。 以下はPaketo Buildpacksが開発しているBuilderです。 Builder 開発者 説明 paketobuildpacks/builder:base Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , Ruby , NGINX and Procfile paketobuildpacks/builder:full Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , PHP , Ruby , Apache HTTPD , NGINX and Procfile paketobuildpacks/builder:tiny Paketo Buildpacks Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java , Java Native Image and Go https://github.com/paketo-buildpacks/stacks をみると各Builderの ユースケース とStackについて以下のように書かれています。 Base (aka "bionic") Ideal for: - .NET Core apps - Java apps and Go apps that require some C libraries - Node.js/ Python / Ruby /etc. apps without many native extensions Contains: - Build: ubuntu :bionic + openssl + CA certs + compilers + shell utilities - Run: ubuntu :bionic + openssl + CA certs Full Ideal for: - PHP /Node.js/ Python / Ruby /etc. apps with many native extensions Contains: - Build: ubuntu :bionic + many common C libraries and utilities - Run: ubuntu :bionic + many common libraries and utilities Tiny Ideal for: - Most Go apps - Java apps and Java GraalVM Native Images Contains: - Build: ubuntu :bionic + openssl + CA certs + compilers + shell utilities - Run: distroless-like bionic + glibc + openssl + CA certs GitHub リポジトリ Builder、Stack、Buildpackはそれぞれ以下の GitHub リポジトリ で管理されています。 paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny Builder https://github.com/paketo-buildpacks/base-builder https://github.com/paketo-buildpacks/full-builder https://github.com/paketo-buildpacks/tiny-builder Stack https://github.com/paketo-buildpacks/stacks 同左 同左 Buildpack https://github.com/paketo-buildpacks 配下の各 リポジトリ 同左 同左 Stack Build imageとRun imageの概要は以下のとおりです。 Build image paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny イメージ paketobuildpacks/build:base-cnb paketobuildpacks/build:full-cnb paketobuildpacks/build:tiny-cnb ( base-cnb と同じ) Dockerfile Dockerfile 同左 ( base-cnb と同じ) ベースイメージ ubuntu:bionic 同左 ( base-cnb と同じ) パッケージリスト build build ( base-cnb と同じ) パッケージ数 172 703 ( base-cnb と同じ) イメージサイズ 327MB 1.02GB ( base-cnb と同じ) Run image paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny イメージ paketobuildpacks/run:base-cnb paketobuildpacks/run:full-cnb paketobuildpacks/run:tiny-cnb Dockerfile Dockerfile 同左 Dockerfile ベースイメージ ubuntu:bionic 同左 scratch パッケージリスト run run packagelist パッケージ数 96 586 不明 1 イメージサイズ 88.9MB 691MB 17.4MB Buildpack プログラミング言語 やビルドツールのサポートは以下のとおりです。 nginxや HTTPD のコンテナイメージのビルドにも対応している点が特徴的ですね。 paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny C++ x x x .NET o o x Java o (Liberica JDK + Maven /Gradle) o (Liberica JDK + Maven /Gradle) o (Liberica JDK + Maven /Gradle) Go o o o Node.js o (npm/Yarn) o (npm/Yarn) x Python o o x Ruby o o x PHP x o x nginx o o x HTTPD x o x 各Builderを評価する ここからは、前述した主要なBuilderをいくつかの観点で評価します。 なぜBuilderを評価するのか 前述したように、実際にコンテナイメージのビルドを行うのは、 Google やHerokuなど3rd partyが実装したBuilderです。Builderによってビルドプロセスやベースとなるコンテナイメージに差異があるため、生成されるコンテナイメージの品質は使用したBuilderやBuildpackに大きく依存します。 よって本記事では、より品質の高いコンテナイメージを生成するために、前述した主要なBuilderの評価を行います。 評価の観点 以下の観点で各Builderを評価します。 プログラミング言語 やビルドツールへの対応状況 生成されるコンテナイメージのattack surface の小ささ 開発の活発さ ベンダー ニュートラ ル なお、attack surface とは、攻撃を試みることができる対象範囲のことです。例えば、アプリケーションに不必要なパッケージやファイルがコンテナイメージに含まれていると、それらに 脆弱性 があった場合、攻撃されるリスクが高まります。そのため、コンテナイメージには必要なパッケージやファイルのみを含めるべきです。 プログラミング言語 やビルドツールへの対応状況 Builderは自分たちが使用する プログラミング言語 やビルドツールに対応しているものを選ぶ必要があります。 各Builderの プログラミング言語 やビルドツールへの対応状況を改めてまとめます。 C++ .NET Java Go Node.js Python Ruby PHP nginx HTTPD gcr.io/buildpacks/builder:v1 o o o (AdoptOpenJDK + Maven /Gradle または GraalVM + Maven ) o o (npm/Yarn) o x x x x heroku/buildpacks:18 x x o (OpenJDK + Maven /Gradle) o o (npm/Yarn) o o o x x heroku/buildpacks:20 x x o (OpenJDK + Maven /Gradle) o o (npm/Yarn) o o o x x paketobuildpacks/builder:base x o o (Liberica JDK + Maven /Gradle) o o (npm/Yarn) o o x o x paketobuildpacks/builder:full x o o (Liberica JDK + Maven /Gradle) o o (npm/Yarn) o o o o o paketobuildpacks/builder:tiny x x o (Liberica JDK + Maven /Gradle) o x x x x x x どのBuilderもGoと Java には対応しています。それ以外の プログラミング言語 やビルドツールはBuilderによって対応有無が異なります。 生成されるコンテナイメージのattack surface の小ささ 生成されるコンテナイメージは、余計なファイルやパッケージを含んでおらず、attack surface は小さいほうが望ましいです。 各BuilderのRun imageの情報を改めてまとめます。 ベースイメージ パッケージ数 イメージサイズ gcr.io/buildpacks/builder:v1 gcr.io/gcp-runtimes/ubuntu_18_0_4 123 120MB heroku/buildpacks:18 ubuntu:18.04 354 510MB heroku/buildpacks:20 ubuntu:20.04 354 566MB paketobuildpacks/builder:base ubuntu:bionic 96 88.9MB paketobuildpacks/builder:full ubuntu:bionic 586 691MB paketobuildpacks/builder:tiny scratch 不明 17.4MB Builder paketobuildpacks/builder:tiny のRun imageは scratch に必要なパッケージのみを追加する形で作成しており、イメージサイズが最も小さいです。具体的なパッケージ数は確認できていませんが最も少ないと推測されます。 それ以外のBuilderのRun imageは ubuntu:bionic やそれに近いイメージをベースとしており、 bash などアプリケーションの実行に必須でないパッケージを含んでいます。 開発の活発さ Builderは、開発が活発で、機能改善や 脆弱性 の修正が積極的に行われているものが望ましいです。 各Builderを管理している GitHub リポジトリ のStar数と直近1ヶ月のコミット数 4 を見てみましょう。 URL Star数 コミット数 gcr.io/buildpacks/builder:v1 https://github.com/GoogleCloudPlatform/buildpacks 699 40 heroku/buildpacks:18 https://github.com/heroku/pack-images 26 8 heroku/buildpacks:20 https://github.com/heroku/pack-images 26 8 paketobuildpacks/builder:base https://github.com/paketo-buildpacks/base-builder 13 20 paketobuildpacks/builder:full https://github.com/paketo-buildpacks/full-builder 16 24 paketobuildpacks/builder:tiny https://github.com/paketo-buildpacks/tiny-builder 13 15 HerokuとPaketo BuildpacksはBuilder・Stack・Buildpackを個別の リポジトリ で管理していますが、 Google はそれらを1つの リポジトリ で管理しています。そのため単純な比較は出来ませんが、どのBuilderも継続的に更新されていることがわかります。 また、 Google CloudではBuildpacksによるコンテナイメージのビルドをサポートしています。 お知らせ: Google Cloud の Buildpacks でコンテナ イメージ作成が簡単に | Google Cloud Blog このサポートが続く限り、Builder gcr.io/buildpacks/builder:v1 は、 Google Cloud上で実行するのに適したコンテナイメージを生成できるよう、 Google によってメンテナンスされることが期待できます。 ベンダー ニュートラ ル Paketo Buildpacksはベンダー ニュートラ ルを謳っています。 公式ドキュメント には以下のように記述されています。 The project has vendor-neutral governance through the Cloud Foundry Foundation 一方で、 Google やHerokuのBuilderは、それぞれのベンダーの クラウド プラットフォーム上で実行するコンテナイメージの作成を主な対象としています。例えばBuilder gcr.io/buildpacks/builder:v1 の GitHub リポジトリ https://github.com/GoogleCloudPlatform/buildpacks には、 Google Cloud向けである旨が以下のように記述されています。 This repository contains a set of builders and buildpacks designed to run on Google Cloud's container platforms: Cloud Run , GKE , Anthos , and Compute Engine running Container-Optimized OS . They are also used as the build system for App Engine and Cloud Functions . ただし現時点ではこれらのBuilderを使ってベンダーに依存しないコンテナイメージを生成することも可能です。 考察 Goや Java で記述されたアプリケーションであれば、Run imageのattack surface の小さい paketobuildpacks/builder:tiny を使用するのがよいでしょう。それ以外のBuilderは、Run imageが bash などアプリケーションの実行に必須でないパッケージを含んでいるため、仕事で使用するにはあまり適さないでしょう。 また、仕事で使用するコンテナイメージは十分な品質が求められます。そのため、既存のBuilderを使用する場合は、使用するRun imageの構成やBuildpackの実装を確認するのがよいでしょう。また、各Builderは積極的にメンテナンスされているため、確認は1回だけでなく継続的に行うのがよいでしょう。 ただ本記事の趣旨を否定するようですが、それを行うぐらいならば、自分たちのサービスに適したBuilderを自作して使用するほうがコンテナイメージの品質をより制御できてよいと私は考えています。もちろん、Builderを自作する分の手間はかかります。そのため、もし1つのアプリケーションでしかそのBuilderを使わないなら、Dockerfileを書いたほうが手間は少ないでしょう。一方で社内に同じ プログラミング言語 やビルドツールを使うプロジェクトが複数あるなら、Builderを自作して使用すると、それらのコンテナイメージの品質を横断的に保証できます。 本記事では説明しませんが、Builderを自作するのはそれほど難しくありません。詳しくは公式ドキュメントや Buildpacksのビルダーをスクラッチから作ってみる | フューチャー技術ブログ などを参照ください。 また、近年のソフトウェアは早いスピードで進化しています。今後、より高い品質のコンテナイメージを生成できるBuilderが登場し、それが デファクトスタンダード となる可能性もあります。 今回調査しきれなかった点 生成されるコンテナイメージの品質は主にRun imageとBuildpackによって決まります。今回、Run imageについてはある程度調査できましたが、Builderに含まれる各Buildpackのビルドプロセスの詳細(実装やその品質など)までは調査できませんでした。これは今後の課題にしたいと思います。 おわりに 本記事では Cloud Native Buildpacks の主要なBuilderの調査を行いました。 Cloud Native Buildpacksはコンテナイメージのビルドプロセスを標準化・自動化してくれます。これにより、アプリケーション開発者はDockerfileを意識する必要がなくなり、アプリケーション開発に集中できます。これはとても価値のあることです。今後もCloud Native Buildpacksの進化に注目していきます。 執筆: @shibata.takao 、レビュー: @sato.taichi ( Shodo で執筆されました ) 図は https://buildpacks.io/ のものを引用しています。 ↩ 直接のベースイメージではなく「ベースイメージのベースイメージ」のようにいくつか遡った先で使用されているベースイメージです。他の表についても同様です。 ↩ dpkg -l コマンドで出力されたパッケージの数です。他の表についても同様です。 ↩ dpkg コマンドがインストールされておらず dpkg -l コマンドでパッケージ数を確認できなかったため不明としています。 ↩ 図は https://buildpacks.io/docs/concepts/components/builder/ のものを引用しています。 ↩ 全て2022年1月30日時点の情報です。 ↩ 2021年12月30日から2022年1月30日までのコミット数です。 ↩
アバター
みなさん、こんにちは!ISID FS事業部 市場系ソリューション1部の寺山です。 本日は2022年2月22日ということで、2が5つ並んでいる貴重な瞬間です(しかも ニャンニャン ニャンの日!私猫を3匹飼ってます)。次に同じ数字が5つ以上並んでいる日を迎えるのは、90年後なのですが、私はその時何をしているでしょうかね。。。?(笑) 私は現在、汎用的なマイクロサービスアプリケーション開発プロジェクト内で、このアプリをホストする クラウド インフラスト ラク チャのコード化(Infrastructure as Code, IaC)をチームで進めています。 その取り組みの中でインフラテストのコード化を行いたく、ツールの選定と比較を行いました。その内容を共有させていただこうと思います! コード化対象のテスト なぜコード化するのか? 比較対象のツール サンプルのテストコード Terratest 環境 テストコード EC2のパラメータテスト  疎通テスト 実行結果 AWS-SDK for JS+Jest 環境 テストコード EC2のパラメータテスト 疎通テスト 実行結果 比較 汎用性と自由度 テストコード実装の容易性 実行時間 結果レポート どちらを選択するか? 終わりに 参考)AWS-SDK + TypeScript のテストフレームワークをMochaに変更 コード化対象のテスト 対象として考えているテストの種類は以下のとおりです。 パラメータテスト 実際のインフラスト ラク チャ/ クラウド リソースのパラメータが、設計書やIaCと比較し想定とおりであることを検証する。 疎通テスト ルーティングや ファイアウォール の トラフィック 制御により、許可したアクセス元/アクセス先/ プロトコル /ポート番号でアクセス可能であることを検証する。 テストの名称には他のものもあるかも知れませんが、本記事内では記載の名称を用いて説明します。 また、パラメータテストで検証するパラメータは以下のとおりです。今回は選定/比較の段階でしたので、サンプルとしてEC2 インスタンス (いわゆる踏み台サーバー)を対象としました。 こちらのEC2 インスタンス はテスト前に手動で terraform apply を実行し、デプロイ済みの状態でテストを実行します。 実際のリソースの画面キャプチャを載せると長くなってしまうため、Terraformのstateファイルで代替させてください。 なお、以降のコードは公開にあたり修正している部分がございますので、ご留意ください。 クラウド プロバイダ AWS Terraform 1.0.11 AWS Provider 3.69.0 stateファイル { " module ": " module.mainte ", " mode ": " managed ", " type ": " aws_instance ", " name ": " instance ", " provider ": " provider[ \" registry.terraform.io/hashicorp/aws \" ] ", " instances ": [ { " index_key ": " dev-ec2instance-bastion-01 ", " schema_version ": 1 , " attributes ": { " ami ": " ami-0923d9a4d39b22a91 ", " arn ": " arn:aws:ec2:ap-northeast-1:999999999999:instance/i-0de3100c9e84299af ", " associate_public_ip_address ": true , " availability_zone ": " ap-northeast-1a ", " capacity_reservation_specification ": [ { " capacity_reservation_preference ": " open ", " capacity_reservation_target ": [] } ] , " cpu_core_count ": 1 , " cpu_threads_per_core ": 2 , " credit_specification ": [ { " cpu_credits ": " standard " } ] , " disable_api_termination ": false , " ebs_block_device ": [] , " ebs_optimized ": false , " enclave_options ": [ { " enabled ": false } ] , " ephemeral_block_device ": [] , " get_password_data ": false , " hibernation ": false , " host_id ": null , " iam_instance_profile ": " dev-iamrole-bastion-instance ", " id ": " i-0de3100c9e84299af ", " instance_initiated_shutdown_behavior ": " stop ", " instance_state ": " running ", " instance_type ": " t3a.medium ", " ipv6_address_count ": 0 , " ipv6_addresses ": [] , " key_name ": " dev-ec2keypair-bastion ", " launch_template ": [] , " metadata_options ": [ { " http_endpoint ": " enabled ", " http_put_response_hop_limit ": 1 , " http_tokens ": " optional " } ] , " monitoring ": false , " network_interface ": [] , " outpost_arn ": "", " password_data ": "", " placement_group ": "", " placement_partition_number ": null , " primary_network_interface_id ": " eni-00460786274d281f7 ", " private_dns ": " ip-192-168-137-136.ap-northeast-1.compute.internal ", " private_ip ": " 192.168.137.136 ", " public_dns ": " ec2-99-99-99-99.ap-northeast-1.compute.amazonaws.com ", " public_ip ": " 99.99.99.99 ", " root_block_device ": [ { " delete_on_termination ": true , " device_name ": " /dev/xvda ", " encrypted ": false , " iops ": 100 , " kms_key_id ": "", " tags ": {} , " throughput ": 0 , " volume_id ": " vol-014bb815a9d7d7202 ", " volume_size ": 30 , " volume_type ": " gp2 " } ] , " secondary_private_ips ": [] , " security_groups ": [] , " source_dest_check ": true , " subnet_id ": " subnet-0d648e07f975d70d0 ", " tags ": { " Env ": " dev ", " Name ": " dev-ec2instance-bastion-01 " } , " tags_all ": { " Env ": " dev ", " Name ": " dev-ec2instance-bastion-01 " } , " tenancy ": " default ", " timeouts ": null , " user_data ": " d96ad8c0a045bbc14cbabfe3d4ce442460ddc60e ", " user_data_base64 ": null , " volume_tags ": { " Env ": " dev ", " Name ": " dev-ec2instance-bastion-01 " } , " vpc_security_group_ids ": [ " sg-09eba84df7f9ede87 " ] } , " sensitive_attributes ": [ [ { " type ": " get_attr ", " value ": " ami " } ] ] , " private ": " ABCDEFG.... ", " dependencies ": [ //省略 ] } ] } なぜコード化するのか? インフラのテスト工程をテストピラミッド 1 に当てはめると下図のようになると考えています。 パラメータテストはUTに分類しており、テストピラミッドの考え方に則ると実施頻度が高くなります。また、コストや所要時間は小さいことが望ましいです。 しかしながら私たちは今まで、前述のテストを実行する際、 GUI や CLI を用いて実際のパラメータや動作を目視で確認し、画面キャプチャやコマンドのログを エビデンス として取得してきました。この手法は システム開発 サイクルが短期化している昨今では以下の課題があります。 エビデンス の取得作業やレビュープロセスを含め、実施負荷が高い(スケジュールやコストの圧迫) テストの実施が手動作業となるため、再現性や再試行容易性を高めるのが難しい そのため、パラメータテストをコード化することにより、高頻度で実施してもコストや所要時間を削減したいと考えております。 また、今回のパラメータテストのコード化を契機にインフラテストのコード化を進めることで、以下を達成するのが今後の目標です。 再現性を獲得し、不具合の早期発見を可能することでシステム品質向上に寄与する。 CI/CDパイプラインに組み込むことでインフラもDevOpsの実現をする。 比較対象のツール Terratest インフラテスト専用のヘルパー関数とテストテンプレートを提供する Golang ライブラリ Terraform だけでなく、他の オーケストレーション ツール、各種 クラウド プロバイダの API 、HTTP リク エス トや SSH コマンドの実行をサポート テストコードに Terraform の Plan/Apply/Destroy を組み込めるため自動化と相性が良い AWS SDK for JavaScript v3 + Jest + TypeScript 各 クラウド プロバイダの提供する SDK と、その言語のテスト フレームワーク 後者で Jest と TypeScript という組み合わせにした理由は、インフラ運用のための バッチ処理 をNode.jsランタイム上に、 AWS SDK を利用してTypeScriptで実装していたのが背景です。 バッチ処理 用に準備した実装環境やコード規約等のナレッジを流用しました。 なお、ツールの選定においては、対象のインフラがマネージドサービスやサーバレス アーキテクチャ を採用しているため、サーバOS、やサーバOSにインストールした ミドルウェア のテストを可能とするツール( Goss や Serverspec 等)は対象外にしました。 サーバOSのパラメータではなく、EC2やRDSといった クラウド リソースのパラメータテストを行うツールとしてはTerratestのほぼ一択となるかなと思います。 しかしながら、Terratestと同じことを AWS SDK + テスト フレームワーク でも実装可能なのでは?と思い比較してみたのが裏話となります。 サンプルのテストコード 前のセクションで紹介した2種類のツールで実際にテストコードを実装してみました。 なお、tfstateでは明示的に指定していないパラメータもTerraformや AWS API のデフォルト値が出力されますが、そのような項目はテストコードの実装の対象外としています。 また、 VPC -ID/Subnet-ID/SecurityGroup-IDのパラメータに対するテストを実装していないのは、EC2のテスト内でIDを意識する実装にはしたくないと考えたためです。今後、 VPC のテストを実装する際にIDを引数としてテストできるようなヘルパー関数を提供する予定です。 以下のサンプルでは、プライベート IPアドレス とパブリック IPアドレス が アサイ ンされていることを確認しました。 疎通テストは固定レスポンスを返すエンドポイントのパスに対して HTTPS プロトコル のリク エス トを検証しています。イメージとしてはヘルスチェックエンドポイントに対するテストなのですが、レスポンスのBodyの内容も検証してみたかったので、ALBに固定レスポンスを返却するリスナールールを追加したものとなります。 Terratest 環境 テストコードの実装に利用した環境は以下です。 OS(正確にはDockerイメージ) Debian GNU/Linux 11 (bullseye) IDE VSCode 1.63.2 Go 1.17 Terratest 0.38.9 利用しているライブラリは以下です。( go.mod より抜粋) require ( github.com/aws/aws-sdk- go v1. 40.56 github.com/gruntwork-io/terratest v0. 38.9 github.com/stretchr/testify v1. 7.0 ) require ( github.com/boombuler/barcode v1. 0.1 - 0.20190219062509 -6c824513bacc // indirect github.com/cpuguy83/ go -md2man/v2 v2. 0.0 // indirect github.com/davecgh/ go -spew v1. 1.1 // indirect github.com/ go -errors/errors v1. 0.2 - 0.20180813162953 -d98b870cc4e0 // indirect github.com/ go -sql-driver/mysql v1. 4.1 // indirect github.com/google/uuid v1. 2.0 // indirect github.com/gruntwork-io/ go -commons v0. 8.0 // indirect github.com/hashicorp/errwrap v1. 0.0 // indirect github.com/hashicorp/ go -multierror v1. 1.0 // indirect github.com/jmespath/ go -jmespath v0. 4.0 // indirect github.com/mattn/ go -zglob v0. 0.2 - 0.20190814121620 -e3c945676326 // indirect github.com/pmezard/ go -difflib v1. 0.0 // indirect github.com/pquerna/otp v1. 2.0 // indirect github.com/russross/blackfriday/v2 v2. 1.0 // indirect github.com/urfave/cli v1. 22.2 // indirect golang.org/x/crypto v0. 0.0 - 20210513164829 -c07d793c2f9a // indirect golang.org/x/net v0. 0.0 - 20210614182718 -04defd469f4e // indirect golang.org/x/sys v0. 0.0 - 20210603125802 -9665404d3644 // indirect google.golang.org/appengine v1. 6.7 // indirect gopkg.in/yaml.v3 v3. 0.0 - 20210107192922 -496545a6307b // indirect ) テストコード EC2のパラメータテスト  package test import ( "testing" awsSDK "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/gruntwork-io/terratest/modules/aws" "github.com/stretchr/testify/assert" ) func TestAwsEc2Parameter(t *testing.T) { t.Parallel() // Table driven tests := [] struct { env string instanceType string keyPair string }{ { "dev" , "t3a.medium" , "dev-ec2keypair-bastion" }, } for _, tt := range tests { t.Run( "AWS EC2 instance parameter. env: " +tt.env, func (t *testing.T) { expectedInstanceName := tt.env + "-ec2instance-bastion-01" awsRegion := "ap-northeast-1" instanceIds, err := aws.GetEc2InstanceIdsByTagE(t, awsRegion, "Name" , expectedInstanceName) if err != nil { t.Fatal( "Failed to get EC2 instance IDs." , err) } if !(assert.Len(t, instanceIds, 1 )) { t.Fatalf( "instanceIds not 1, actual: %v" , len (instanceIds)) } ec2Client := aws.NewEc2Client(t, awsRegion) instances, err := ec2Client.DescribeInstances(&ec2.DescribeInstancesInput{ InstanceIds: []* string {awsSDK.String(instanceIds[ 0 ])}, }) if err != nil { t.Fatal( "Failed to DescribeInstances API." , err) } t.Run( "Got EC2 instance only 1" , func (t *testing.T) { assert.Len(t, instances.Reservations, 1 ) assert.Len(t, instances.Reservations[ 0 ].Instances, 1 ) }) t.Run( "CPU architecture" , func (t *testing.T) { assert.Equal(t, "x86_64" , awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].Architecture)) }) t.Run( "Instance type" , func (t *testing.T) { assert.Equal(t, tt.instanceType, awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].InstanceType)) }) t.Run( "Instance key pair" , func (t *testing.T) { assert.Equal(t, tt.keyPair, awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].KeyName)) }) t.Run( "Instance profile" , func (t *testing.T) { accoundId := aws.GetAccountId(t) assert.Equal(t, "arn:aws:iam::" +accoundId+ ":instance-profile/" +tt.env+ "-iamrole-bastion-instance" , awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].IamInstanceProfile.Arn)) }) t.Run( "Security group" , func (t *testing.T) { assert.Len(t, instances.Reservations[ 0 ].Instances[ 0 ].SecurityGroups, 1 ) assert.Equal(t, tt.env+ "-sg-bastion" , awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].SecurityGroups[ 0 ].GroupName)) }) t.Run( "Asigned private ip address" , func (t *testing.T) { assert.NotEmpty(t, instances.Reservations[ 0 ].Instances[ 0 ].PrivateIpAddress) }) t.Run( "Asigned public ip address" , func (t *testing.T) { assert.NotEmpty(t, instances.Reservations[ 0 ].Instances[ 0 ].PublicIpAddress) }) }) } } EC2 インスタンス のパラメータ取得には、Terratestの aws モジュールを利用しています。このモジュールは AWS - SDK のラッパーなので、 API Refence よりプロパティを参照しながら実装できます。 aws.GetEc2InstanceIdsByTagE というヘルパー関数が提供されていたため使ってみました。が、この後直接 AWS - SDK とJestで実装している時に気づいたのですが、 DescribeInstances でもタグによりフィルタができたのでコード量削減できますね。コードを書いている当時は気が付いていませんでした。ここでは紹介ということでそのままにしています。 疎通テスト package test import ( "crypto/tls" "testing" "time" http_helper "github.com/gruntwork-io/terratest/modules/http-helper" ) func TestAwsAlbHttps(t *testing.T) { t.Parallel() tests := [] struct { env string domain string }{ { "dev" , "dev.aws.domain.com" }, } for _, tt := range tests { t.Run(tt.env, func (t *testing.T) { tlsConfig := tls.Config{ MinVersion: 2 , } path := "/test" t.Run( "HTTPS request to " +path+ " is 200 status." , func (t *testing.T) { targetUrl := "https://public." + tt.domain + path http_helper.HttpGetWithRetry(t, targetUrl, &tlsConfig, 200 , "success test response." , 5 , 3 *time.Second) }) }) } } Terratestの http-helper モジュールの提供するヘルパー関数を利用しています。私でも 2 すんなり実装できました。 実行結果 以下のような実行結果を得ます。 $ ls -l ./ total 140 -rw-r--r-- 1 vscode vscode 1327 Jan 30 02:07 go.mod -rw-r--r-- 1 vscode vscode 127730 Jan 30 02:07 go.sum -rw-r--r-- 1 vscode vscode 7039 Feb 6 15:27 README.md drwxr-xr-x 4 vscode vscode 128 Jan 30 20:07 test $ ls -l ./test/ total 8 -rw-r--r-- 1 vscode vscode 2886 Feb 6 19:49 ec2_test.go -rw-r--r-- 1 vscode vscode 679 Feb 6 01:11 https_test.go $ go test -v ./test/ === RUN TestAwsEc2Parameter === PAUSE TestAwsEc2Parameter === RUN TestAwsAlbHttps === PAUSE TestAwsAlbHttps === CONT TestAwsEc2Parameter === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev === CONT TestAwsAlbHttps === RUN TestAwsAlbHttps/dev === RUN TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. 2022-02-06T19:51:12+09:00 retry.go:91: HTTP GET to URL https://public.dev.aws.domain.com/test TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. 2022-02-06T19:51:12+09:00 http_helper.go:32: Making an HTTP GET call to URL https://public.dev.aws.domain.com/test === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Got_EC2_instance_only_1 === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/CPU_architecture === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_type === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_key_pair === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_profile --- PASS: TestAwsAlbHttps (0.36s) --- PASS: TestAwsAlbHttps/dev (0.36s) --- PASS: TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. (0.36s) === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Security_group === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_private_ip_address === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_public_ip_address --- PASS: TestAwsEc2Parameter (1.33s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev (1.33s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Got_EC2_instance_only_1 (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/CPU_architecture (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_type (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_key_pair (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_profile (0.98s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Security_group (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_private_ip_address (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_public_ip_address (0.00s) PASS ok github.com/ISID/tech-blog/test 1.345s テストコードで t.Parallel() メソッドを利用しているのでテスト/サブテストが並行して実行されているのが分かります。 テストが完了したら、testing フレームワーク のデフォルト形式で結果のレポートが出力されます。テスト > サブテスト とインデントが下がって表示されています。 今回実装したテスト/サブテストは全て合格しているので、 PASS ステータスで出力されています。不合格の場合は FAIL ステータスが出力されます。 AWS - SDK for JS+Jest 環境 OS(正確にはDockerイメージ) Debian GNU/Linux 11 (bullseye) IDE VSCode 1.63.2 Node.js v14.18.3 TypeScript 4.5.5 AWS - SDK 3.49.0 ※一部v2を利用しています Jest 27.4.7 利用しているライブラリは以下です。( package.json より抜粋) { " devDependencies ": { " @aws-sdk/types ": " ^3.47.1 ", " @types/jest ": " ^27.4.0 ", " @typescript-eslint/eslint-plugin ": " ^5.10.1 ", " @typescript-eslint/parser ": " ^5.10.1 ", " eslint ": " ^8.7.0 ", " eslint-config-prettier ": " ^8.3.0 ", " prettier ": " ^2.5.1 ", " ts-jest ": " ^27.1.3 ", " ts-node ": " ^10.4.0 ", " typescript ": " ^4.5.5 " } , " dependencies ": { " @aws-sdk/client-ec2 ": " ^3.49.0 ", " aws-sdk ": " ^2.1065.0 ", " axios ": " ^0.25.0 ", " jest ": " ^27.4.7 " } } テストコード EC2のパラメータテスト import { DescribeInstancesCommand , DescribeInstancesCommandOutput , EC2Client } from '@aws-sdk/client-ec2' ; import { STS } from 'aws-sdk' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; instanceType: string ; keyPair: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , instanceType: 't3a.medium' , keyPair: 'dev-ec2keypair-bastion' , } , ] ; describe.each ( testParamTable )( 'AWS EC2 instance parameter. env: $env.' , ( { env , instanceType , keyPair } ) => { const expectedInstanceName = ` ${ env } -ec2instance-bastion-01` ; const ec2Client = new EC2Client ( { region: 'ap-northeast-1' , apiVersion: '2016-11-15' } ); const sts = new STS (); let instances: DescribeInstancesCommandOutput ; let accountId: string ; beforeAll (async () => { instances = await ec2Client .send ( new DescribeInstancesCommand ( { Filters: [ { Name: 'tag:Name' , Values: [ expectedInstanceName ] , } , ] , } ) ) . catch (( reason ) => { throw new Error ( `Failed to DescribeInstances API: ${ reason } ` ); } ); if ( instances.Reservations?.length !== 1 ) { throw new Error ( `Got EC2 instances reservation not only 1: ${ instances.Reservations?.length } ` ); } const identity = await sts.getCallerIdentity ( {} ) .promise (); if ( identity.Account != null ) { accountId = identity.Account ; } } ); describe ( `Test start( ${ env } )` , () => { test ( 'Got instance only 1' , () => { expect ( instances.Reservations?. [ 0 ] .Instances ) .toHaveLength ( 1 ); } ); test ( 'CPU architecture' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .Architecture ) .toEqual ( 'x86_64' ); } ); test ( 'Instance type' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .InstanceType ) .toEqual ( instanceType ); } ); test ( 'Instance key pair' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .KeyName ) .toEqual ( keyPair ); } ); test ( 'Instance profile' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .IamInstanceProfile?.Arn ) .toEqual ( `arn:aws:iam:: ${ accountId } :instance-profile/ ${ env } -iamrole-bastion-instance` ); } ); test ( 'Security Group' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups ) .toHaveLength ( 1 ); expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups?. [ 0 ] .GroupName ) .toEqual ( ` ${ env } -sg-bastion` ); } ); test ( 'Asigned private ip address' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PrivateIpAddress ) .toBeTruthy (); } ); test ( 'Asigned public ip address' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PublicIpAddress ) .toBeTruthy (); } ); } ); } ); Terratestのテストコードでは、 Golang で慣例となっているTable Driven 3 なテストとしていたので、こちらでもTable Drivenを導入してみました。 疎通テスト import axios from 'axios' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; domain: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , domain: 'dev.aws.domain.com' , } , ] ; describe.each ( testParamTable )( 'AWS ALB connectivity test. env: $env' , ( { env , domain } ) => { describe ( `Test start( ${ env } )` , () => { const path = '/test' ; test ( `HTTPS request to ${ path } is 200 status.` , async () => { // responseに型付けをする方が望ましいが、テストのためany型を許容する const response = await axios ( { method: 'GET' , url: `https://public. ${ domain }${ path } ` , } ); expect.assertions ( 2 ); expect ( response. status) .toEqual ( 200 ); expect ( response.data ) .toEqual ( 'success test response.' ); } ); } ); } ); HTTPクライアントにはJSで デファクト な axios を利用しました。 実行結果 Jestの設定は以下のとおりです。( jest.config.js ) module.exports = { clearMocks: true , collectCoverage: false , roots: [ '<rootDir>test' ] , testMatch: [ '**/__tests__/**/*.[jt]s?(x)' , '**/?(*.)+(spec|test).[tj]s?(x)' ] , transform: { '^.+ \\ .ts$' : 'ts-jest' } , verbose: true , } ; テストはnpm スクリプト として実行します。( package.json より抜粋) { " scripts ": { " test ": " jest " } } 以下のような実行結果を得ます。 $ npm run test > aws@1.0.0 test <path> > jest PASS test/https.test.ts AWS ALB connectivity test. env: dev Test start(dev) ✓ HTTPS request to /test is 200 status. (163 ms) PASS test/ec2.test.ts (8.461 s) AWS EC2 instance parameter. env: dev Test start(dev) ✓ Got instance only 1 (2 ms) ✓ CPU architecture (1 ms) ✓ Instance type ✓ Instance key pair ✓ Instance profile ✓ Security Group (1 ms) ✓ Asigned private ip address ✓ Asigned public ip address Test Suites: 2 passed, 2 total Tests: 9 passed, 9 total Snapshots: 0 total Time: 9.81 s, estimated 10 s Ran all test suites. テストスイートとテストがシーケンシャルに実行されています。 テストが完了したら、Jest フレームワーク のデフォルト形式で結果のレポートが出力されます。テストスイート内の各テストの合否とサマリが出力されています。 今回実装したテスト/サブテストは全て合格しているので、 ✓ ステータスで出力されています。。不合格の場合は × ステータスが出力されます。 比較 ここからは、実際に利用してみて得られた結果や感想より、いくつかの観点で比較します。 汎用性と自由度 ここでの「汎用性」と「自由度」は、以下を指す意図で使用しています。 汎用性:一つのツールでカバー可能な範囲の広さ 自由度:実装する言語や補助ライブラリの選択肢の多さ これらの観点に対し、以下のように評価しました。 比較対象のツール セクションで紹介したとおり、Terratestは複数の クラウド プロバイダや オーケストレーション ツールをサポートしているため、汎用性が高い。 一方、実装する言語は Golang に固定される。 SDK + テスト フレームワーク は、サポートされる言語の中からであれば自由に選択できるので自由度は高い。 一方、テスト対象の クラウド プロバイダ毎に検討が必要となる。 つまり、今回比較したツールにおいては トレードオフ の関係にあると言えます。 この評価はツール選定の時点で自明ではありましたが、記事の構成上この場で言及させていただきました。 テストコード実装の容易性 私自身が、 Golang とTypeScriptのどちらが得意というものもないため大きな差は感じませんでした。 個人的には配列やスライスを扱う際にポインタの理解が必要な分、 Golang の方が言語としての難易度が高いと感じますが、本件のようにシンプルにテストコードを記述する範囲では大きな影響はないと考えます。 実装スキルに依存するため、あまり意味はないと思いつつ、ステップ数でも比較してみました。 ツール EC2パラメータテスト 疎通テスト 合計 Terratest 69 29 98 AWS - SDK for JS+Jest 75 25 100 やはり大きな差はないですね。 実行時間 ツール EC2パラメータテスト 疎通テスト 全体 Terratest 1.33s 0.36s 1.35s AWS - SDK for JS+Jest 8.46s 0.16s 9.81s こちらは大きな差が出ました。特にCI/CDパイプラインに組み込んだ場合など、実行頻度が高いテストにおいて時間は重要な指標となります。 結果レポート Terratest(正確には testging フレームワーク )は テスト/サブテスト と結合されて結果が出力されるので冗長に感じる。 Jest はテストスイートとテストが改行とインデントを分けて出力される。 Jest はテスト結果のサマリも出力される。 という点より、個人的には、標準出力に表示されるレポートは Jest の方が見易いと感じます。 どちらを選択するか? 今回紹介したもの以外も含め、どのツールを選択するかはプロジェクトやシステムに委ねられると思います。 私の取り組みではどちらにするか?なのですが、ひじょーーーに迷いました。 プログラミング言語 は適材適所で選択するのが望ましいですが、チームメンバのスキルセットも重要な判断材料となります。 私のチームはどちらも経験を有していたわけではないのですが、インフラ運用 バッチ処理 をTypeScriptで実装してNode.jsで実行する方式を採用していたため、実際に比較する前は AWS SDK + Jestにしようと考えていました。 しかしながら、実際に調査して動かしてみた結果、Terratestを採用するという結論を一旦出しました。理由は以下です。 汎用性の高さ ホストするマイクロサービスアプリケーションは、 AWS 以外のインフラへの展開を見込んでいるため 実行時間が短い 現在考えているテストサイクルは、IaCのメンテナンス後に環境をデプロイ後、半自動(Webhookのようなイベント駆動を想定)での実行を検討しているため、実行頻度はそこまで高くない それでも実行時間の短さは大きなアドバンテージと評価した 終わりに 実は、実際に調査するまでTerratestは「Terraformのtfstateの中身をテストするツール」だと思い込んでいました。 偏見や思い込みは良くないなと反省します。。。 また、実際に触ってみるのと机上調査との違いの大きさも実感します。 今回はインフラテストコード化ツールについて比較してみました。参考になった方や、弊社に興味を持ってくれた方がいらしたら幸いです。 次は2111年11月11日にお会いしましょう! 参考) AWS - SDK + TypeScript のテスト フレームワーク をMochaに変更 @higa さんから「テスト フレームワーク を Mocha に変えたらテスト実行時間も変わるのでは?」というアド バイス をいただいたため追加で試してみました。 詳細な説明は割愛いたします。 利用したライブラリとバージョン( package.json より抜粋) { " devDependencies ": { " @aws-sdk/types ": " ^3.47.1 ", " @types/chai ": " ^4.3.0 ", " @types/mocha ": " ^9.1.0 ", " @typescript-eslint/eslint-plugin ": " ^5.10.1 ", " @typescript-eslint/parser ": " ^5.10.1 ", " eslint ": " ^8.7.0 ", " eslint-config-prettier ": " ^8.3.0 ", " prettier ": " ^2.5.1 ", " ts-node ": " ^10.4.0 ", " typescript ": " ^4.5.5 " } , " dependencies ": { " @aws-sdk/client-ec2 ": " ^3.49.0 ", " aws-sdk ": " ^2.1065.0 ", " axios ": " ^0.25.0 ", " chai ": " ^4.3.6 ", " mocha ": " ^9.2.0 " } } EC2パラメータテストのテストコード import { DescribeInstancesCommand , DescribeInstancesCommandOutput , EC2Client } from '@aws-sdk/client-ec2' ; import { STS } from 'aws-sdk' ; import { assert } from 'chai' ; import { before , describe , it } from 'mocha' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; instanceType: string ; keyPair: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , instanceType: 't3a.medium' , keyPair: 'dev-ec2keypair-bastion' , } , ] ; describe ( 'AWS EC2 instance parameter' , () => { const ec2Client = new EC2Client ( { region: 'ap-northeast-1' , apiVersion: '2016-11-15' } ); const sts = new STS (); let accountId: string | undefined ; before (async function () { accountId = (await sts.getCallerIdentity ( {} ) .promise ()) .Account ; } ); testParamTable.forEach (( testParam ) => { const expectedInstanceName = ` ${ testParam.env } -ec2instance-bastion-01` ; let instances: DescribeInstancesCommandOutput ; before (async function () { instances = await ec2Client .send ( new DescribeInstancesCommand ( { Filters: [ { Name: 'tag:Name' , Values: [ expectedInstanceName ] , } , ] , } ) ) . catch (( reason ) => { throw new Error ( `Failed to DescribeInstances API: ${ reason } ` ); } ); if ( instances.Reservations?.length !== 1 ) { throw new Error ( `Got EC2 instances reservation not only 1: ${ instances.Reservations?.length } ` ); } } ); describe ( `Test start( ${ testParam.env } )` , () => { it ( 'Got instance only 1' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?.length , 1 ); } ); it ( 'CPU architecture' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .Architecture , 'x86_64' ); } ); it ( 'Instance key pair' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .KeyName , testParam.keyPair ); } ); it ( 'Instance profile' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .IamInstanceProfile?.Arn , `arn:aws:iam:: ${ accountId } :instance-profile/ ${ testParam.env } -iamrole-bastion-instance` ); } ); it ( 'Security Group' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups?.length , 1 ); assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups?. [ 0 ] .GroupName , ` ${ testParam.env } -sg-bastion` ); } ); it ( 'Asigned private ip address' , () => { assert.exists ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PrivateIpAddress ); } ); it ( 'Asigned public ip address' , () => { assert.exists ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PublicIpAddress ); } ); } ); } ); } ); 疎通テストのテストコード import axios from 'axios' ; import { assert } from 'chai' ; import { describe , it } from 'mocha' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; domain: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , domain: 'dev.aws.domain.com' , } , ] ; describe ( 'AWS ALB connectivity test' , () => { testParamTable.forEach (( testParam ) => { describe ( `Test start( ${ testParam.env } )` , () => { const path = '/test' ; it ( `HTTPS request to ${ path } is 200 status.` , async () => { // responseに型付けをする方が望ましいが、テストのためany型を許容する const response = await axios ( { method: 'GET' , url: `https://public. ${ testParam.domain }${ path } ` , } ); assert.equal ( response. status, 200 ); assert.equal ( response.data , 'success test response.' ); } ); } ); } ); } ); Mocha の設定 module.exports = { extension: [ 'ts' ] , spec: [ 'test/*.test.ts' , 'test/**/*.test.ts' ] , require: 'ts-node/register' , } ; npm スクリプト { " scripts ": { " test ": " mocha " } } テスト実行 $ npm run test > aws@1.0.0 test /workspaces/tech-blog-matrial/code/infra-test-with-awssdk-mocha > mocha AWS EC2 instance parameter Test start(dev) ✔ Got instance only 1 ✔ CPU architecture ✔ Instance key pair ✔ Instance profile ✔ Security Group ✔ Asigned private ip address ✔ Asigned public ip address AWS ALB connectivity test Test start(dev) ✔ HTTPS request to /test is 200 status. (68ms) 8 passing (1s) Docker コンテナのベースイメージを Ubuntu 21.04(Hirsute Hippo) に変更したため、他の組み合わせも再計測しました。 ツール EC2パラメータテスト 疎通テスト 全体 Terratest 1.33s 0.40s 1.343s AWS - SDK for JS+Jest 6.843s 0.103s 8.209s AWS - SDK for JS+Mocha - 0.068s 1.00s は、早い。。。 ※Mocha のレポートにミリ秒のオーダで実行時間を出力する方法を調査しきれませんでした。ご容赦ください。 執筆: 寺山 輝 (@terayama.akira) 、レビュー: @higa ( Shodo で執筆されました ) Mike Cohn氏が「Succeeding with Agile 」の中で提唱したもの ↩ プログラミングを業務で行うことなくキャリアを歩んできたため、プログラミング全般初学者です ↩ https://github.com/golang/go/wiki/TableDrivenTests ↩
アバター
みなさん、こんにちは!ISID FS事業部 市場系ソリューション1部の寺山です。 本日は2022年2月22日ということで、2が5つ並んでいる貴重な瞬間です(しかも ニャンニャン ニャンの日!私猫を3匹飼ってます)。次に同じ数字が5つ以上並んでいる日を迎えるのは、90年後なのですが、私はその時何をしているでしょうかね。。。?(笑) 私は現在、汎用的なマイクロサービスアプリケーション開発プロジェクト内で、このアプリをホストする クラウド インフラスト ラク チャのコード化(Infrastructure as Code, IaC)をチームで進めています。 その取り組みの中でインフラテストのコード化を行いたく、ツールの選定と比較を行いました。その内容を共有させていただこうと思います! コード化対象のテスト なぜコード化するのか? 比較対象のツール サンプルのテストコード Terratest 環境 テストコード EC2のパラメータテスト  疎通テスト 実行結果 AWS-SDK for JS+Jest 環境 テストコード EC2のパラメータテスト 疎通テスト 実行結果 比較 汎用性と自由度 テストコード実装の容易性 実行時間 結果レポート どちらを選択するか? 終わりに 参考)AWS-SDK + TypeScript のテストフレームワークをMochaに変更 コード化対象のテスト 対象として考えているテストの種類は以下のとおりです。 パラメータテスト 実際のインフラスト ラク チャ/ クラウド リソースのパラメータが、設計書やIaCと比較し想定とおりであることを検証する。 疎通テスト ルーティングや ファイアウォール の トラフィック 制御により、許可したアクセス元/アクセス先/ プロトコル /ポート番号でアクセス可能であることを検証する。 テストの名称には他のものもあるかも知れませんが、本記事内では記載の名称を用いて説明します。 また、パラメータテストで検証するパラメータは以下のとおりです。今回は選定/比較の段階でしたので、サンプルとしてEC2 インスタンス (いわゆる踏み台サーバー)を対象としました。 こちらのEC2 インスタンス はテスト前に手動で terraform apply を実行し、デプロイ済みの状態でテストを実行します。 実際のリソースの画面キャプチャを載せると長くなってしまうため、Terraformのstateファイルで代替させてください。 なお、以降のコードは公開にあたり修正している部分がございますので、ご留意ください。 クラウド プロバイダ AWS Terraform 1.0.11 AWS Provider 3.69.0 stateファイル { " module ": " module.mainte ", " mode ": " managed ", " type ": " aws_instance ", " name ": " instance ", " provider ": " provider[ \" registry.terraform.io/hashicorp/aws \" ] ", " instances ": [ { " index_key ": " dev-ec2instance-bastion-01 ", " schema_version ": 1 , " attributes ": { " ami ": " ami-0923d9a4d39b22a91 ", " arn ": " arn:aws:ec2:ap-northeast-1:999999999999:instance/i-0de3100c9e84299af ", " associate_public_ip_address ": true , " availability_zone ": " ap-northeast-1a ", " capacity_reservation_specification ": [ { " capacity_reservation_preference ": " open ", " capacity_reservation_target ": [] } ] , " cpu_core_count ": 1 , " cpu_threads_per_core ": 2 , " credit_specification ": [ { " cpu_credits ": " standard " } ] , " disable_api_termination ": false , " ebs_block_device ": [] , " ebs_optimized ": false , " enclave_options ": [ { " enabled ": false } ] , " ephemeral_block_device ": [] , " get_password_data ": false , " hibernation ": false , " host_id ": null , " iam_instance_profile ": " dev-iamrole-bastion-instance ", " id ": " i-0de3100c9e84299af ", " instance_initiated_shutdown_behavior ": " stop ", " instance_state ": " running ", " instance_type ": " t3a.medium ", " ipv6_address_count ": 0 , " ipv6_addresses ": [] , " key_name ": " dev-ec2keypair-bastion ", " launch_template ": [] , " metadata_options ": [ { " http_endpoint ": " enabled ", " http_put_response_hop_limit ": 1 , " http_tokens ": " optional " } ] , " monitoring ": false , " network_interface ": [] , " outpost_arn ": "", " password_data ": "", " placement_group ": "", " placement_partition_number ": null , " primary_network_interface_id ": " eni-00460786274d281f7 ", " private_dns ": " ip-192-168-137-136.ap-northeast-1.compute.internal ", " private_ip ": " 192.168.137.136 ", " public_dns ": " ec2-99-99-99-99.ap-northeast-1.compute.amazonaws.com ", " public_ip ": " 99.99.99.99 ", " root_block_device ": [ { " delete_on_termination ": true , " device_name ": " /dev/xvda ", " encrypted ": false , " iops ": 100 , " kms_key_id ": "", " tags ": {} , " throughput ": 0 , " volume_id ": " vol-014bb815a9d7d7202 ", " volume_size ": 30 , " volume_type ": " gp2 " } ] , " secondary_private_ips ": [] , " security_groups ": [] , " source_dest_check ": true , " subnet_id ": " subnet-0d648e07f975d70d0 ", " tags ": { " Env ": " dev ", " Name ": " dev-ec2instance-bastion-01 " } , " tags_all ": { " Env ": " dev ", " Name ": " dev-ec2instance-bastion-01 " } , " tenancy ": " default ", " timeouts ": null , " user_data ": " d96ad8c0a045bbc14cbabfe3d4ce442460ddc60e ", " user_data_base64 ": null , " volume_tags ": { " Env ": " dev ", " Name ": " dev-ec2instance-bastion-01 " } , " vpc_security_group_ids ": [ " sg-09eba84df7f9ede87 " ] } , " sensitive_attributes ": [ [ { " type ": " get_attr ", " value ": " ami " } ] ] , " private ": " ABCDEFG.... ", " dependencies ": [ //省略 ] } ] } なぜコード化するのか? インフラのテスト工程をテストピラミッド 1 に当てはめると下図のようになると考えています。 パラメータテストはUTに分類しており、テストピラミッドの考え方に則ると実施頻度が高くなります。また、コストや所要時間は小さいことが望ましいです。 しかしながら私たちは今まで、前述のテストを実行する際、 GUI や CLI を用いて実際のパラメータや動作を目視で確認し、画面キャプチャやコマンドのログを エビデンス として取得してきました。この手法は システム開発 サイクルが短期化している昨今では以下の課題があります。 エビデンス の取得作業やレビュープロセスを含め、実施負荷が高い(スケジュールやコストの圧迫) テストの実施が手動作業となるため、再現性や再試行容易性を高めるのが難しい そのため、パラメータテストをコード化することにより、高頻度で実施してもコストや所要時間を削減したいと考えております。 また、今回のパラメータテストのコード化を契機にインフラテストのコード化を進めることで、以下を達成するのが今後の目標です。 再現性を獲得し、不具合の早期発見を可能することでシステム品質向上に寄与する。 CI/CDパイプラインに組み込むことでインフラもDevOpsの実現をする。 比較対象のツール Terratest インフラテスト専用のヘルパー関数とテストテンプレートを提供する Golang ライブラリ Terraform だけでなく、他の オーケストレーション ツール、各種 クラウド プロバイダの API 、HTTP リク エス トや SSH コマンドの実行をサポート テストコードに Terraform の Plan/Apply/Destroy を組み込めるため自動化と相性が良い AWS SDK for JavaScript v3 + Jest + TypeScript 各 クラウド プロバイダの提供する SDK と、その言語のテスト フレームワーク 後者で Jest と TypeScript という組み合わせにした理由は、インフラ運用のための バッチ処理 をNode.jsランタイム上に、 AWS SDK を利用してTypeScriptで実装していたのが背景です。 バッチ処理 用に準備した実装環境やコード規約等のナレッジを流用しました。 なお、ツールの選定においては、対象のインフラがマネージドサービスやサーバレス アーキテクチャ を採用しているため、サーバOS、やサーバOSにインストールした ミドルウェア のテストを可能とするツール( Goss や Serverspec 等)は対象外にしました。 サーバOSのパラメータではなく、EC2やRDSといった クラウド リソースのパラメータテストを行うツールとしてはTerratestのほぼ一択となるかなと思います。 しかしながら、Terratestと同じことを AWS SDK + テスト フレームワーク でも実装可能なのでは?と思い比較してみたのが裏話となります。 サンプルのテストコード 前のセクションで紹介した2種類のツールで実際にテストコードを実装してみました。 なお、tfstateでは明示的に指定していないパラメータもTerraformや AWS API のデフォルト値が出力されますが、そのような項目はテストコードの実装の対象外としています。 また、 VPC -ID/Subnet-ID/SecurityGroup-IDのパラメータに対するテストを実装していないのは、EC2のテスト内でIDを意識する実装にはしたくないと考えたためです。今後、 VPC のテストを実装する際にIDを引数としてテストできるようなヘルパー関数を提供する予定です。 以下のサンプルでは、プライベート IPアドレス とパブリック IPアドレス が アサイ ンされていることを確認しました。 疎通テストは固定レスポンスを返すエンドポイントのパスに対して HTTPS プロトコル のリク エス トを検証しています。イメージとしてはヘルスチェックエンドポイントに対するテストなのですが、レスポンスのBodyの内容も検証してみたかったので、ALBに固定レスポンスを返却するリスナールールを追加したものとなります。 Terratest 環境 テストコードの実装に利用した環境は以下です。 OS(正確にはDockerイメージ) Debian GNU/Linux 11 (bullseye) IDE VSCode 1.63.2 Go 1.17 Terratest 0.38.9 利用しているライブラリは以下です。( go.mod より抜粋) require ( github.com/aws/aws-sdk- go v1. 40.56 github.com/gruntwork-io/terratest v0. 38.9 github.com/stretchr/testify v1. 7.0 ) require ( github.com/boombuler/barcode v1. 0.1 - 0.20190219062509 -6c824513bacc // indirect github.com/cpuguy83/ go -md2man/v2 v2. 0.0 // indirect github.com/davecgh/ go -spew v1. 1.1 // indirect github.com/ go -errors/errors v1. 0.2 - 0.20180813162953 -d98b870cc4e0 // indirect github.com/ go -sql-driver/mysql v1. 4.1 // indirect github.com/google/uuid v1. 2.0 // indirect github.com/gruntwork-io/ go -commons v0. 8.0 // indirect github.com/hashicorp/errwrap v1. 0.0 // indirect github.com/hashicorp/ go -multierror v1. 1.0 // indirect github.com/jmespath/ go -jmespath v0. 4.0 // indirect github.com/mattn/ go -zglob v0. 0.2 - 0.20190814121620 -e3c945676326 // indirect github.com/pmezard/ go -difflib v1. 0.0 // indirect github.com/pquerna/otp v1. 2.0 // indirect github.com/russross/blackfriday/v2 v2. 1.0 // indirect github.com/urfave/cli v1. 22.2 // indirect golang.org/x/crypto v0. 0.0 - 20210513164829 -c07d793c2f9a // indirect golang.org/x/net v0. 0.0 - 20210614182718 -04defd469f4e // indirect golang.org/x/sys v0. 0.0 - 20210603125802 -9665404d3644 // indirect google.golang.org/appengine v1. 6.7 // indirect gopkg.in/yaml.v3 v3. 0.0 - 20210107192922 -496545a6307b // indirect ) テストコード EC2のパラメータテスト  package test import ( "testing" awsSDK "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/gruntwork-io/terratest/modules/aws" "github.com/stretchr/testify/assert" ) func TestAwsEc2Parameter(t *testing.T) { t.Parallel() // Table driven tests := [] struct { env string instanceType string keyPair string }{ { "dev" , "t3a.medium" , "dev-ec2keypair-bastion" }, } for _, tt := range tests { t.Run( "AWS EC2 instance parameter. env: " +tt.env, func (t *testing.T) { expectedInstanceName := tt.env + "-ec2instance-bastion-01" awsRegion := "ap-northeast-1" instanceIds, err := aws.GetEc2InstanceIdsByTagE(t, awsRegion, "Name" , expectedInstanceName) if err != nil { t.Fatal( "Failed to get EC2 instance IDs." , err) } if !(assert.Len(t, instanceIds, 1 )) { t.Fatalf( "instanceIds not 1, actual: %v" , len (instanceIds)) } ec2Client := aws.NewEc2Client(t, awsRegion) instances, err := ec2Client.DescribeInstances(&ec2.DescribeInstancesInput{ InstanceIds: []* string {awsSDK.String(instanceIds[ 0 ])}, }) if err != nil { t.Fatal( "Failed to DescribeInstances API." , err) } t.Run( "Got EC2 instance only 1" , func (t *testing.T) { assert.Len(t, instances.Reservations, 1 ) assert.Len(t, instances.Reservations[ 0 ].Instances, 1 ) }) t.Run( "CPU architecture" , func (t *testing.T) { assert.Equal(t, "x86_64" , awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].Architecture)) }) t.Run( "Instance type" , func (t *testing.T) { assert.Equal(t, tt.instanceType, awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].InstanceType)) }) t.Run( "Instance key pair" , func (t *testing.T) { assert.Equal(t, tt.keyPair, awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].KeyName)) }) t.Run( "Instance profile" , func (t *testing.T) { accoundId := aws.GetAccountId(t) assert.Equal(t, "arn:aws:iam::" +accoundId+ ":instance-profile/" +tt.env+ "-iamrole-bastion-instance" , awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].IamInstanceProfile.Arn)) }) t.Run( "Security group" , func (t *testing.T) { assert.Len(t, instances.Reservations[ 0 ].Instances[ 0 ].SecurityGroups, 1 ) assert.Equal(t, tt.env+ "-sg-bastion" , awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].SecurityGroups[ 0 ].GroupName)) }) t.Run( "Asigned private ip address" , func (t *testing.T) { assert.NotEmpty(t, instances.Reservations[ 0 ].Instances[ 0 ].PrivateIpAddress) }) t.Run( "Asigned public ip address" , func (t *testing.T) { assert.NotEmpty(t, instances.Reservations[ 0 ].Instances[ 0 ].PublicIpAddress) }) }) } } EC2 インスタンス のパラメータ取得には、Terratestの aws モジュールを利用しています。このモジュールは AWS - SDK のラッパーなので、 API Refence よりプロパティを参照しながら実装できます。 aws.GetEc2InstanceIdsByTagE というヘルパー関数が提供されていたため使ってみました。が、この後直接 AWS - SDK とJestで実装している時に気づいたのですが、 DescribeInstances でもタグによりフィルタができたのでコード量削減できますね。コードを書いている当時は気が付いていませんでした。ここでは紹介ということでそのままにしています。 疎通テスト package test import ( "crypto/tls" "testing" "time" http_helper "github.com/gruntwork-io/terratest/modules/http-helper" ) func TestAwsAlbHttps(t *testing.T) { t.Parallel() tests := [] struct { env string domain string }{ { "dev" , "dev.aws.domain.com" }, } for _, tt := range tests { t.Run(tt.env, func (t *testing.T) { tlsConfig := tls.Config{ MinVersion: 2 , } path := "/test" t.Run( "HTTPS request to " +path+ " is 200 status." , func (t *testing.T) { targetUrl := "https://public." + tt.domain + path http_helper.HttpGetWithRetry(t, targetUrl, &tlsConfig, 200 , "success test response." , 5 , 3 *time.Second) }) }) } } Terratestの http-helper モジュールの提供するヘルパー関数を利用しています。私でも 2 すんなり実装できました。 実行結果 以下のような実行結果を得ます。 $ ls -l ./ total 140 -rw-r--r-- 1 vscode vscode 1327 Jan 30 02:07 go.mod -rw-r--r-- 1 vscode vscode 127730 Jan 30 02:07 go.sum -rw-r--r-- 1 vscode vscode 7039 Feb 6 15:27 README.md drwxr-xr-x 4 vscode vscode 128 Jan 30 20:07 test $ ls -l ./test/ total 8 -rw-r--r-- 1 vscode vscode 2886 Feb 6 19:49 ec2_test.go -rw-r--r-- 1 vscode vscode 679 Feb 6 01:11 https_test.go $ go test -v ./test/ === RUN TestAwsEc2Parameter === PAUSE TestAwsEc2Parameter === RUN TestAwsAlbHttps === PAUSE TestAwsAlbHttps === CONT TestAwsEc2Parameter === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev === CONT TestAwsAlbHttps === RUN TestAwsAlbHttps/dev === RUN TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. 2022-02-06T19:51:12+09:00 retry.go:91: HTTP GET to URL https://public.dev.aws.domain.com/test TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. 2022-02-06T19:51:12+09:00 http_helper.go:32: Making an HTTP GET call to URL https://public.dev.aws.domain.com/test === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Got_EC2_instance_only_1 === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/CPU_architecture === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_type === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_key_pair === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_profile --- PASS: TestAwsAlbHttps (0.36s) --- PASS: TestAwsAlbHttps/dev (0.36s) --- PASS: TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. (0.36s) === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Security_group === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_private_ip_address === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_public_ip_address --- PASS: TestAwsEc2Parameter (1.33s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev (1.33s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Got_EC2_instance_only_1 (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/CPU_architecture (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_type (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_key_pair (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_profile (0.98s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Security_group (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_private_ip_address (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_public_ip_address (0.00s) PASS ok github.com/ISID/tech-blog/test 1.345s テストコードで t.Parallel() メソッドを利用しているのでテスト/サブテストが並行して実行されているのが分かります。 テストが完了したら、testing フレームワーク のデフォルト形式で結果のレポートが出力されます。テスト > サブテスト とインデントが下がって表示されています。 今回実装したテスト/サブテストは全て合格しているので、 PASS ステータスで出力されています。不合格の場合は FAIL ステータスが出力されます。 AWS - SDK for JS+Jest 環境 OS(正確にはDockerイメージ) Debian GNU/Linux 11 (bullseye) IDE VSCode 1.63.2 Node.js v14.18.3 TypeScript 4.5.5 AWS - SDK 3.49.0 ※一部v2を利用しています Jest 27.4.7 利用しているライブラリは以下です。( package.json より抜粋) { " devDependencies ": { " @aws-sdk/types ": " ^3.47.1 ", " @types/jest ": " ^27.4.0 ", " @typescript-eslint/eslint-plugin ": " ^5.10.1 ", " @typescript-eslint/parser ": " ^5.10.1 ", " eslint ": " ^8.7.0 ", " eslint-config-prettier ": " ^8.3.0 ", " prettier ": " ^2.5.1 ", " ts-jest ": " ^27.1.3 ", " ts-node ": " ^10.4.0 ", " typescript ": " ^4.5.5 " } , " dependencies ": { " @aws-sdk/client-ec2 ": " ^3.49.0 ", " aws-sdk ": " ^2.1065.0 ", " axios ": " ^0.25.0 ", " jest ": " ^27.4.7 " } } テストコード EC2のパラメータテスト import { DescribeInstancesCommand , DescribeInstancesCommandOutput , EC2Client } from '@aws-sdk/client-ec2' ; import { STS } from 'aws-sdk' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; instanceType: string ; keyPair: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , instanceType: 't3a.medium' , keyPair: 'dev-ec2keypair-bastion' , } , ] ; describe.each ( testParamTable )( 'AWS EC2 instance parameter. env: $env.' , ( { env , instanceType , keyPair } ) => { const expectedInstanceName = ` ${ env } -ec2instance-bastion-01` ; const ec2Client = new EC2Client ( { region: 'ap-northeast-1' , apiVersion: '2016-11-15' } ); const sts = new STS (); let instances: DescribeInstancesCommandOutput ; let accountId: string ; beforeAll (async () => { instances = await ec2Client .send ( new DescribeInstancesCommand ( { Filters: [ { Name: 'tag:Name' , Values: [ expectedInstanceName ] , } , ] , } ) ) . catch (( reason ) => { throw new Error ( `Failed to DescribeInstances API: ${ reason } ` ); } ); if ( instances.Reservations?.length !== 1 ) { throw new Error ( `Got EC2 instances reservation not only 1: ${ instances.Reservations?.length } ` ); } const identity = await sts.getCallerIdentity ( {} ) .promise (); if ( identity.Account != null ) { accountId = identity.Account ; } } ); describe ( `Test start( ${ env } )` , () => { test ( 'Got instance only 1' , () => { expect ( instances.Reservations?. [ 0 ] .Instances ) .toHaveLength ( 1 ); } ); test ( 'CPU architecture' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .Architecture ) .toEqual ( 'x86_64' ); } ); test ( 'Instance type' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .InstanceType ) .toEqual ( instanceType ); } ); test ( 'Instance key pair' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .KeyName ) .toEqual ( keyPair ); } ); test ( 'Instance profile' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .IamInstanceProfile?.Arn ) .toEqual ( `arn:aws:iam:: ${ accountId } :instance-profile/ ${ env } -iamrole-bastion-instance` ); } ); test ( 'Security Group' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups ) .toHaveLength ( 1 ); expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups?. [ 0 ] .GroupName ) .toEqual ( ` ${ env } -sg-bastion` ); } ); test ( 'Asigned private ip address' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PrivateIpAddress ) .toBeTruthy (); } ); test ( 'Asigned public ip address' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PublicIpAddress ) .toBeTruthy (); } ); } ); } ); Terratestのテストコードでは、 Golang で慣例となっているTable Driven 3 なテストとしていたので、こちらでもTable Drivenを導入してみました。 疎通テスト import axios from 'axios' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; domain: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , domain: 'dev.aws.domain.com' , } , ] ; describe.each ( testParamTable )( 'AWS ALB connectivity test. env: $env' , ( { env , domain } ) => { describe ( `Test start( ${ env } )` , () => { const path = '/test' ; test ( `HTTPS request to ${ path } is 200 status.` , async () => { // responseに型付けをする方が望ましいが、テストのためany型を許容する const response = await axios ( { method: 'GET' , url: `https://public. ${ domain }${ path } ` , } ); expect.assertions ( 2 ); expect ( response. status) .toEqual ( 200 ); expect ( response.data ) .toEqual ( 'success test response.' ); } ); } ); } ); HTTPクライアントにはJSで デファクト な axios を利用しました。 実行結果 Jestの設定は以下のとおりです。( jest.config.js ) module.exports = { clearMocks: true , collectCoverage: false , roots: [ '<rootDir>test' ] , testMatch: [ '**/__tests__/**/*.[jt]s?(x)' , '**/?(*.)+(spec|test).[tj]s?(x)' ] , transform: { '^.+ \\ .ts$' : 'ts-jest' } , verbose: true , } ; テストはnpm スクリプト として実行します。( package.json より抜粋) { " scripts ": { " test ": " jest " } } 以下のような実行結果を得ます。 $ npm run test > aws@1.0.0 test <path> > jest PASS test/https.test.ts AWS ALB connectivity test. env: dev Test start(dev) ✓ HTTPS request to /test is 200 status. (163 ms) PASS test/ec2.test.ts (8.461 s) AWS EC2 instance parameter. env: dev Test start(dev) ✓ Got instance only 1 (2 ms) ✓ CPU architecture (1 ms) ✓ Instance type ✓ Instance key pair ✓ Instance profile ✓ Security Group (1 ms) ✓ Asigned private ip address ✓ Asigned public ip address Test Suites: 2 passed, 2 total Tests: 9 passed, 9 total Snapshots: 0 total Time: 9.81 s, estimated 10 s Ran all test suites. テストスイートとテストがシーケンシャルに実行されています。 テストが完了したら、Jest フレームワーク のデフォルト形式で結果のレポートが出力されます。テストスイート内の各テストの合否とサマリが出力されています。 今回実装したテスト/サブテストは全て合格しているので、 ✓ ステータスで出力されています。。不合格の場合は × ステータスが出力されます。 比較 ここからは、実際に利用してみて得られた結果や感想より、いくつかの観点で比較します。 汎用性と自由度 ここでの「汎用性」と「自由度」は、以下を指す意図で使用しています。 汎用性:一つのツールでカバー可能な範囲の広さ 自由度:実装する言語や補助ライブラリの選択肢の多さ これらの観点に対し、以下のように評価しました。 比較対象のツール セクションで紹介したとおり、Terratestは複数の クラウド プロバイダや オーケストレーション ツールをサポートしているため、汎用性が高い。 一方、実装する言語は Golang に固定される。 SDK + テスト フレームワーク は、サポートされる言語の中からであれば自由に選択できるので自由度は高い。 一方、テスト対象の クラウド プロバイダ毎に検討が必要となる。 つまり、今回比較したツールにおいては トレードオフ の関係にあると言えます。 この評価はツール選定の時点で自明ではありましたが、記事の構成上この場で言及させていただきました。 テストコード実装の容易性 私自身が、 Golang とTypeScriptのどちらが得意というものもないため大きな差は感じませんでした。 個人的には配列やスライスを扱う際にポインタの理解が必要な分、 Golang の方が言語としての難易度が高いと感じますが、本件のようにシンプルにテストコードを記述する範囲では大きな影響はないと考えます。 実装スキルに依存するため、あまり意味はないと思いつつ、ステップ数でも比較してみました。 ツール EC2パラメータテスト 疎通テスト 合計 Terratest 69 29 98 AWS - SDK for JS+Jest 75 25 100 やはり大きな差はないですね。 実行時間 ツール EC2パラメータテスト 疎通テスト 全体 Terratest 1.33s 0.36s 1.35s AWS - SDK for JS+Jest 8.46s 0.16s 9.81s こちらは大きな差が出ました。特にCI/CDパイプラインに組み込んだ場合など、実行頻度が高いテストにおいて時間は重要な指標となります。 結果レポート Terratest(正確には testging フレームワーク )は テスト/サブテスト と結合されて結果が出力されるので冗長に感じる。 Jest はテストスイートとテストが改行とインデントを分けて出力される。 Jest はテスト結果のサマリも出力される。 という点より、個人的には、標準出力に表示されるレポートは Jest の方が見易いと感じます。 どちらを選択するか? 今回紹介したもの以外も含め、どのツールを選択するかはプロジェクトやシステムに委ねられると思います。 私の取り組みではどちらにするか?なのですが、ひじょーーーに迷いました。 プログラミング言語 は適材適所で選択するのが望ましいですが、チームメンバのスキルセットも重要な判断材料となります。 私のチームはどちらも経験を有していたわけではないのですが、インフラ運用 バッチ処理 をTypeScriptで実装してNode.jsで実行する方式を採用していたため、実際に比較する前は AWS SDK + Jestにしようと考えていました。 しかしながら、実際に調査して動かしてみた結果、Terratestを採用するという結論を一旦出しました。理由は以下です。 汎用性の高さ ホストするマイクロサービスアプリケーションは、 AWS 以外のインフラへの展開を見込んでいるため 実行時間が短い 現在考えているテストサイクルは、IaCのメンテナンス後に環境をデプロイ後、半自動(Webhookのようなイベント駆動を想定)での実行を検討しているため、実行頻度はそこまで高くない それでも実行時間の短さは大きなアドバンテージと評価した 終わりに 実は、実際に調査するまでTerratestは「Terraformのtfstateの中身をテストするツール」だと思い込んでいました。 偏見や思い込みは良くないなと反省します。。。 また、実際に触ってみるのと机上調査との違いの大きさも実感します。 今回はインフラテストコード化ツールについて比較してみました。参考になった方や、弊社に興味を持ってくれた方がいらしたら幸いです。 次は2111年11月11日にお会いしましょう! 参考) AWS - SDK + TypeScript のテスト フレームワーク をMochaに変更 @higa さんから「テスト フレームワーク を Mocha に変えたらテスト実行時間も変わるのでは?」というアド バイス をいただいたため追加で試してみました。 詳細な説明は割愛いたします。 利用したライブラリとバージョン( package.json より抜粋) { " devDependencies ": { " @aws-sdk/types ": " ^3.47.1 ", " @types/chai ": " ^4.3.0 ", " @types/mocha ": " ^9.1.0 ", " @typescript-eslint/eslint-plugin ": " ^5.10.1 ", " @typescript-eslint/parser ": " ^5.10.1 ", " eslint ": " ^8.7.0 ", " eslint-config-prettier ": " ^8.3.0 ", " prettier ": " ^2.5.1 ", " ts-node ": " ^10.4.0 ", " typescript ": " ^4.5.5 " } , " dependencies ": { " @aws-sdk/client-ec2 ": " ^3.49.0 ", " aws-sdk ": " ^2.1065.0 ", " axios ": " ^0.25.0 ", " chai ": " ^4.3.6 ", " mocha ": " ^9.2.0 " } } EC2パラメータテストのテストコード import { DescribeInstancesCommand , DescribeInstancesCommandOutput , EC2Client } from '@aws-sdk/client-ec2' ; import { STS } from 'aws-sdk' ; import { assert } from 'chai' ; import { before , describe , it } from 'mocha' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; instanceType: string ; keyPair: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , instanceType: 't3a.medium' , keyPair: 'dev-ec2keypair-bastion' , } , ] ; describe ( 'AWS EC2 instance parameter' , () => { const ec2Client = new EC2Client ( { region: 'ap-northeast-1' , apiVersion: '2016-11-15' } ); const sts = new STS (); let accountId: string | undefined ; before (async function () { accountId = (await sts.getCallerIdentity ( {} ) .promise ()) .Account ; } ); testParamTable.forEach (( testParam ) => { const expectedInstanceName = ` ${ testParam.env } -ec2instance-bastion-01` ; let instances: DescribeInstancesCommandOutput ; before (async function () { instances = await ec2Client .send ( new DescribeInstancesCommand ( { Filters: [ { Name: 'tag:Name' , Values: [ expectedInstanceName ] , } , ] , } ) ) . catch (( reason ) => { throw new Error ( `Failed to DescribeInstances API: ${ reason } ` ); } ); if ( instances.Reservations?.length !== 1 ) { throw new Error ( `Got EC2 instances reservation not only 1: ${ instances.Reservations?.length } ` ); } } ); describe ( `Test start( ${ testParam.env } )` , () => { it ( 'Got instance only 1' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?.length , 1 ); } ); it ( 'CPU architecture' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .Architecture , 'x86_64' ); } ); it ( 'Instance key pair' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .KeyName , testParam.keyPair ); } ); it ( 'Instance profile' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .IamInstanceProfile?.Arn , `arn:aws:iam:: ${ accountId } :instance-profile/ ${ testParam.env } -iamrole-bastion-instance` ); } ); it ( 'Security Group' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups?.length , 1 ); assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups?. [ 0 ] .GroupName , ` ${ testParam.env } -sg-bastion` ); } ); it ( 'Asigned private ip address' , () => { assert.exists ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PrivateIpAddress ); } ); it ( 'Asigned public ip address' , () => { assert.exists ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PublicIpAddress ); } ); } ); } ); } ); 疎通テストのテストコード import axios from 'axios' ; import { assert } from 'chai' ; import { describe , it } from 'mocha' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; domain: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , domain: 'dev.aws.domain.com' , } , ] ; describe ( 'AWS ALB connectivity test' , () => { testParamTable.forEach (( testParam ) => { describe ( `Test start( ${ testParam.env } )` , () => { const path = '/test' ; it ( `HTTPS request to ${ path } is 200 status.` , async () => { // responseに型付けをする方が望ましいが、テストのためany型を許容する const response = await axios ( { method: 'GET' , url: `https://public. ${ testParam.domain }${ path } ` , } ); assert.equal ( response. status, 200 ); assert.equal ( response.data , 'success test response.' ); } ); } ); } ); } ); Mocha の設定 module.exports = { extension: [ 'ts' ] , spec: [ 'test/*.test.ts' , 'test/**/*.test.ts' ] , require: 'ts-node/register' , } ; npm スクリプト { " scripts ": { " test ": " mocha " } } テスト実行 $ npm run test > aws@1.0.0 test /workspaces/tech-blog-matrial/code/infra-test-with-awssdk-mocha > mocha AWS EC2 instance parameter Test start(dev) ✔ Got instance only 1 ✔ CPU architecture ✔ Instance key pair ✔ Instance profile ✔ Security Group ✔ Asigned private ip address ✔ Asigned public ip address AWS ALB connectivity test Test start(dev) ✔ HTTPS request to /test is 200 status. (68ms) 8 passing (1s) Docker コンテナのベースイメージを Ubuntu 21.04(Hirsute Hippo) に変更したため、他の組み合わせも再計測しました。 ツール EC2パラメータテスト 疎通テスト 全体 Terratest 1.33s 0.40s 1.343s AWS - SDK for JS+Jest 6.843s 0.103s 8.209s AWS - SDK for JS+Mocha - 0.068s 1.00s は、早い。。。 ※Mocha のレポートにミリ秒のオーダで実行時間を出力する方法を調査しきれませんでした。ご容赦ください。 執筆: 寺山 輝 (@terayama.akira) 、レビュー: @higa ( Shodo で執筆されました ) Mike Cohn氏が「Succeeding with Agile 」の中で提唱したもの ↩ プログラミングを業務で行うことなくキャリアを歩んできたため、プログラミング全般初学者です ↩ https://github.com/golang/go/wiki/TableDrivenTests ↩
アバター
電通国際情報サービス 、クロス イノベーション 本部、オープン イノベーション ラボの 比嘉康雄 です。 ブロックチェーン 週報2/21いってみようか。 Web3 Web3関連ニュース その他の話題 Web3 前回 、ク リエータ ー/ファンエコノミーがトレンドになっているという話をしましたが、その流れは今週も続いています。 ただ、その呼び方がWeb3と言われることが多くなったので、今後Web3で統一したいと思います。 Web3は、非中央集権的な Webサービス のことですが、それだとなんのことかわからないと思います。非中央集権的サービスとは、サービスの運営者がサービスを支配せず、サービスのユーザーも運営に関われるようなサービスのことです。これは、個人的な見解ですけど。 ユーザーが運営に関わる手段として、DAO(自律分散型組織)が使われることが一般的です。 DAOの参加資格は、NFTを持っていることとするケースがほとんどです。そこから短絡的に、Web3=NFTを使ったサービスと思っている人もいるようなので、そこは注意が必要です。 Web3関連ニュース The Song That Owns Itself (stoi.org) arrives at ETHDenver on Algorand protocol Jesse Boykins III というミュージシャンが自ら立ち上げたWeb3サービスがSTOIです。 なぜSTOIはEthereumからAlgorandに乗り換えたたか 理由は3つ上がってますが、3つ目の理由がうまく説明できないので、2つ目まで紹介しておきます。 Algorandは、 トランザクション コストが安く、安定している。ニーズがあれば、100億の トーク ンを発行することも可能。 全体的に脱炭素が考慮されている(carbon-neutral footprint)。 前回話したEthereumから他のブロックチェーンへ の動きとも合致しますね。 所有の新しいかたち、P2Pファイル共有から音楽NFTまで その他の話題 ケニア中央銀行がデジタル通貨の導入について国民から意見を募集 デジタル通貨は、 カリブ海 諸国以外では、アフリカの国が積極的なんですね。 たぶん、クロスボーダーな仕送りを改善したいということだと思います。 アダルト系SNS「OnlyFans」が認証済みNFTをプロフィール画像にできる機能を提供 アダルト系 SNS では、本人かどうかめっちゃ重要ですよね。写真でなりすましされたら困りますから。出会い系 SNS でも一緒だと思います。 英歳入関税庁が2.2億円相当の脱税案件の捜査にともないNFTを押収、英法執行機関として初 NFTの押収ってどうやるんでしょうか。プライベートキー(Wallet)を押収したということでしょうか。 BlockFiの米SECとの約115億円の和解は、今後のDeFiレンディングにとって何を意味するのか? 利益の分配をしようとすると、日本でも アメリ カでも証券とみなされます。証券なら、それなりのルールを守る必要があります。でも、守っていなかったという話です。 Web3のパワープレイヤー「アニモカブランズ」が日本進出、戦略的子会社「Animoca Brands株式会社」が11億円のシード調達 IPを利用して、Web3で儲けようという話です。僕の定義だとこれはWeb3ではないんですが... 執筆: @higa 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
電通国際情報サービス 、クロス イノベーション 本部、オープン イノベーション ラボの 比嘉康雄 です。 ブロックチェーン 週報2/21いってみようか。 Web3 Web3関連ニュース その他の話題 Web3 前回 、ク リエータ ー/ファンエコノミーがトレンドになっているという話をしましたが、その流れは今週も続いています。 ただ、その呼び方がWeb3と言われることが多くなったので、今後Web3で統一したいと思います。 Web3は、非中央集権的な Webサービス のことですが、それだとなんのことかわからないと思います。非中央集権的サービスとは、サービスの運営者がサービスを支配せず、サービスのユーザーも運営に関われるようなサービスのことです。これは、個人的な見解ですけど。 ユーザーが運営に関わる手段として、DAO(自律分散型組織)が使われることが一般的です。 DAOの参加資格は、NFTを持っていることとするケースがほとんどです。そこから短絡的に、Web3=NFTを使ったサービスと思っている人もいるようなので、そこは注意が必要です。 Web3関連ニュース The Song That Owns Itself (stoi.org) arrives at ETHDenver on Algorand protocol Jesse Boykins III というミュージシャンが自ら立ち上げたWeb3サービスがSTOIです。 なぜSTOIはEthereumからAlgorandに乗り換えたたか 理由は3つ上がってますが、3つ目の理由がうまく説明できないので、2つ目まで紹介しておきます。 Algorandは、 トランザクション コストが安く、安定している。ニーズがあれば、100億の トーク ンを発行することも可能。 全体的に脱炭素が考慮されている(carbon-neutral footprint)。 前回話したEthereumから他のブロックチェーンへ の動きとも合致しますね。 所有の新しいかたち、P2Pファイル共有から音楽NFTまで その他の話題 ケニア中央銀行がデジタル通貨の導入について国民から意見を募集 デジタル通貨は、 カリブ海 諸国以外では、アフリカの国が積極的なんですね。 たぶん、クロスボーダーな仕送りを改善したいということだと思います。 アダルト系SNS「OnlyFans」が認証済みNFTをプロフィール画像にできる機能を提供 アダルト系 SNS では、本人かどうかめっちゃ重要ですよね。写真でなりすましされたら困りますから。出会い系 SNS でも一緒だと思います。 英歳入関税庁が2.2億円相当の脱税案件の捜査にともないNFTを押収、英法執行機関として初 NFTの押収ってどうやるんでしょうか。プライベートキー(Wallet)を押収したということでしょうか。 BlockFiの米SECとの約115億円の和解は、今後のDeFiレンディングにとって何を意味するのか? 利益の分配をしようとすると、日本でも アメリ カでも証券とみなされます。証券なら、それなりのルールを守る必要があります。でも、守っていなかったという話です。 Web3のパワープレイヤー「アニモカブランズ」が日本進出、戦略的子会社「Animoca Brands株式会社」が11億円のシード調達 IPを利用して、Web3で儲けようという話です。僕の定義だとこれはWeb3ではないんですが... 執筆: @higa 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
こんにちは、 電通国際情報サービス (ISID)の上羽です。 私は クラウド イノベーション センターという組織に所属し、社内における パブリッククラウド の活用推進や クラウド テク ノロ ジー の研究開発を行っています。この記事は最近Azureでパブリックプレビューとして公開された Azure Kubernetes Service(以下、AKS)の機能であるGitOps について解説します。本記事では、CI/CD、および Kubernetes の基本的な概念をある程度理解していることを前提としています。 GitOpsとは CI/CDによるデプロイを実現する方法として、多くの人が思い浮かべるのはCIツール( GitHub ActionsやJenkinsなど)を使った方法ではないでしょうか。この方法では、 ソースコード リポジトリ への変更をトリガーとしてCIツールが起動し、ビルドなどのCI( 継続的インテグレーション )からCD(継続的デリバリー・デプロイ)までを手続き的に実行します。このようなデプロイ方法はCIOpsと呼ばれ、CIツールが中心となったシンプルで開発者にとってわかりやすい方法です。ただし、CIOpsによるデプロイはいくつか課題があります。 意図したデプロイ状況になっているか把握しにくい CIツールは変更をデプロイするだけです。デプロイ先の状態を管理したい場合は、状態を取得するために複雑なワークフローを組む必要があります。 CIツールの持つ権限が大きくなる デプロイのために、デプロイ先環境の認証情報をCIツールが持つ必要があります。 このような課題へのアプローチとして、WeaveWorks社の提唱する GitOps という方法が考えられました。GitOpsはCDの方法の1つで、誤解を恐れずに要約すると「Kuberenetes クラスタ を構成する マニフェスト のすべてをGit リポジトリ で管理し、その情報を正としてデプロイする方法」です 1 。 CIOpsが持つ課題に対して、それぞれ以下のようなアプローチをとっています。 Git リポジトリ を唯一の情報源(Single Source of Truth)とする 「Git リポジトリ に存在する マニフェスト = Kubernetes クラスタ の状態」とすることで、 クラスタ の状態を把握できるようにします。 CIとCDを分離する CIツールがデプロイ先の認証情報を持つ必要もなくなり、ビルドやテストといったワークフローに注力できるようにします。CDツールを Kubernetes クラスタ 内に配置して、デプロイに注力します。CDツールがGitで管理されている マニフェスト と自身の構成情報の差分を検知すると、 マニフェスト を自身にデプロイします。 ここまでの説明を簡単に図示したものが以下です。CIOpsでは ソースコード リポジトリ から一方向に矢印が伸びている対して、GitOpsでは マニフェスト リポジトリ を境として矢印が反対を向いています。先ほどGitOpsの特徴として述べた「唯一の情報源」が マニフェスト リポジトリ であり、この リポジトリ を境に「CIとCDの分離」を実現していることがわかります。 GitOpsを実現するツールとして Flux や ArgoCD があります。 AKS のGitOps機能にはFluxが利用されています。 AKS でGitOpsを使ってみる ここからが本題です。冒頭で紹介した AKS のGitOps機能を使ってみます。 Azure CLI を利用して環境を構築します。 リソースプロバイダーの登録とAzure CLI 拡張機能 の有効化 今回利用する AKS のGitOps機能はそのままの状態で利用できず、 リソースプロバイダー の登録が必要になります。また、 CLI からGitOpsの構成を作成するために CLI の 拡張機能 も必要です。それらを以下コマンドにより登録します。 # リソースプロバイダの登録 az provider register --namespace Microsoft.Kubernetes az provider register --namespace Microsoft.ContainerService az provider register --namespace Microsoft.KubernetesConfiguration # AKS拡張機能の有効化 az feature register --namespace Microsoft.ContainerService --name AKS-ExtensionManager # Azure CLI拡張機能のインストール az extension add -n k8s-configuration az extension add -n k8s-extension なお、これらの登録には数十分要することがあります。リソースプロバイダーの登録は非同期処理なので、コマンド az provider show -n <名前空間> -o table により登録状況を確認してください。StateがRegisteredになれば登録完了です。 AKS クラスタ の作成 以下コマンドにより AKS クラスタ を作成します。今回作成する クラスタ はGitOpsの検証利用想定のため、必要最小限の構成としています。 az aks create --resource-group < リソースグループ名 > --name < クラスタ名 > --node-vm-size Standard_B2s --node-count 1 --generate-ssh-keys GitOpsの構成 以下コマンドを実行してGitOpsの構成を AKS に追加します。 az k8s-configuration flux create \ --resource-group < リソースグループ名 > \ --cluster-name < クラスタ名 > \ --cluster-type managedClusters \ --name gitops-test \ --namespace gitops-test \ --scope cluster \ --url https://github.com/fluxcd/flux2-kustomize-helm-example \ --branch main \ --kustomization name =infra path =./infrastructure prune =true \ --kustomization name =apps path =./apps/staging prune =true dependsOn = [" infra "] コマンドについて補足します。 --resource-group <リソースグループ名> --cluster-name < クラスタ 名> 作成した AKS の クラスタ 名、および AKS が存在するリソースグループ名を指定してください。 --cluster-type managedClusters インストール対象の クラスタ として、 managedClusters を指定してください。これは AKS のマネージド クラスタ であることを意味します。ほかには connectedClusters というAzure Arc(Azureに接続したオンプレ環境)の クラスタ も指定できます。 --name gitops-test --namespace gitops-test --scope cluster name はこのGitOpsの構成を識別するための名称です。 namespace はこの構成を配置する 名前空間 を指定します。 クラスタ 内になければ新規作成されます。 scope はこの構成のアクセススコープです。検証用なので、clusterに設定します。 --url https://github.com/fluxcd/flux2-kustomize-helm-example --branch main \ url には マニフェスト が存在する リポジトリ を指定します。 本記事においては、Fluxが公開している サンプルリポジトリ を利用します。 branch は リポジトリ に存在するブランチを指定します。ここで指定したブランチに変更があった場合、 AKS にデプロイされます。ほかにコミット単位や、タグを指定できるオプションもあります。 また今回はパブリックな リポジトリ を指定しましたが、プライベートな リポジトリ を指定することも可能です。その場合は リポジトリ に対する認証情報を入力する必要があります。 --kustomization name=infra path=./infrastructure prune=true --kustomization name=apps path=./apps/staging prune=true dependsOn=["infra"] 次にKustomizationの設定です。 Kustomization とはFluxで利用するCRDの1つで、Gitで管理されている マニフェスト のうち クラスタ に適用するものを定義したファイルです。 Kubernetes のエコシステムである Kustomize とも深く関係しますが、この記事では割愛します。 今回作成するKustomizationの設定は2つ(infraとapps)です。このうちの、infra インスタンス で設定したパスにある ディレクト リ構成を見てみます。 infra/  ├ nginx/  │ └ kustomization.yaml  │ └ namespace.yaml  │ └ release.yaml  ├ sources/  │ └ kustomization.yaml  │ └ 複数マニフェスト  ├ redis/  │ └ kustomization.yaml  │ └ 複数マニフェスト  └ kustomization.yaml infra/kustomization.yaml ファイルを見てみます。 apiVersion : kustomize.config.k8s.io/v1beta1 kind : Kustomization resources : - sources - nginx - redis Kustomizationの マニフェスト で、リソースとして3つ定義されています。この3つのリソースは ディレクト リ名を表していて、それぞれの ディレクト リの中にさらに kustomization.yaml が存在します。一例として nginx ディレクト リにある kustomization.yaml の中を見てみます。 apiVersion : kustomize.config.k8s.io/v1beta1 kind : Kustomization namespace : nginx resources : - namespace.yaml - release.yaml リソースとして マニフェスト の yaml ファイルが定義されています。このように定義された マニフェスト をFluxが検知しデプロイする、という仕組みになっています。 本記事ではinfraの構成を確認してみましたが、appsについても同様です。ただし、appsの依存関係にinfraが設定されています。infraのリソースがデプロイされた後にappsをデプロイするための設定です。 デプロイの確認 まずはFluxが クラスタ にインストールされているかを確認します。ここからは視覚的にわかりやすくするためにAzure Portal を利用します。 AKS のリソースメニューから作成した クラスタ を選択し、ブレードからワークロードを選択します。 名前空間 を flux-system でフィルタすると、Fluxを構成するオブジェクトを確認できます。 Fluxに関する アーキテクチャ は説明しません。興味のある方は こちら をご参照ください。 次にGitOpsの構成を確認します。 ブレードからGitOps(プレビュー)を選択します。 先ほど CLI から作成した構成を確認できます。構成名をクリックして詳細画面に移ります。 現在のデプロイ状況を表す「状態」、同期する リポジトリ を表す「 リポジトリ 」、設定したKustomizationなどを表す「プロパティ」がそれぞれ確認できます。いくつか気になる項目をピックアップします。 コンプライアンス の状態 AzureにはAzure Policyという コンプライアンス を管理するサービスが存在します。そのAzure Policyによる検出結果がこれです。 AKS が対象としている ポリシー はこちらから確認できます。 種類 GitOpsを実現するためのツールが示されています。Flux v2であることがわかりますね。あえて表示しているということは今後ほかのツールにも対応予定なのかもしれません。(筆者個人の感想です) 構成オブジェクト インストールの状態 今回のデプロイ対象のオブジェクト、およびその状態を示しています。 リポジトリ にある kustomization.yaml を展開した結果のオブジェクトです。インストールの状態がSucceedになっているので、もうデプロイは完了しているようです。リンクをクリックして中を見てみます。 設定したKustomization自体や、同期先の リポジトリ を管理するGitRepositoryオブジェクト、 リポジトリ に管理されている マニフェスト で指定したHelmReleaseなどが確認できます。今回指定したサンプル リポジトリ からデプロイされるアプリケーションは podinfo という軽量のWebアプリケーションです。 CLI を利用して動作確認してみます。 次のコマンドで AKS に対する認証情報を取得します。 az aks get-credentials --resource-group < リソースグループ名 > --name < クラスタ名 > podinfoアプリケーションが公開されているポートを手元にポート フォワ ードして、アプリケーションに接続してみます。 # ポートフォワード kubectl -n nginx port-forward svc/nginx-ingress-controller 8081:80 & # アプリケーションへのリクエスト curl -H " Host: podinfo.staging " http://localhost:8081 { " hostname " : " podinfo-6dddd646b6-7d8fb " , " version " : " 6.0.3 " , " revision " : "" , " color " : " #34577c " , " logo " : " https://raw.githubusercontent.com/stefanprodan/podinfo/gh-pages/cuddle_clap.gif " , " message " : " greetings from podinfo v6.0.3 " , " goos " : " linux " , " goarch " : " amd64 " , " runtime " : " go1.16.9 " , " num_goroutine " : " 9 " , " num_cpu " : " 2 " } podinfoからのレスポンスが返ってきました。この hostname はPodの名前と一致しているはずです。podを取得して確認してみます。 kubectl get pods -n podinfo NAME READY STATUS RESTARTS AGE podinfo-6dddd646b6-7d8fb 1 / 1 Running 0 129m デプロイコマンドなど一切なしにアプリケーションをデプロイできました。 おわりに 本記事では AKS のGitOps機能を利用してアプリケーションをデプロイする手順を解説しました。 Flux自体はどのKubernetesプラットフォームにおいても利用可能です 。Azureにおいてはそれを 拡張機能 として利用でき、管理用の GUI が提供されていたり、Azureのサービス(Azure Policy)と組み合わせて利用できるのが利点であると筆者は考えています。 最後までお読みいただきありがとうございました。 執筆: @ueba.yuki 、レビュー: @shibata.takao ( Shodo で執筆されました ) Kuberenetes以外でもGitOpsを実現するツール( PipeCD )はありますが、解説を簡潔にするために今回は説明を省きました ↩
アバター
こんにちは、 電通国際情報サービス (ISID)の上羽です。 私は クラウド イノベーション センターという組織に所属し、社内における パブリッククラウド の活用推進や クラウド テク ノロ ジー の研究開発を行っています。この記事は最近Azureでパブリックプレビューとして公開された Azure Kubernetes Service(以下、AKS)の機能であるGitOps について解説します。本記事では、CI/CD、および Kubernetes の基本的な概念をある程度理解していることを前提としています。 GitOpsとは CI/CDによるデプロイを実現する方法として、多くの人が思い浮かべるのはCIツール( GitHub ActionsやJenkinsなど)を使った方法ではないでしょうか。この方法では、 ソースコード リポジトリ への変更をトリガーとしてCIツールが起動し、ビルドなどのCI( 継続的インテグレーション )からCD(継続的デリバリー・デプロイ)までを手続き的に実行します。このようなデプロイ方法はCIOpsと呼ばれ、CIツールが中心となったシンプルで開発者にとってわかりやすい方法です。ただし、CIOpsによるデプロイはいくつか課題があります。 意図したデプロイ状況になっているか把握しにくい CIツールは変更をデプロイするだけです。デプロイ先の状態を管理したい場合は、状態を取得するために複雑なワークフローを組む必要があります。 CIツールの持つ権限が大きくなる デプロイのために、デプロイ先環境の認証情報をCIツールが持つ必要があります。 このような課題へのアプローチとして、WeaveWorks社の提唱する GitOps という方法が考えられました。GitOpsはCDの方法の1つで、誤解を恐れずに要約すると「Kuberenetes クラスタ を構成する マニフェスト のすべてをGit リポジトリ で管理し、その情報を正としてデプロイする方法」です 1 。 CIOpsが持つ課題に対して、それぞれ以下のようなアプローチをとっています。 Git リポジトリ を唯一の情報源(Single Source of Truth)とする 「Git リポジトリ に存在する マニフェスト = Kubernetes クラスタ の状態」とすることで、 クラスタ の状態を把握できるようにします。 CIとCDを分離する CIツールがデプロイ先の認証情報を持つ必要もなくなり、ビルドやテストといったワークフローに注力できるようにします。CDツールを Kubernetes クラスタ 内に配置して、デプロイに注力します。CDツールがGitで管理されている マニフェスト と自身の構成情報の差分を検知すると、 マニフェスト を自身にデプロイします。 ここまでの説明を簡単に図示したものが以下です。CIOpsでは ソースコード リポジトリ から一方向に矢印が伸びている対して、GitOpsでは マニフェスト リポジトリ を境として矢印が反対を向いています。先ほどGitOpsの特徴として述べた「唯一の情報源」が マニフェスト リポジトリ であり、この リポジトリ を境に「CIとCDの分離」を実現していることがわかります。 GitOpsを実現するツールとして Flux や ArgoCD があります。 AKS のGitOps機能にはFluxが利用されています。 AKS でGitOpsを使ってみる ここからが本題です。冒頭で紹介した AKS のGitOps機能を使ってみます。 Azure CLI を利用して環境を構築します。 リソースプロバイダーの登録とAzure CLI 拡張機能 の有効化 今回利用する AKS のGitOps機能はそのままの状態で利用できず、 リソースプロバイダー の登録が必要になります。また、 CLI からGitOpsの構成を作成するために CLI の 拡張機能 も必要です。それらを以下コマンドにより登録します。 # リソースプロバイダの登録 az provider register --namespace Microsoft.Kubernetes az provider register --namespace Microsoft.ContainerService az provider register --namespace Microsoft.KubernetesConfiguration # AKS拡張機能の有効化 az feature register --namespace Microsoft.ContainerService --name AKS-ExtensionManager # Azure CLI拡張機能のインストール az extension add -n k8s-configuration az extension add -n k8s-extension なお、これらの登録には数十分要することがあります。リソースプロバイダーの登録は非同期処理なので、コマンド az provider show -n <名前空間> -o table により登録状況を確認してください。StateがRegisteredになれば登録完了です。 AKS クラスタ の作成 以下コマンドにより AKS クラスタ を作成します。今回作成する クラスタ はGitOpsの検証利用想定のため、必要最小限の構成としています。 az aks create --resource-group < リソースグループ名 > --name < クラスタ名 > --node-vm-size Standard_B2s --node-count 1 --generate-ssh-keys GitOpsの構成 以下コマンドを実行してGitOpsの構成を AKS に追加します。 az k8s-configuration flux create \ --resource-group < リソースグループ名 > \ --cluster-name < クラスタ名 > \ --cluster-type managedClusters \ --name gitops-test \ --namespace gitops-test \ --scope cluster \ --url https://github.com/fluxcd/flux2-kustomize-helm-example \ --branch main \ --kustomization name =infra path =./infrastructure prune =true \ --kustomization name =apps path =./apps/staging prune =true dependsOn = [" infra "] コマンドについて補足します。 --resource-group <リソースグループ名> --cluster-name < クラスタ 名> 作成した AKS の クラスタ 名、および AKS が存在するリソースグループ名を指定してください。 --cluster-type managedClusters インストール対象の クラスタ として、 managedClusters を指定してください。これは AKS のマネージド クラスタ であることを意味します。ほかには connectedClusters というAzure Arc(Azureに接続したオンプレ環境)の クラスタ も指定できます。 --name gitops-test --namespace gitops-test --scope cluster name はこのGitOpsの構成を識別するための名称です。 namespace はこの構成を配置する 名前空間 を指定します。 クラスタ 内になければ新規作成されます。 scope はこの構成のアクセススコープです。検証用なので、clusterに設定します。 --url https://github.com/fluxcd/flux2-kustomize-helm-example --branch main \ url には マニフェスト が存在する リポジトリ を指定します。 本記事においては、Fluxが公開している サンプルリポジトリ を利用します。 branch は リポジトリ に存在するブランチを指定します。ここで指定したブランチに変更があった場合、 AKS にデプロイされます。ほかにコミット単位や、タグを指定できるオプションもあります。 また今回はパブリックな リポジトリ を指定しましたが、プライベートな リポジトリ を指定することも可能です。その場合は リポジトリ に対する認証情報を入力する必要があります。 --kustomization name=infra path=./infrastructure prune=true --kustomization name=apps path=./apps/staging prune=true dependsOn=["infra"] 次にKustomizationの設定です。 Kustomization とはFluxで利用するCRDの1つで、Gitで管理されている マニフェスト のうち クラスタ に適用するものを定義したファイルです。 Kubernetes のエコシステムである Kustomize とも深く関係しますが、この記事では割愛します。 今回作成するKustomizationの設定は2つ(infraとapps)です。このうちの、infra インスタンス で設定したパスにある ディレクト リ構成を見てみます。 infra/  ├ nginx/  │ └ kustomization.yaml  │ └ namespace.yaml  │ └ release.yaml  ├ sources/  │ └ kustomization.yaml  │ └ 複数マニフェスト  ├ redis/  │ └ kustomization.yaml  │ └ 複数マニフェスト  └ kustomization.yaml infra/kustomization.yaml ファイルを見てみます。 apiVersion : kustomize.config.k8s.io/v1beta1 kind : Kustomization resources : - sources - nginx - redis Kustomizationの マニフェスト で、リソースとして3つ定義されています。この3つのリソースは ディレクト リ名を表していて、それぞれの ディレクト リの中にさらに kustomization.yaml が存在します。一例として nginx ディレクト リにある kustomization.yaml の中を見てみます。 apiVersion : kustomize.config.k8s.io/v1beta1 kind : Kustomization namespace : nginx resources : - namespace.yaml - release.yaml リソースとして マニフェスト の yaml ファイルが定義されています。このように定義された マニフェスト をFluxが検知しデプロイする、という仕組みになっています。 本記事ではinfraの構成を確認してみましたが、appsについても同様です。ただし、appsの依存関係にinfraが設定されています。infraのリソースがデプロイされた後にappsをデプロイするための設定です。 デプロイの確認 まずはFluxが クラスタ にインストールされているかを確認します。ここからは視覚的にわかりやすくするためにAzure Portal を利用します。 AKS のリソースメニューから作成した クラスタ を選択し、ブレードからワークロードを選択します。 名前空間 を flux-system でフィルタすると、Fluxを構成するオブジェクトを確認できます。 Fluxに関する アーキテクチャ は説明しません。興味のある方は こちら をご参照ください。 次にGitOpsの構成を確認します。 ブレードからGitOps(プレビュー)を選択します。 先ほど CLI から作成した構成を確認できます。構成名をクリックして詳細画面に移ります。 現在のデプロイ状況を表す「状態」、同期する リポジトリ を表す「 リポジトリ 」、設定したKustomizationなどを表す「プロパティ」がそれぞれ確認できます。いくつか気になる項目をピックアップします。 コンプライアンス の状態 AzureにはAzure Policyという コンプライアンス を管理するサービスが存在します。そのAzure Policyによる検出結果がこれです。 AKS が対象としている ポリシー はこちらから確認できます。 種類 GitOpsを実現するためのツールが示されています。Flux v2であることがわかりますね。あえて表示しているということは今後ほかのツールにも対応予定なのかもしれません。(筆者個人の感想です) 構成オブジェクト インストールの状態 今回のデプロイ対象のオブジェクト、およびその状態を示しています。 リポジトリ にある kustomization.yaml を展開した結果のオブジェクトです。インストールの状態がSucceedになっているので、もうデプロイは完了しているようです。リンクをクリックして中を見てみます。 設定したKustomization自体や、同期先の リポジトリ を管理するGitRepositoryオブジェクト、 リポジトリ に管理されている マニフェスト で指定したHelmReleaseなどが確認できます。今回指定したサンプル リポジトリ からデプロイされるアプリケーションは podinfo という軽量のWebアプリケーションです。 CLI を利用して動作確認してみます。 次のコマンドで AKS に対する認証情報を取得します。 az aks get-credentials --resource-group < リソースグループ名 > --name < クラスタ名 > podinfoアプリケーションが公開されているポートを手元にポート フォワ ードして、アプリケーションに接続してみます。 # ポートフォワード kubectl -n nginx port-forward svc/nginx-ingress-controller 8081:80 & # アプリケーションへのリクエスト curl -H " Host: podinfo.staging " http://localhost:8081 { " hostname " : " podinfo-6dddd646b6-7d8fb " , " version " : " 6.0.3 " , " revision " : "" , " color " : " #34577c " , " logo " : " https://raw.githubusercontent.com/stefanprodan/podinfo/gh-pages/cuddle_clap.gif " , " message " : " greetings from podinfo v6.0.3 " , " goos " : " linux " , " goarch " : " amd64 " , " runtime " : " go1.16.9 " , " num_goroutine " : " 9 " , " num_cpu " : " 2 " } podinfoからのレスポンスが返ってきました。この hostname はPodの名前と一致しているはずです。podを取得して確認してみます。 kubectl get pods -n podinfo NAME READY STATUS RESTARTS AGE podinfo-6dddd646b6-7d8fb 1 / 1 Running 0 129m デプロイコマンドなど一切なしにアプリケーションをデプロイできました。 おわりに 本記事では AKS のGitOps機能を利用してアプリケーションをデプロイする手順を解説しました。 Flux自体はどのKubernetesプラットフォームにおいても利用可能です 。Azureにおいてはそれを 拡張機能 として利用でき、管理用の GUI が提供されていたり、Azureのサービス(Azure Policy)と組み合わせて利用できるのが利点であると筆者は考えています。 最後までお読みいただきありがとうございました。 執筆: @ueba.yuki 、レビュー: @shibata.takao ( Shodo で執筆されました ) Kuberenetes以外でもGitOpsを実現するツール( PipeCD )はありますが、解説を簡潔にするために今回は説明を省きました ↩
アバター
電通国際情報サービス 、クロス イノベーション 本部、オープン イノベーション ラボの 比嘉康雄 です。 今回は、 ブロックチェーン に関する先週のニュースをまとめます。 Ethereum以外のブロックチェーンを使う動きが活発化 クリエーター/ファンエコノミーとDAO その他のニュース Ethereum以外の ブロックチェーン を使う動きが活発化 これまでNFTといえばEthereumでした。しかし、 スループット の低さ、手数料(ガス代)の高さがネックとなりEthereum以外の ブロックチェーン を使う動きが活発化しています。 Dapper LabsのFlowブロックチェーン活用、ミクシィとDAZNがスポーツ特化型NFTマーケットプレイスを今春提供開始 Flowは、 NBA Top Shot でも使われている ブロックチェーン です。 ゲーム特化ブロックチェーン「Oasys」発表、設立メンバーにバンダイナムコやセガも Oasys で面白いのは、レイヤー2をプライベートチェーンにして、手数料(ガス代)を無料にしているところですね。 Polygonがプライベートトークンセールを実施、ソフトバンクなどから約520億円を調達 PolygonはEthereum上のレイヤー2なので、Ethereum以外の ブロックチェーン といって良いのか微妙なところですが、Ethereumのネックを解決する動きとしては一緒ですね。 ク リエータ ー/ファンエコノミーとDAO ク リエータ ーの資金調達の手段として、これまでもNFTが利用されてきましたが、投機的な側面が強いものでした。 しかし、これからはNFTの購入者であるファンを大切にしていく流れが生まれています。 こうしたク リエータ ーとファンを結びつける組織として、DAO(Decentralized Autonomous Organization )を活用する動きが広まっています。 DAOとは分散型で自律的に機能する組織のことです。従来の組織では、中央に意思決定をする組織/人がいて、その決定に従い組織が運営されていました。これに対してDAOでは、ルールに基づいて活動を行い、中央の組織がなくても自動化された運営ができます。 ク リエータ ー/ファンエコノミーがどのようにDAOを利用しているかというと、ファンは、ファンクラブの会員証に当たるNFTを購入します。このNFTがあれば、DAOに所属できます。 DAOに所属していると、ク リエータ ーの Vlog /写真/未公開の曲など、そのDAOに所属していなければ見られないコンテンツを見ることができます。ク リエータ ーとファンが直接チャットできるようにしているDAOもあります。 セレブとビデオチャットできるCameo、限定アートワークなどが手に入るNFTプロジェクト「Cameo Pass」発表 分散型「マーベル」のようなNFTメディア帝国を目指すPixel Vault、約115億円の資金を調達 YouTubeがNFTやライブショッピングなどクリエイターツールを拡充へ、TikTokやInstagramに対抗 YouTube は、まだDAOは活用していませんがそうなっていくんじゃないかと予想します。 その他のニュース グラフィティ&ストリートアートに特化した、東京発のNFTアート専門オークション「TOTEMO」 トークンブリッジのWormhole、ハッカーが約370億円相当の暗号資産を盗んだことを確認 イーサリアム開発者ツールプラットフォーム「Hardhat」開発元、a16zなどの寄付により非営利団体「Nomic Foundation」に 執筆: @higa 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
電通国際情報サービス 、クロス イノベーション 本部、オープン イノベーション ラボの 比嘉康雄 です。 今回は、 ブロックチェーン に関する先週のニュースをまとめます。 Ethereum以外のブロックチェーンを使う動きが活発化 クリエーター/ファンエコノミーとDAO その他のニュース Ethereum以外の ブロックチェーン を使う動きが活発化 これまでNFTといえばEthereumでした。しかし、 スループット の低さ、手数料(ガス代)の高さがネックとなりEthereum以外の ブロックチェーン を使う動きが活発化しています。 Dapper LabsのFlowブロックチェーン活用、ミクシィとDAZNがスポーツ特化型NFTマーケットプレイスを今春提供開始 Flowは、 NBA Top Shot でも使われている ブロックチェーン です。 ゲーム特化ブロックチェーン「Oasys」発表、設立メンバーにバンダイナムコやセガも Oasys で面白いのは、レイヤー2をプライベートチェーンにして、手数料(ガス代)を無料にしているところですね。 Polygonがプライベートトークンセールを実施、ソフトバンクなどから約520億円を調達 PolygonはEthereum上のレイヤー2なので、Ethereum以外の ブロックチェーン といって良いのか微妙なところですが、Ethereumのネックを解決する動きとしては一緒ですね。 ク リエータ ー/ファンエコノミーとDAO ク リエータ ーの資金調達の手段として、これまでもNFTが利用されてきましたが、投機的な側面が強いものでした。 しかし、これからはNFTの購入者であるファンを大切にしていく流れが生まれています。 こうしたク リエータ ーとファンを結びつける組織として、DAO(Decentralized Autonomous Organization )を活用する動きが広まっています。 DAOとは分散型で自律的に機能する組織のことです。従来の組織では、中央に意思決定をする組織/人がいて、その決定に従い組織が運営されていました。これに対してDAOでは、ルールに基づいて活動を行い、中央の組織がなくても自動化された運営ができます。 ク リエータ ー/ファンエコノミーがどのようにDAOを利用しているかというと、ファンは、ファンクラブの会員証に当たるNFTを購入します。このNFTがあれば、DAOに所属できます。 DAOに所属していると、ク リエータ ーの Vlog /写真/未公開の曲など、そのDAOに所属していなければ見られないコンテンツを見ることができます。ク リエータ ーとファンが直接チャットできるようにしているDAOもあります。 セレブとビデオチャットできるCameo、限定アートワークなどが手に入るNFTプロジェクト「Cameo Pass」発表 分散型「マーベル」のようなNFTメディア帝国を目指すPixel Vault、約115億円の資金を調達 YouTubeがNFTやライブショッピングなどクリエイターツールを拡充へ、TikTokやInstagramに対抗 YouTube は、まだDAOは活用していませんがそうなっていくんじゃないかと予想します。 その他のニュース グラフィティ&ストリートアートに特化した、東京発のNFTアート専門オークション「TOTEMO」 トークンブリッジのWormhole、ハッカーが約370億円相当の暗号資産を盗んだことを確認 イーサリアム開発者ツールプラットフォーム「Hardhat」開発元、a16zなどの寄付により非営利団体「Nomic Foundation」に 執筆: @higa 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
こんにちは、X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループ、2021年新卒入社の大西です。私は昨年10月にセキュリティグループへ配属され、3ヶ月間、技術調査として GitHub 上の機密情報を見つけ出すチェックツールを調査していました。今日は、 GitHub リポジトリ に機密情報をプッシュしてしまった場合に、それを検知するツールについてお話しします。 皆さんは、 ソースコード を書く際に機密情報をハードコードしてしまっていないでしょうか。機密情報をハードコーディングしたまま GitHub 上に公開すると、その情報を不正利用されてしまいます。例えば、 AWS のアクセスキーID、シークレットアクセスキーが漏洩すると、勝手にEC2 インスタンス を立ち上げられて仮想通貨のマイニングに使用され、莫大な請求が届くという事例が相次いでいます。これを防止するためには機密情報のハードコード防止が必要ですが、人間が注意するにしても限界があります。自動化はできないか、と考えたのが今回技術調査を行った背景です。ISIDでも業務で GitHub を利用しており、パブリック リポジトリ もいくつか所有しています。ISIDが持つパブリック リポジトリ は こちら から見られます。 技術調査記録 Gitleaks 使ってみた感想 Secret Scanning 使ってみた感想 今後の対応 技術調査記録 それでは、不正利用などから会社の資産、情報を守るためのチェックツールを調査した記録を書いていきます。 この調査のためにまず行ったことは、候補に挙がっていたチェックツールの特徴や使い方を知ることです。今回いくつかのチェックツールを対象に調査したのですが、 オープンソース ツールも含まれており、 GitHub 上で ソースコード 自体も見ることができました。 それぞれのツールの特徴を掴んだ後は、実際に様々な機密情報(もし流出しても使用できないように失効済み)を リポジトリ に仕込んでから、各ツールでスキャンし検知率や誤検知率、解析速度などを比較しました。ここで時間がかかったのが、 リポジトリ に仕込むための機密情報を様々なプロバイダーから収集してくることです。今回は、 AWS やAzure、 GCP などの クラウド 系を中心に16種類の機密情報を埋め込んだのですが、 クラウド に触るのがほぼ初めてだったのでどこにどんな情報があるのかを探すのが大変でした。社内の クラウド グループの方からもお力をお借りし、無事にターゲットとしていた機密情報をゲットしました。そして、それらを埋め込んだ リポジトリ をスキャンし、この調査で私たちがツールに求める要件が明確になってきました。その要件とは、 - 過去のコミット履歴もスキャンできること - メンテナンス、サポート等が充実していること - 費用面が安く抑えられること でした。 過去のコミット履歴のスキャンに関しては、HEADコミットやmainブランチだけでなく、全てのブランチ、そしてブランチごとの履歴も含め全てスキャンしてくれるツールであることが重要になってきます。なぜなら、履歴に機密情報が含まれていれば簡単にそれを盗めるからです。また、メンテナンス・サポートに関しては、機密情報の種類は年々変わっていくので、検出できる機密情報の種類を増やしたり、検知のルールを変えたりしなければなりません(例えば、 AWS のアクセスキーIDはAKIA, ANPAなどの プレフィックス が付いているが、将来この プレフィックス が変更されると、それに伴って検知のルールも変更しなければならない)。将来のことも考え、細かなところまできっちりメンテナンスしてくれているようなツールを選ぶことが重要であると考えました。費用面に関しては、もちろん安く抑えられた方がいいですね。 というわけで、今回は5つツールを調査しましたが、その中でも良いと判断した オープンソース ツール1つと有償ツール1つをご紹介したいと思います。 Gitleaks Gitleaks は、機密情報をハードコードすることを防ぐ静的解析ツール。 オープンソース なので無償で利用でき、HomebrewやDockerなどから簡単にインストールできる。リモート、ローカルどちらのスキャンも可能。 言語: Go 検出する機密情報の種類: AWS , GCP , SSH , GitHub , Slackなどの機密情報、約100種類(version 8.2.7) https://github.com/zricethezav/gitleaks/blob/master/config/gitleaks.toml GitHub でのStar数: 8.8k 機密情報の検出方法: 正規表現 下図のように"gitleaks detect -v"とコマンドを打つと、検出された機密情報が表示される↓ 1 使ってみた感想 メリット 過去のコミット履歴まで遡って検知してくれる メインブランチだけでなく、他のブランチ、そしてそれぞれのブランチの履歴までスキャンできる ルールのカスタマイズが可能( 正規表現 でルールを書く必要がある) ルールをカスタマイズすればより多くの機密情報をスキャンできる 8系がリリースされ、7系に比べスキャンスピードが速くなった 8系がリリースされてから検知できるシークレット数が増加した(+60種類くらい) pre-commit hookを使用すればコミットする前にスキャンされるため、 GitHub に誤って機密情報をプッシュしてしまうことがなくなる GitHub Actions が GitHub Marketplaceに登録されている ymlファイルに記述するだけで、CI/CDパイプラインの中で検出できる デメリット 8系で検知できるシークレット数が増えた分、誤検知が多くなった 具体的には、調査した リポジトリ の中にIonic API tokenという名前で45件の検出があった ソースコード の中'section ...'という文字列を含むKeyがありその中の'ion 'という文字列が 正規表現 で引っかかった Configファイルのallow listの中に上記の文字列を入れれば、スキャンはスキップされる 正規表現 をカスタマイズすれば誤検知を減らすことができる Secret Scanning Secret Scanningは GitHub が提供するスキャン機能。Secret Scanningは、すべてのパブリック リポジトリ 、および GitHub Advanced Securityが有効になっている組織が所有するプライベー トリポジ トリで利用できる。パブリック リポジトリ では自動的にSecret Scanningが有効になり、 リポジトリ をプッシュすると、コンテンツをスキャンして機密情報を探す。 検出する機密情報の種類: - パブリック リポジトリ :約100種類 - プライベー トリポジ トリ:約100種類 詳しくは、 こちら のページで! GitHub リポジトリ のメインページ -> Securityタブ -> 左サイドバーのSecret scanning alertsをクリックすると、機密情報が見つかった場合その一覧が見られる↓ 2 使ってみた感想 メリット Secret Scanningで機密情報が検出された場合、 GitHub が機密情報を発行したサービスプロバイダーと連携をとって対応に当たってくれる パブリック リポジトリ トーク ンなどの機密情報(シークレット)を発行したサービスプロバイダー( AWS , Azureなど)に通知する サービスプロバイダーは、資格情報を検証してから、シークレットを取り消すか、新しいシークレットを発行するか、直接連絡するかを決定する(ユーザーまたはサービスプロバイダーに関連するリスクによって異なる) プライベー トリポジ トリ リポジトリ 管理者と組織の所有者にメールアラートを送信する リポジトリ にシークレットをコミットしたユーザーに対し、関連するシークレットスキャンアラートへのリンクを含む電子メールアラートを送信する。コミット作成者は、 リポジトリ 内のアラートを表示し、アラートを解決できる。 GitHub は リポジトリ にアラートを表示する Secret Scanningを使用していて困った時に GitHub 社のサポートを受けられる(英語対応) ルールのカスタマイズが可能( 正規表現 でルールを書く必要がある) デメリット プライベー トリポジ トリをスキャンするためには、所属する組織が、有償の GitHub Advanced Securityをオプション購入してからSecret Scanningを有効にしなければならない リポジトリ をプッシュした後にスキャンするため、スキャンする時点ですでに機密情報が漏れている可能性がある 今後の対応 今後は、試験運用として、Gitleaksを使って機密情報のスキャンをしていくことになりました。理由としては、上記でも述べたように、過去のコミット履歴もスキャンできること、メンテナンスが充実していること、無償で利用できることがあげられます。また、 GitHub 上のコミュニケーションも活発で、例えば、投稿されたissueはアクティブに対応されています。Gitleaksで社内の リポジトリ を定期的にスキャンし、機密情報が検出された時は リポジトリ 所有者に通達するなどし、開発者のセキュリティ意識向上につなげていきます。 (もしも、機密情報を見つけたら......) もし、機密情報を GitHub にプッシュしていることに気が付いたら、その情報を完全に削除するのには手間がかかります。なぜなら、gitの仕組み上変更履歴は全てツリー中に含まれており、履歴をたどれば機密情報の含まれたコミットまでたどり着けることがあるため、履歴を全て改変する必要があるからです。機密情報の含まれたコミットをいかなるブランチやタグからも遡れないようにする必要があります。そのためにはBFGツールなどを使ってgit リポジトリ の履歴から特定の文字列やファイルを削除します。また、 GitHub 上ではgit リポジトリ そのものの他に、Pull RequestやIssueに貼られたEmbed commentなど、git リポジトリ 内のコンテンツが表示されうる機会が存在します。 これらの情報を完全に削除するには GitHub のサポートに連絡して削除してもらうしかありません。 機密情報の削除については こちら の記事が参考になりました。 今後はツールを活用し機密情報を外部に漏らさない対策を強化していきたいと思います。 執筆: @onishi.mayu 、レビュー: @higa ( Shodo で執筆されました ) 図は https://asciinema.org/a/455683 のものを引用しています ↩ 図は https://docs.github.com/en/enterprise-cloud@latest/code-security/secret-scanning/managing-alerts-from-secret-scanning?learn=secret_scanning&learnProduct=code-security のものを引用しています ↩
アバター
こんにちは、X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループ、2021年新卒入社の大西です。私は昨年10月にセキュリティグループへ配属され、3ヶ月間、技術調査として GitHub 上の機密情報を見つけ出すチェックツールを調査していました。今日は、 GitHub リポジトリ に機密情報をプッシュしてしまった場合に、それを検知するツールについてお話しします。 皆さんは、 ソースコード を書く際に機密情報をハードコードしてしまっていないでしょうか。機密情報をハードコーディングしたまま GitHub 上に公開すると、その情報を不正利用されてしまいます。例えば、 AWS のアクセスキーID、シークレットアクセスキーが漏洩すると、勝手にEC2 インスタンス を立ち上げられて仮想通貨のマイニングに使用され、莫大な請求が届くという事例が相次いでいます。これを防止するためには機密情報のハードコード防止が必要ですが、人間が注意するにしても限界があります。自動化はできないか、と考えたのが今回技術調査を行った背景です。ISIDでも業務で GitHub を利用しており、パブリック リポジトリ もいくつか所有しています。ISIDが持つパブリック リポジトリ は こちら から見られます。 技術調査記録 Gitleaks 使ってみた感想 Secret Scanning 使ってみた感想 今後の対応 技術調査記録 それでは、不正利用などから会社の資産、情報を守るためのチェックツールを調査した記録を書いていきます。 この調査のためにまず行ったことは、候補に挙がっていたチェックツールの特徴や使い方を知ることです。今回いくつかのチェックツールを対象に調査したのですが、 オープンソース ツールも含まれており、 GitHub 上で ソースコード 自体も見ることができました。 それぞれのツールの特徴を掴んだ後は、実際に様々な機密情報(もし流出しても使用できないように失効済み)を リポジトリ に仕込んでから、各ツールでスキャンし検知率や誤検知率、解析速度などを比較しました。ここで時間がかかったのが、 リポジトリ に仕込むための機密情報を様々なプロバイダーから収集してくることです。今回は、 AWS やAzure、 GCP などの クラウド 系を中心に16種類の機密情報を埋め込んだのですが、 クラウド に触るのがほぼ初めてだったのでどこにどんな情報があるのかを探すのが大変でした。社内の クラウド グループの方からもお力をお借りし、無事にターゲットとしていた機密情報をゲットしました。そして、それらを埋め込んだ リポジトリ をスキャンし、この調査で私たちがツールに求める要件が明確になってきました。その要件とは、 - 過去のコミット履歴もスキャンできること - メンテナンス、サポート等が充実していること - 費用面が安く抑えられること でした。 過去のコミット履歴のスキャンに関しては、HEADコミットやmainブランチだけでなく、全てのブランチ、そしてブランチごとの履歴も含め全てスキャンしてくれるツールであることが重要になってきます。なぜなら、履歴に機密情報が含まれていれば簡単にそれを盗めるからです。また、メンテナンス・サポートに関しては、機密情報の種類は年々変わっていくので、検出できる機密情報の種類を増やしたり、検知のルールを変えたりしなければなりません(例えば、 AWS のアクセスキーIDはAKIA, ANPAなどの プレフィックス が付いているが、将来この プレフィックス が変更されると、それに伴って検知のルールも変更しなければならない)。将来のことも考え、細かなところまできっちりメンテナンスしてくれているようなツールを選ぶことが重要であると考えました。費用面に関しては、もちろん安く抑えられた方がいいですね。 というわけで、今回は5つツールを調査しましたが、その中でも良いと判断した オープンソース ツール1つと有償ツール1つをご紹介したいと思います。 Gitleaks Gitleaks は、機密情報をハードコードすることを防ぐ静的解析ツール。 オープンソース なので無償で利用でき、HomebrewやDockerなどから簡単にインストールできる。リモート、ローカルどちらのスキャンも可能。 言語: Go 検出する機密情報の種類: AWS , GCP , SSH , GitHub , Slackなどの機密情報、約100種類(version 8.2.7) https://github.com/zricethezav/gitleaks/blob/master/config/gitleaks.toml GitHub でのStar数: 8.8k 機密情報の検出方法: 正規表現 下図のように"gitleaks detect -v"とコマンドを打つと、検出された機密情報が表示される↓ 1 使ってみた感想 メリット 過去のコミット履歴まで遡って検知してくれる メインブランチだけでなく、他のブランチ、そしてそれぞれのブランチの履歴までスキャンできる ルールのカスタマイズが可能( 正規表現 でルールを書く必要がある) ルールをカスタマイズすればより多くの機密情報をスキャンできる 8系がリリースされ、7系に比べスキャンスピードが速くなった 8系がリリースされてから検知できるシークレット数が増加した(+60種類くらい) pre-commit hookを使用すればコミットする前にスキャンされるため、 GitHub に誤って機密情報をプッシュしてしまうことがなくなる GitHub Actions が GitHub Marketplaceに登録されている ymlファイルに記述するだけで、CI/CDパイプラインの中で検出できる デメリット 8系で検知できるシークレット数が増えた分、誤検知が多くなった 具体的には、調査した リポジトリ の中にIonic API tokenという名前で45件の検出があった ソースコード の中'section ...'という文字列を含むKeyがありその中の'ion 'という文字列が 正規表現 で引っかかった Configファイルのallow listの中に上記の文字列を入れれば、スキャンはスキップされる 正規表現 をカスタマイズすれば誤検知を減らすことができる Secret Scanning Secret Scanningは GitHub が提供するスキャン機能。Secret Scanningは、すべてのパブリック リポジトリ 、および GitHub Advanced Securityが有効になっている組織が所有するプライベー トリポジ トリで利用できる。パブリック リポジトリ では自動的にSecret Scanningが有効になり、 リポジトリ をプッシュすると、コンテンツをスキャンして機密情報を探す。 検出する機密情報の種類: - パブリック リポジトリ :約100種類 - プライベー トリポジ トリ:約100種類 詳しくは、 こちら のページで! GitHub リポジトリ のメインページ -> Securityタブ -> 左サイドバーのSecret scanning alertsをクリックすると、機密情報が見つかった場合その一覧が見られる↓ 2 使ってみた感想 メリット Secret Scanningで機密情報が検出された場合、 GitHub が機密情報を発行したサービスプロバイダーと連携をとって対応に当たってくれる パブリック リポジトリ トーク ンなどの機密情報(シークレット)を発行したサービスプロバイダー( AWS , Azureなど)に通知する サービスプロバイダーは、資格情報を検証してから、シークレットを取り消すか、新しいシークレットを発行するか、直接連絡するかを決定する(ユーザーまたはサービスプロバイダーに関連するリスクによって異なる) プライベー トリポジ トリ リポジトリ 管理者と組織の所有者にメールアラートを送信する リポジトリ にシークレットをコミットしたユーザーに対し、関連するシークレットスキャンアラートへのリンクを含む電子メールアラートを送信する。コミット作成者は、 リポジトリ 内のアラートを表示し、アラートを解決できる。 GitHub は リポジトリ にアラートを表示する Secret Scanningを使用していて困った時に GitHub 社のサポートを受けられる(英語対応) ルールのカスタマイズが可能( 正規表現 でルールを書く必要がある) デメリット プライベー トリポジ トリをスキャンするためには、所属する組織が、有償の GitHub Advanced Securityをオプション購入してからSecret Scanningを有効にしなければならない リポジトリ をプッシュした後にスキャンするため、スキャンする時点ですでに機密情報が漏れている可能性がある 今後の対応 今後は、試験運用として、Gitleaksを使って機密情報のスキャンをしていくことになりました。理由としては、上記でも述べたように、過去のコミット履歴もスキャンできること、メンテナンスが充実していること、無償で利用できることがあげられます。また、 GitHub 上のコミュニケーションも活発で、例えば、投稿されたissueはアクティブに対応されています。Gitleaksで社内の リポジトリ を定期的にスキャンし、機密情報が検出された時は リポジトリ 所有者に通達するなどし、開発者のセキュリティ意識向上につなげていきます。 (もしも、機密情報を見つけたら......) もし、機密情報を GitHub にプッシュしていることに気が付いたら、その情報を完全に削除するのには手間がかかります。なぜなら、gitの仕組み上変更履歴は全てツリー中に含まれており、履歴をたどれば機密情報の含まれたコミットまでたどり着けることがあるため、履歴を全て改変する必要があるからです。機密情報の含まれたコミットをいかなるブランチやタグからも遡れないようにする必要があります。そのためにはBFGツールなどを使ってgit リポジトリ の履歴から特定の文字列やファイルを削除します。また、 GitHub 上ではgit リポジトリ そのものの他に、Pull RequestやIssueに貼られたEmbed commentなど、git リポジトリ 内のコンテンツが表示されうる機会が存在します。 これらの情報を完全に削除するには GitHub のサポートに連絡して削除してもらうしかありません。 機密情報の削除については こちら の記事が参考になりました。 今後はツールを活用し機密情報を外部に漏らさない対策を強化していきたいと思います。 執筆: @onishi.mayu 、レビュー: @higa ( Shodo で執筆されました ) 図は https://asciinema.org/a/455683 のものを引用しています ↩ 図は https://docs.github.com/en/enterprise-cloud@latest/code-security/secret-scanning/managing-alerts-from-secret-scanning?learn=secret_scanning&learnProduct=code-security のものを引用しています ↩
アバター
こんにちは。XI本部 クラウド イノベーション センターの柴田です。 最近ではアプリケーションを実行する手段としてコンテナ技術が広く用いられています。 しかし、本番環境で稼働できる品質のコンテナイメージを作る際、初学者がコンテナ設計の段階で見落としがちなポイントがいくつかあります。 そこで今回はコンテナイメージを設計する際の推奨事項をいくつかご紹介したいと思います。 コンテナとは コンテナイメージを設計する際の推奨事項 The Twelve-Factor Appに従ってアプリケーションを実装する プロセス:アプリケーションを1つもしくは複数のステートレスなプロセスとして実行する 廃棄容易性:高速な起動とグレースフルシャットダウンで堅ろう性を最大化する ログ:ログをイベントストリームとして扱う 1コンテナにつき1アプリケーション コンテナイメージに余分なパッケージを含めない 適切なベースイメージを選択する マルチステージビルドを活用する コンテナイメージに機密情報を含めない コンテナ内におけるアプリケーションの実行ユーザーを root 以外に変更する ベストプラクティスに従ってDockerfileを書く コンテナイメージの脆弱性スキャンを実施する おわりに 参考 コンテナとは コンテナは仮想化技術の一種です。あらかじめ作成したコンテナイメージをもとに、異なる環境上で環境の影響を受けずにコンテナ化したアプリケーションを実行できます。 コンテナにはいくつかのメリットがあります。 可搬性が高い :コンテナを使うことで、あらかじめ作成したコンテナイメージをもとに、異なる環境上で環境の影響を受けずにコンテナ化したアプリケーションを実行できます。例えば、開発環境と本番環境の間に差異があって開発環境で動いたアプリケーションを本番環境にデプロイしたところ動かなかった、といった事態をある程度防ぐことができます。 仮想マシン と比べて軽量 :従来のハイパーバイザー型の 仮想マシン と異なり、コンテナはホストマシンの カーネル を利用してプロセスとして起動します。そのためコンテナは 仮想マシン と比べて高速に起動・停止できます。 コンテナイメージを設計する際の推奨事項 The Twelve-Factor Appに従ってアプリケーションを実装する コンテナ化するアプリケーションは The Twelve-Factor App に従っていることが望ましいです。The Twelve-Factor Appはモダンなアプリケーション開発における12個のベストプ ラク ティスです。 コードベース :バージョン管理されている1つのコードベースと複数のデプロイ 依存関係 :依存関係を明示的に宣言し分離する 設定 :設定を 環境変数 に格納する バックエンドサービス :バックエンドサービスをアタッチされたリソースとして扱う ビルド、リリース、実行 :ビルド、リリース、実行の3つのステージを厳密に分離する プロセス :アプリケーションを1つもしくは複数のステートレスなプロセスとして実行する ポート バインディング :ポート バインディング を通してサービスを公開する 並行性 : プロセスモデル によってスケールアウトする 廃棄容易性 :高速な起動とグレースフルシャットダウンで堅ろう性を最大化する 開発/本番一致 :開発、ステージング、本番環境をできるだけ一致させた状態を保つ ログ :ログをイベントストリームとして扱う 管理プロセス :管理タスクを1回限りのプロセスとして実行する 詳細は The Twelve-Factor App を参照ください。 コンテナのライフサイクルは 仮想マシン と比べて短く 基盤障害とそれに伴う自動復旧 オートスケーリング 新しいバージョンのコンテナイメージのデプロイ などをトリガーにコンテナの終了・再作成が頻繁に発生します。アプリケーションのコンテナをいつでも安全に終了できることができるよう、特に「6.プロセス」「9.廃棄容易性」「11.ログ」は適切に実装することが望ましいです。 プロセス:アプリケーションを1つもしくは複数のステートレスなプロセスとして実行する コンテナが永続データやセッション情報を持っている場合、コンテナを安全に終了するのが難しくなります。永続データやセッション情報はできるだけ外部のデータストアで管理し、コンテナはなるべくステートレスにするのが望ましいです。 廃棄容易性:高速な起動とグレースフルシャットダウンで堅ろう性を最大化する アプリケーションのコンテナはいつでもユーザーへの影響なく安全に終了できるよう実装されていることが望ましいです。 アプリケーションを安全に終了する方法の1つにSIGTERMシグナルの適切な処理が挙げられます。多くのコンテナ オーケストレーション ツールはコンテナの終了時にSIGTERMシグナルを送信します。例えばWebアプリケーションならSIGTERMシグナルを受け取ったら現在のリク エス トを全て処理した後で安全に終了するようアプリケーションを実装するとよいでしょう。 ログ:ログをイベントストリームとして扱う アプリケーションのログをファイルとして出力している場合、コンテナの終了に伴うログ消失の恐れがあります。アプリケーションのログは標準出力・ 標準エラー出力 に出力し、コンテナ オーケストレーション ツール側で収集・保存することが望ましいです。 1コンテナにつき1アプリケーション 1つのコンテナにつき1つのアプリケーションのみを起動することが望ましいです。 仮想マシン では1つの 仮想マシン 上でWeb・AP・DBなど複数のアプリケーションを起動することがありましたが、コンテナでは1つのコンテナ上に複数のアプリケーションを起動することは推奨されません。かわりに、アプリケーションごとにコンテナを起動してください。 コンテナイメージに余分なパッケージを含めない 攻撃者による攻撃を防ぐため、本番環境で実行するコンテナイメージは、余分なパッケージがインストールされておらず、攻撃の余地は少ないことが望ましいです。 適切なベースイメージを選択する アプリケーションをコンテナ化する際、まずベースとなるコンテナイメージを選択します。ベースイメージは軽量かつセキュアなものを選択することが望ましいです。具体的にはまず distroless の使用を検討するとよいでしょう。 マルチステージビルドを活用する ソースコード や コンパイラ など、アプリケーションのビルド時には必要だけれども実行時には不要なパッケージがあります。 マルチステージビルド を活用することで、アプリケーションのビルド用コンテナイメージと実行用コンテナイメージを分離し、最終的なビルド成果物のコンテナイメージに含まれるパッケージを最小化できます。 コンテナイメージに機密情報を含めない コンテナイメージにはクレデンシャルなどの機密情報を含めないでください。かわりに、クレデンシャルなどの機密情報はコンテナの実行時に 環境変数 経由でアプリケーションへ渡すようにしてください。 また以下のようなDockerfileを書かないようにしてください。 rm コマンドで機密情報を削除してもコンテナイメージのレイヤーとして機密情報が残ります。 # 機密情報をコンテナイメージへ追加 COPY credentials ./ # 何らかの処理を実行 RUN ... # コンテナイメージから不要になった機密情報を削除(実際にはcredentialsはレイヤとしてコンテナイメージに残っている) RUN rm ./credentials コンテナ内におけるアプリケーションの実行ユーザーを root 以外に変更する コンテナ内におけるアプリケーションの実行ユーザーは root 以外に変更することが望ましいです。 万が一攻撃者にアプリケーションの 脆弱性 を悪用された場合、実行ユーザーが root だと、コンテナだけでなく、コンテナの稼働するホストマシンも攻撃される恐れがあります。 ベストプ ラク ティスに従ってDockerfileを書く Dockerfileはベストプ ラク ティスに従って記述することが望ましいです。 Best practices for writing Dockerfiles | Docker Documentation Dockerfileがベストプ ラク ティスに従っているかを 機械的 にチェックしてくれるツールも存在します。 hadolint dockle コンテナイメージの 脆弱性 スキャンを実施する コンテナイメージに 脆弱性 のあるパッケージが含まれていないか定期的にスキャンすることが望ましいです。 コンテナイメージの 脆弱性 スキャンを実行するツールには例えば以下があります。 Trivy Clair また主要なイメージ レジストリ ではコンテナイメージの登録時に自動的に 脆弱性 スキャンを実行する機能を提供しています。 Amazon ECR Azure Container Registry Google Container Registry Docker Hub (有償プランのみ) おわりに 本記事では、まだコンテナをあまり使い馴れていない初学者の方向けに、コンテナイメージを設計する際の推奨事項をいくつかご紹介しました。本記事が読んでいただいた方のお役に立てば幸いです。 参考 The Twelve-Factor App (日本語訳) Best practices for writing Dockerfiles | Docker Documentation コンテナ構築のおすすめの方法  |  Cloud アーキテクチャ センター  |  Google Cloud Dockerfileのベストプラクティス Top 20 | Sysdig ECS のアプリケーションを正常にシャットダウンする方法 | Amazon Web Services ブログ What a Windows Container looks like ? - Speaker Deck 軽量Dockerイメージに安易にAlpineを使うのはやめたほうがいいという話 - inductor's blog Kubernetes完全ガイド 第2版 - インプレスブックス Docker/Kubernetes開発・運用のためのセキュリティ実践ガイド(Compass Booksシリーズ) | マイナビブックス 執筆: @shibata.takao 、レビュー: @fhiroaki ( Shodo で執筆されました )
アバター
こんにちは。XI本部 クラウド イノベーション センターの柴田です。 最近ではアプリケーションを実行する手段としてコンテナ技術が広く用いられています。 しかし、本番環境で稼働できる品質のコンテナイメージを作る際、初学者がコンテナ設計の段階で見落としがちなポイントがいくつかあります。 そこで今回はコンテナイメージを設計する際の推奨事項をいくつかご紹介したいと思います。 コンテナとは コンテナイメージを設計する際の推奨事項 The Twelve-Factor Appに従ってアプリケーションを実装する プロセス:アプリケーションを1つもしくは複数のステートレスなプロセスとして実行する 廃棄容易性:高速な起動とグレースフルシャットダウンで堅ろう性を最大化する ログ:ログをイベントストリームとして扱う 1コンテナにつき1アプリケーション コンテナイメージに余分なパッケージを含めない 適切なベースイメージを選択する マルチステージビルドを活用する コンテナイメージに機密情報を含めない コンテナ内におけるアプリケーションの実行ユーザーを root 以外に変更する ベストプラクティスに従ってDockerfileを書く コンテナイメージの脆弱性スキャンを実施する おわりに 参考 コンテナとは コンテナは仮想化技術の一種です。あらかじめ作成したコンテナイメージをもとに、異なる環境上で環境の影響を受けずにコンテナ化したアプリケーションを実行できます。 コンテナにはいくつかのメリットがあります。 可搬性が高い :コンテナを使うことで、あらかじめ作成したコンテナイメージをもとに、異なる環境上で環境の影響を受けずにコンテナ化したアプリケーションを実行できます。例えば、開発環境と本番環境の間に差異があって開発環境で動いたアプリケーションを本番環境にデプロイしたところ動かなかった、といった事態をある程度防ぐことができます。 仮想マシン と比べて軽量 :従来のハイパーバイザー型の 仮想マシン と異なり、コンテナはホストマシンの カーネル を利用してプロセスとして起動します。そのためコンテナは 仮想マシン と比べて高速に起動・停止できます。 コンテナイメージを設計する際の推奨事項 The Twelve-Factor Appに従ってアプリケーションを実装する コンテナ化するアプリケーションは The Twelve-Factor App に従っていることが望ましいです。The Twelve-Factor Appはモダンなアプリケーション開発における12個のベストプ ラク ティスです。 コードベース :バージョン管理されている1つのコードベースと複数のデプロイ 依存関係 :依存関係を明示的に宣言し分離する 設定 :設定を 環境変数 に格納する バックエンドサービス :バックエンドサービスをアタッチされたリソースとして扱う ビルド、リリース、実行 :ビルド、リリース、実行の3つのステージを厳密に分離する プロセス :アプリケーションを1つもしくは複数のステートレスなプロセスとして実行する ポート バインディング :ポート バインディング を通してサービスを公開する 並行性 : プロセスモデル によってスケールアウトする 廃棄容易性 :高速な起動とグレースフルシャットダウンで堅ろう性を最大化する 開発/本番一致 :開発、ステージング、本番環境をできるだけ一致させた状態を保つ ログ :ログをイベントストリームとして扱う 管理プロセス :管理タスクを1回限りのプロセスとして実行する 詳細は The Twelve-Factor App を参照ください。 コンテナのライフサイクルは 仮想マシン と比べて短く 基盤障害とそれに伴う自動復旧 オートスケーリング 新しいバージョンのコンテナイメージのデプロイ などをトリガーにコンテナの終了・再作成が頻繁に発生します。アプリケーションのコンテナをいつでも安全に終了できることができるよう、特に「6.プロセス」「9.廃棄容易性」「11.ログ」は適切に実装することが望ましいです。 プロセス:アプリケーションを1つもしくは複数のステートレスなプロセスとして実行する コンテナが永続データやセッション情報を持っている場合、コンテナを安全に終了するのが難しくなります。永続データやセッション情報はできるだけ外部のデータストアで管理し、コンテナはなるべくステートレスにするのが望ましいです。 廃棄容易性:高速な起動とグレースフルシャットダウンで堅ろう性を最大化する アプリケーションのコンテナはいつでもユーザーへの影響なく安全に終了できるよう実装されていることが望ましいです。 アプリケーションを安全に終了する方法の1つにSIGTERMシグナルの適切な処理が挙げられます。多くのコンテナ オーケストレーション ツールはコンテナの終了時にSIGTERMシグナルを送信します。例えばWebアプリケーションならSIGTERMシグナルを受け取ったら現在のリク エス トを全て処理した後で安全に終了するようアプリケーションを実装するとよいでしょう。 ログ:ログをイベントストリームとして扱う アプリケーションのログをファイルとして出力している場合、コンテナの終了に伴うログ消失の恐れがあります。アプリケーションのログは標準出力・ 標準エラー出力 に出力し、コンテナ オーケストレーション ツール側で収集・保存することが望ましいです。 1コンテナにつき1アプリケーション 1つのコンテナにつき1つのアプリケーションのみを起動することが望ましいです。 仮想マシン では1つの 仮想マシン 上でWeb・AP・DBなど複数のアプリケーションを起動することがありましたが、コンテナでは1つのコンテナ上に複数のアプリケーションを起動することは推奨されません。かわりに、アプリケーションごとにコンテナを起動してください。 コンテナイメージに余分なパッケージを含めない 攻撃者による攻撃を防ぐため、本番環境で実行するコンテナイメージは、余分なパッケージがインストールされておらず、攻撃の余地は少ないことが望ましいです。 適切なベースイメージを選択する アプリケーションをコンテナ化する際、まずベースとなるコンテナイメージを選択します。ベースイメージは軽量かつセキュアなものを選択することが望ましいです。具体的にはまず distroless の使用を検討するとよいでしょう。 マルチステージビルドを活用する ソースコード や コンパイラ など、アプリケーションのビルド時には必要だけれども実行時には不要なパッケージがあります。 マルチステージビルド を活用することで、アプリケーションのビルド用コンテナイメージと実行用コンテナイメージを分離し、最終的なビルド成果物のコンテナイメージに含まれるパッケージを最小化できます。 コンテナイメージに機密情報を含めない コンテナイメージにはクレデンシャルなどの機密情報を含めないでください。かわりに、クレデンシャルなどの機密情報はコンテナの実行時に 環境変数 経由でアプリケーションへ渡すようにしてください。 また以下のようなDockerfileを書かないようにしてください。 rm コマンドで機密情報を削除してもコンテナイメージのレイヤーとして機密情報が残ります。 # 機密情報をコンテナイメージへ追加 COPY credentials ./ # 何らかの処理を実行 RUN ... # コンテナイメージから不要になった機密情報を削除(実際にはcredentialsはレイヤとしてコンテナイメージに残っている) RUN rm ./credentials コンテナ内におけるアプリケーションの実行ユーザーを root 以外に変更する コンテナ内におけるアプリケーションの実行ユーザーは root 以外に変更することが望ましいです。 万が一攻撃者にアプリケーションの 脆弱性 を悪用された場合、実行ユーザーが root だと、コンテナだけでなく、コンテナの稼働するホストマシンも攻撃される恐れがあります。 ベストプ ラク ティスに従ってDockerfileを書く Dockerfileはベストプ ラク ティスに従って記述することが望ましいです。 Best practices for writing Dockerfiles | Docker Documentation Dockerfileがベストプ ラク ティスに従っているかを 機械的 にチェックしてくれるツールも存在します。 hadolint dockle コンテナイメージの 脆弱性 スキャンを実施する コンテナイメージに 脆弱性 のあるパッケージが含まれていないか定期的にスキャンすることが望ましいです。 コンテナイメージの 脆弱性 スキャンを実行するツールには例えば以下があります。 Trivy Clair また主要なイメージ レジストリ ではコンテナイメージの登録時に自動的に 脆弱性 スキャンを実行する機能を提供しています。 Amazon ECR Azure Container Registry Google Container Registry Docker Hub (有償プランのみ) おわりに 本記事では、まだコンテナをあまり使い馴れていない初学者の方向けに、コンテナイメージを設計する際の推奨事項をいくつかご紹介しました。本記事が読んでいただいた方のお役に立てば幸いです。 参考 The Twelve-Factor App (日本語訳) Best practices for writing Dockerfiles | Docker Documentation コンテナ構築のおすすめの方法  |  Cloud アーキテクチャ センター  |  Google Cloud Dockerfileのベストプラクティス Top 20 | Sysdig ECS のアプリケーションを正常にシャットダウンする方法 | Amazon Web Services ブログ What a Windows Container looks like ? - Speaker Deck 軽量Dockerイメージに安易にAlpineを使うのはやめたほうがいいという話 - inductor's blog Kubernetes完全ガイド 第2版 - インプレスブックス Docker/Kubernetes開発・運用のためのセキュリティ実践ガイド(Compass Booksシリーズ) | マイナビブックス 執筆: @shibata.takao 、レビュー: @fhiroaki ( Shodo で執筆されました )
アバター
みなさん、こんにちは。 電通国際情報サービス (ISID)コーポレート本部システム推進部の宮井です。この記事ではISIDの基幹系システムのDB変更( Oracle → Amazon Aurora )にまつわる意思決定のお話をします。 自己紹介 なぜOracleから移行するのか おわりに 自己紹介 その前に少しだけ自己紹介を。わたくし「システム推進部」という部署の部長を務めているのですが、「システム推進部」という部署名って、これだけ聞いてもよく分からないですよね? えぇ、私もそう思います。一言で申し上げれば、「情シス+α」といったところです。社内インフラ・ネットワークに加え基幹系システムの開発/運用/保守、はたまたIT全般統制、さらには情報セキュリティといった業務領域をカバーする部署となります。 なぜ Oracle から移行するのか いま「基幹系システムの開発」と書きましたが、一般的に基幹系システムってパッケージを買ってきて必要なカスタマイズを施したうえで使うというケースが多い・・・ですよね? ですが、ISIDではなにを思ったか(!?)、十数年前になんと フルスクラッチ で自社開発してしまいました。そうして生まれた基幹系システムは、社内から寄せられる手厳しい声に耐えながらも(涙)、その後さまざまなエンハンスを積み重ね現在に至るまでいちおうISIDグループの屋台骨を裏から支えています。 で、今般、その基幹系システムのDBを Oracle から PostgreSQL 、正確に言えば PostgreSQL と互換性のある Amazon Aurora に乗り換えることとなりました。 ご承知のとおり、 Oracle は確かに抜群の実績と信頼性を誇ります。ですが、いかんせんお値段がちと高い。加えてライセンス体系も非常に複雑、さらには保守費用が毎年数%ずつ 複利 方式で上がっていくというオマケも。(涙) 基幹系システムということもあり、これまではずっと Oracle を使い続けてきました。しかし、こうしたコストやリスクの点、他の DBMS も機能面や信頼性の面で Oracle に比肩するほど充実してきている点を考慮し、今回Auroraへ乗り換える方針としました。 決定打となったのは、コストシミュレーション。 当然ながらDBを変えることにより大規模なシステム改修/テストが必要となり、それには相応のコストが掛かるわけです。それでもなお、乗り換えることで大きなコストメリットが得られるという結果になりました。(あくまで弊社での使い方を想定した場合ですので、この点は十分にご注意ください) また、わたくし個人的には 社内システムも従来の重厚長大で安全確実なガチガチ路線から、リスクを取ってより良い方向を目指して挑戦したい という思いもあります。 おわりに かくして始まった Oracle 卒業プロジェクトですが、実はまだ開発の真っ最中です。実際にやってみて技術面でどういった課題があり、それをどのようなアプローチで乗り越えていったのか等、後日に改めてこのISID Tech Blogでお伝えする予定です。 有益な情報をみなさまにご提供できるようにするためにも、なにはともあれプロジェクト関係者一同、 Oracle 卒業プロジェクトを成功させるべく、ガンバリマス。 最後までお読みいただき、ありがとうございました。 執筆: @miyai 、レビュー: @handa.kenta ( Shodo で執筆されました )
アバター
みなさん、こんにちは。 電通国際情報サービス (ISID)コーポレート本部システム推進部の宮井です。この記事ではISIDの基幹系システムのDB変更( Oracle → Amazon Aurora )にまつわる意思決定のお話をします。 自己紹介 なぜOracleから移行するのか おわりに 自己紹介 その前に少しだけ自己紹介を。わたくし「システム推進部」という部署の部長を務めているのですが、「システム推進部」という部署名って、これだけ聞いてもよく分からないですよね? えぇ、私もそう思います。一言で申し上げれば、「情シス+α」といったところです。社内インフラ・ネットワークに加え基幹系システムの開発/運用/保守、はたまたIT全般統制、さらには情報セキュリティといった業務領域をカバーする部署となります。 なぜ Oracle から移行するのか いま「基幹系システムの開発」と書きましたが、一般的に基幹系システムってパッケージを買ってきて必要なカスタマイズを施したうえで使うというケースが多い・・・ですよね? ですが、ISIDではなにを思ったか(!?)、十数年前になんと フルスクラッチ で自社開発してしまいました。そうして生まれた基幹系システムは、社内から寄せられる手厳しい声に耐えながらも(涙)、その後さまざまなエンハンスを積み重ね現在に至るまでいちおうISIDグループの屋台骨を裏から支えています。 で、今般、その基幹系システムのDBを Oracle から PostgreSQL 、正確に言えば PostgreSQL と互換性のある Amazon Aurora に乗り換えることとなりました。 ご承知のとおり、 Oracle は確かに抜群の実績と信頼性を誇ります。ですが、いかんせんお値段がちと高い。加えてライセンス体系も非常に複雑、さらには保守費用が毎年数%ずつ 複利 方式で上がっていくというオマケも。(涙) 基幹系システムということもあり、これまではずっと Oracle を使い続けてきました。しかし、こうしたコストやリスクの点、他の DBMS も機能面や信頼性の面で Oracle に比肩するほど充実してきている点を考慮し、今回Auroraへ乗り換える方針としました。 決定打となったのは、コストシミュレーション。 当然ながらDBを変えることにより大規模なシステム改修/テストが必要となり、それには相応のコストが掛かるわけです。それでもなお、乗り換えることで大きなコストメリットが得られるという結果になりました。(あくまで弊社での使い方を想定した場合ですので、この点は十分にご注意ください) また、わたくし個人的には 社内システムも従来の重厚長大で安全確実なガチガチ路線から、リスクを取ってより良い方向を目指して挑戦したい という思いもあります。 おわりに かくして始まった Oracle 卒業プロジェクトですが、実はまだ開発の真っ最中です。実際にやってみて技術面でどういった課題があり、それをどのようなアプローチで乗り越えていったのか等、後日に改めてこのISID Tech Blogでお伝えする予定です。 有益な情報をみなさまにご提供できるようにするためにも、なにはともあれプロジェクト関係者一同、 Oracle 卒業プロジェクトを成功させるべく、ガンバリマス。 最後までお読みいただき、ありがとうございました。 執筆: @miyai 、レビュー: @handa.kenta ( Shodo で執筆されました )
アバター
みなさんこんにちは、 電通国際情報サービス (ISID)X イノベーション 本部ソフトウェアデザインセンターの佐藤太一です。 この記事では、Git を使った仕事のやり方(以降は Git ワークフローと記載)を設計する上での検討事項を説明します。 これによって、読者の皆さんがGitワークフローを適切に定義できるようになることを主たる目的としています。 また、筆者の能力不足によって記載しきれなかった考慮事項について、より深く Git を使いこなしている識者からの指摘を受ける機会を得ることを副次的な目的とします。 この記事には書かれていないものの、検討すべき事項について知見のある方はブログ記事を書いたり、 Twitter 等の SNS で指摘してくださるとありがたいです。 はじめに 基本的な考え方 Git ワークフロー設計における考慮事項 チームの人数 monorepoの検討 参考文献 プロジェクト管理ツールとの連携 Pull Request ブランチ名 頻出する定型業務の自動化(CI/CD サービスとの統合) ビルド 静的解析 ソースコードの自動生成 単体テスト 結合テスト ドキュメントの自動生成 パッケージング 依存ライブラリのアップデート システムテスト アプリケーション運用との統合 Git ワークフローの実際 シングルブランチモデル 参考文献 GitHub Flow 参考文献 GitLab Flow 参考文献 Gitflow (A successful Git branching model) 参考文献 まとめ はじめに この記事は、Git や GitHub の基本的な使い方を読者が理解していることを前提に書いています。 つまり、Git サーバとしての GitHub だけでなく、その機能を十分に使うことを想定しています。 もし基本的な使い方に不安があるなら、まずは以下のサイトや書籍で学習してください。 Git でのバージョン コントロールの概要 半日程度でgitについて把握できる Microsoft の教育コンテンツです。 サル先生の Git 入門 Git の初歩の初歩を図解しながら学習できるサイトです 改訂 2 版 わかばちゃんと学ぶ Git 使い方入門 漫画で Git を学習するならこの本がおすすめです 新しい領域として Git を学ぶならとっつき易さで選ぶのがいいと個人的には考えています いちばんやさしい Git&GitHub の教本 人気講師が教えるバージョン管理&共有入門 Git と GitHub をまとめて学習するならこの本がおすすめです 基本的な考え方 Gitのワークフローを決めるのは仕事の質を上げるためです。失敗の可能性を低減し、また失敗したとしても労せずやり直せることで効率よく働けるようにしましょう。 Gitのワークフローを検討する際、常に意識してほしいのは 作りこみ過ぎない ということです。 まず、起きてもいない問題のすべてを事前に対応しようとしないでください。また、プロジェクトメンバーがワークフローの習熟に使う時間をできるかぎり減らしましょう。 プロジェクトメンバーが複雑すぎるワークフローに振り回されると仕事の生産性は確実に低減し、その結果として仕事の質が落ちていきます。 Gitのワークフローは自分のプロジェクトにおいて問題が起きてもやり直せるギリギリまで軽量化しましょう。 Git ワークフロー設計における考慮事項 この記事の主題となるGitワークフロー設計における考慮事項を説明します。 ここでは、Gitを中心にした仕事のやり方全体を指してGit ワークフローと呼んでいます。 少し抽象的な話が続きますので、Gitワークフローについて考えたことがないなら、記事の後半にある Git ワークフローの実際を読んでから戻ってくると少しわかり易くなるかもしれません。 チームの人数 仕事のやり方を考えるのですから、まずチームがどのようなメンバーで構成されているのか検討しましょう。 例えば、2,3人のチームでは労せずお互いのやっていることを正確に把握できるので、複雑なワークフローは必要ありません。ミスはするものとしてお互い補いあう想定で働くと効率がいいです。 こういった少人数であれば、最低限の ケアレスミス を防ぐためだけにワークフローを定義しましょう。 メンバーが10人を超えたあたりで、お互いの仕事内容を正確に把握しあうのは難しくなります。 これくらいの人数から、Gitワークフローを検討する上でメンバーの入れ替わり頻度について考慮しましょう。 数年間固定されていて気心の知れたメンバー10人と、この3か月で集まった10人では採用すべきワークフローに多少の違いはでるでしょう。 メンバーが固定されているチームでは少人数のチームと同じようなGitワークフローで十分です。変に手順を難しくするのはやめましょう。 メンバーが流動的なチームでは作業記録を残しやすい形でワークフローを定義すると、問題が発生した際に対処し易くなります。 メンバーが30人を超えると大抵のプロジェクトでお互いの仕事内容を正確に把握しあうのは不可能です。 チームを2つか3つに分割した上で役割と責任範囲を明確にした作業分担が必要になるでしょう。 メンバーは流動的で少なくとも四半期に一回程度は誰かが抜けて、誰かが増えます。 つまり、ワークフローに習熟していないメンバーがいつでも一定数存在することを前提にワークフロー操作が失敗しても他のチームにその影響が派生しないことを重視しましょう。 こういう状況ではチーム同士の成果を結合するワークフローはどうしても複雑なものになりがちです。 monorepoの検討 Gitでは小さい単位の開発成果物ごとに リポジトリ を作る方が大抵の場合うまくいきます。 例えば、サーバアプリケーションとクライアントアプリケーション、デプロイ スクリプト 、マニュアル類、設計ドキュメントなどをそれぞれを別な リポジトリ として管理するとよいでしょう。 成果物の種類毎に リポジトリ が分かれていれば、それらを更新するメンバーの役割と責任範囲が自然と明確で分かり易くなります。 例えば、クライアントアプリケーションの開発メンバーには、サーバアプリケーションの リポジトリ に対して読み取り専用の権限を付与することを考えてみましょう。まず、コードの読むことはできるので問題発生時に分析はできます。 一方で、クライアントアプリケーションの修正のつもりでサーバアプリケーションに修正を加えてしまうといったミスは発生しなくなります。 一方で、 リポジトリ が分かれていると問題になるケースもあります。 考えてみてください。.protoや JSON Schemaのように、サーバアプリケーションとクライアントアプリケーションが通信するための プロトコル を定義するIDLは、どちらの リポジトリ に置くのがいいでしょうか?それとも、 通信プロトコル の定義は設計ドキュメントとセットで新しい リポジトリ を作るべきでしょうか?また、クライアントアプリケーションとしてモバイルアプリケーションを実装することにしました、 Android と iOS では リポジトリ を分けるべきでしょうか? このように何かあるたびに リポジトリ を増やしていくと、 リポジトリ の増加は止まりません。 リポジトリ が増えると手元の開発環境を適切な状態に維持するための手順は煩雑になります。また、CIやCDなどのビルドプロセスも複雑になります。 これに対して、開発成果物を全て単一の リポジトリ 内に収めてしまうのがmonorepoです。 単一の リポジトリ 内にあらゆる成果物を入れて管理すれば、メンバーの新規追加時における難しさは大きく低減します。 monorepoを採用することによって、まず新しいメンバーが既存の成果物を探し回るために必要なコストが低減します。 そして、ドキュメントや ソースコード の間で 相対パス を使ったリンクを張れます。種類の成果物同士で情報の連携が取り易いことは、仕様に対する理解の速度を早めます。 Google のように依存ライブラリも含めて全てmonorepoに入れてしまうような組織もあります。 対応を検討すべきデメリットはいくつかあります。 ファイル数やコミット数が多くなるので、単純に リポジトリ が大きくなる GitHub の リポジトリ は5GB程度に収めるよう推奨されている Repository size limits cloneやfetchに時間がかかり易くなる 対応策は、 パーシャルクローンとシャロークローンを活用しよう タグの対象範囲が分かりづらくなり易い タグの 命名規則 をしっかり考える必要がある メンバーが編集できる成果物の範囲を限定するのが難しい GitHub にはCODEOWNERSという仕組みで ディレクト リ単位やファイル単位の必須レビュアーを設定できる コードオーナーについて 参考文献 Git で monorepo を扱う際の課題とヒント Why Google Stores Billions of Lines of Code in a Single Repository monorepo.tools プロジェクト管理ツールとの連携 ソフトウェア開発をするためにGitを使うのですから、プロジェクト管理ツールとGitワークフローは密接に関連します。 GitHub にはタスク管理機能が備わっていますが、ある一定以上の規模で利用しようとするには機能不足です。 ボトムアップ な管理をするにも、例えばタスクの分割と集約の機能がなかったり、チケットの検索クエリが貧弱だったりします。 トップダウン な管理という意味では、例えばタスクの進捗状況を俯瞰するのは非常に難しいですし、承認フローを構築したり WBS を引いたりといったことも出来ません。 ステークホルダー となる組織が複数あり、プロジェクトとの関係性において一定の複雑さがあるならプロジェクト管理ツールを導入すべきです。 理想的には、ソフトウェア開発系のタスクとそれ以外のタスクにおける情報連携を自動的に実施するのが望ましいでしょう。つまり、プロジェクト管理ツールに定義したワークフローとGitを使ったワークフローを上手く連携させるのが望ましいというわけです。 基本的にはプロジェクト管理ツール側に GitHub 上のPull Requestやブランチの情報を取込んでいく形になります。 例えば、広く使われているプロジェクト管理ツールであるJiraには スマートコミット という仕組みがあり、コミットメッセージを所定の書式で記述するとJiraのIssueが持つ状態を操作できます。 プロジェクト管理ツール側がWebhookに対応しているならPull Requestから情報を送信できます。 また、 GitHub Actions を使うならPull Requestでなくても情報をプロジェクト管理ツールに送信できます。 Pull Request Gitはそれぞれの開発者が持っている リポジトリ は完全に独立しており対等なものです。 SVN や CVS といった構成管理ツールを使う場合、開発者のローカルにチェックアウトされた成果物は作業用のコピーであることとは対照的です。 一方で、内部統制がしっかりと効いた開発を行うなら、プロジェクトの進捗と厳密に同期していると言える リポジトリ を一つ決めます。 その中心となる リポジトリ を GitHub 上に置くと使える仕組みがPull Request(PR)です。他のツールではMerge Requestと呼ばれることもあります。 PRを使うと、コードレビューとそれにひもづく定型作業の自動化(CI)をサーバ上で実行できます。 GitHub を使うプロジェクトでは、Gitワークフローを設計するならPRをどうやって使うかが議論の中心になるでしょう。 もしPRを使うのであれば、Issue Templateを用意するとPR自体の質を底上げできます。 About issue and pull request templates ブランチ名 Gitを利用する際にブランチ名を工夫すると、CIで作業を自動化し易くなります。 ワークフローの設計次第ではもう少しルールを追加しますが、基本的にはこれだけです。 作業内容を想像し易い単語をブランチ名に含める ブランチ名は基本的に半角英数だけを使う 意味の区切りは / を使う 区切り文字として / を使うのは、GitKrakenを始めとした GUI クライアントが / 区切りでブランチを階層表示してくれるからです。 この画像では dev/my-task 、 dev/your-task に加えて折りたたまれた test/awesome ブランチがあります。 頻出する定型業務の自動化(CI/CD サービスとの統合) Gitのワークフローを作りこむ理由は、定型業務を自動化してより効率よく働くためです。 どんな作業をCIの中に組込みたいのかをしっかりと検討することはワークフロー設計をする上で大切です。 ここでは、参考のためにCIへ組み込むと便利な定型業務を列挙しますが、最初から全てに取り組もうとしないでください。 ご自分のプロジェクトにおいて明らかに恩恵があると思われるものを少量取り入れるのがおすすめです。 PRに伴って動作するCIワークフローのパフォーマンスが悪いと開発効率は確実に低減しますので、あれもこれもと盛り込み過ぎないようにしましょう。 また、筆者が知らなかったり、列挙できなかった定型業務は非常に多岐にわたることが予想されます。 読者の皆さんがご自分のプロジェクトで定型化している業務を SNS やブログで共有して下さることを期待しています。 ビルド CIに組み込む作業として一番最初に思い浮かぶのがビルドです。 ここでいうビルドは、 ソースコード から実行可能な状態に変換する過程で発生する定型的な業務全般を指しています。 静的解析 コンパイル 型の プログラミング言語 なら コンパイラ がある程度の静的解析に基づくエラーを出力してくれます。 大抵の プログラミング言語 には、Lintと呼ばれるタイプの ソースコード の妥当性を検証するツールがあります。 特に筆者が好きなのは、ベストプ ラク ティスに基づいたコードへ矯正するタイプの静的解析ツールです。 使うことが明確に非推奨というわけではないけども、新しいコードではそのような書き方をしない方がよいという知識を複数の プログラミング言語 にわたって学習し続けるのは非常に難しいです。 例えば、ReactのuseEffectに [] を指定すべきケースは限定的で基本的に何か値を設定したほうがいいとか。 goは1.13でerrorの望ましい書き方が変わったとか。 アプリケーションとしては動作する以上、望ましくないスタイルのコードを書いていても気が付きませんよね。しかし、望ましくないスタイルのコードはソフトウェアの品質を確実に低減させます。 こういったことを学習するきっかけをえるためにLintツールはPRからトリガーされるタスクに組み込むのが望ましいでしょう。 ソースコード の自動生成 ソースコード の自動生成は、それを実施すべきかどうかを判断するのが難しいタスクです。 ジェネリック スやテンプレートなど言語の機能を利用することで、継続的な ソースコード の自動生成は不要にできる場合もあります。 また、自動生成した ソースコード をGitのような構成管理ツールにチェックインするかどうかも注意深く検討してください。 自動生成した ソースコード をチェックインする場合には、 リポジトリ のサイズが大きくなり易い代わりに、CIの実行時間を抑えられます。 一方で、自動生成した ソースコード をチェックインしない場合には、 リポジトリ のサイズを小さく保てる代わりにCIの実行時間が長くなります。 筆者としては、 ソースコード の自動生成は開発者のローカルマシンで実行しPRを出して リポジトリ にチェックインする方式を好んで使っています。それは、基本的には望ましくないことだと分かっていても、生成物に対して手を入れたいという要求を完全に消すことはできないからです。 単体テスト データベースや アプリケーションサーバ を使わない 単体テスト は、PRからトリガーされるタスクとして実行したいタスクです。開発者が実施するテストのあり方を学びたい人には 和田さんのスライド をおすすめしておきます。 QuickCheckのようなパラメタライズドテストや、コード カバレッジ の計測は実行コストが高いのでそれほど頻繁に行わなくてもいいかもしれません。そういう高コストなテストは、Gitワークフローからは切り離して夜中や早朝に定期実行するようなアプローチをおすすめします。 結合テスト ここではデータベースや アプリケーションサーバ 、リバースプロキシなどを構成した上で実施するテストを 結合テスト とします。 テストに時間がかかりすぎるとか、テストが終わらなくて タイムアウト するとか言った問題はスローテスト問題と呼ばれています。 長期間にわたって価値を生み出すソフトウェアを実装していくにあたって、スローテスト問題は避けられません。 スローテスト問題に対する基本的なアプローチは、品質を担保できるギリギリの頻度で実行し、テストの実行コストと品質の トレードオフ における妥当な割合を見つけることです。 機械学習 によってテストの実行対象を賢く選択してくれる Launchable は、この問題に対する画期的な解決策を提供してくれるかもしれないと期待しています。 筆者の個人的な好みは、 ナイトリー ビルドのような形で一日に数回は 結合テスト を実行するものの、日常的なPRトリガーのワークフローからは外すことです。 ドキュメントの自動生成 Javadoc のようなドキュメントコメントから開発者向けドキュメントを自動生成するタスクもまたビルドタスクの一部としてよく組込みます。 フレームワーク やライブラリのように繰り返し再利用されるものを実装する際にはドキュメントコメントをしっかり書くのが望ましいでしょう。一方で、アプリケーションコードのドキュメントコメントは必要最小限に留めておくのがおすすめです。 筆者の経験上、コメント部分は動作しないためかコードと整合性をもってメンテナンスされづらく、その結果コメントとコードの内容にズレがおきることでコードを理解する妨げになる事の方が多いからです。 ドキュメントの自動生成は多くても一日に一回程度、大抵の場合は一週間に一回くらいの頻度で実施すれば事足りるので、PRから実行されるCIタスクには組み込まないことが多いです。 代わりに、クーロンジョブのような形で定期的に実行してPRを自動生成する方が低コストです。 今の GitHub Pagesにはアクセス制御機能があり、許可された開発者だけが閲覧できるサイトを簡単に構築できることは知っておいてください。 Changing the visibility of your GitHub Pages site パッケージング パッケージングは、Dockerコンテナイメージや、jarのような形式で アーカイブ されたファイルを作ることです。 単に アーカイブ すればいいものから署名が必要なものまで、言語やデプロイ先によって実行すべきタスクは様々です。 ただ、大きな傾向としてパッケージングはリリース作業の一部として実行するタスクです。 パッケージングとして実施すべきタスクの内容は極めて複雑で多岐にわたるため、今回はそういうものがあるとだけ言っておきます。 依存ライブラリのアップデート 現代的なソフトウェア開発において オープンソース ソフトウェア( OSS )の利用は避けられません。 そして、 OSS のライブラリは高頻度にアップデートされます。特に緊急性の高いセキュリティパッチと呼ばれるようなアップデートは実施しなければなりません。 とはいえ、定期的な依存ライブラリのアップデートを手動でやっていると時間がどれだけあっても不足します。つまり、自動化するしかありません。そのためには、比較的高品質な 回帰テスト が必要になります。 依存ライブラリのアップデートを自動的にやってくれるサービスとしては、 Renovate や、 GitHub に標準で組み込まれている Dependabot を使うのが良いでしょう。これらのサービスを利用するとライブラリをアップデートするPRを自動的に作ってくれます。 自動的に作られるPRをGitワークフローの中でどういう位置づけにするのかは、丁寧に検討してください。深く考えずに導入すると、無限に生成されるPRに押しつぶされてしまいますよ。 これは、筆者がメンテナンスしている社内ツールの 回帰テスト が不安定なせいで自動マージが上手く動作せず蓄積してしまったPRの山です。 システムテスト Gitのワークフローを検討する上で、 システムテスト の位置づけは整理するのが望ましいでしょう。 まずは、ワークフローの一部に システムテスト を組み込むのかどうかを検討してください。 システムテスト の自動化は極めて深遠な話題ですので、大抵のプロジェクトでは組み込まないという結論になるでしょう。 ソフトウェアが価値を生み長生きするならば、規模は大きくなり複雑になっていきます。 その中で システムテスト の自動化は取り組むべき課題の一つです。 Gitワークフローの一部として システムテスト を実行すべき状況になっていないかを、ときどき気にかけてください。 アプリケーション運用との統合 Gitワークフローとアプリケーション運用の統合については、パッケージングの話題でありデプロイの話題です。 少なくとも、ワークフローの終了ステータスをどのような形で、いつ誰に通知するのかは検討してください。 伝統的にはショートメッセージサービス(SMS)や電子メールを使っていますよね。 現代的にはSlackやTeams、LINEなどチャットツールへのメッセージ送信となるでしょう。 また、アプリケーション運用側からGitに対する情報提供をどのように実施するのかを検討するのも忘れないでください。 基本的にはプロジェクト管理ツールに対するタスクの起票( GitHub Issuesなど)を介して行うことになるはずです。 Git ワークフローの実際 ここからは、ワークフロー設計の参考になるようなGitワークフローをいくつか紹介します。 ただし、具体的な作業手順については説明していません。参考文献に詳細な説明のあるページをリンクしておきましたので参考にしてください。 ここで紹介しているものは、あくまでも基本的な型であって、これをそのまま導入すべきものではありません。 とはいえ、一定の複雑さがあるワークフローを導入する際、最初のうちは基本的な型のまま何も変えずに導入することがGitワークフローのあり方をチームとして理解する助けになるでしょう。 この説明では、デフォルトのブランチをmainブランチと記載しています。 以前は、 GitHub におけるデフォルトのブランチ名はmasterでしたが、世相を反映して現在はmainをデフォルトのブランチ名とすることが推奨されています。 GitHub、これから作成するリポジトリのデフォルトブランチ名が「main」に。「master」から「main」へ変更 シングルブランチモデル シングルブランチモデルは、開発者はサーバの リポジトリ をcloneしたらmainブランチに変更作業をコミットしていき、作業が終わり次第mainブランチをサーバにpushする方式です。 これは、 SVN からGitへ ソースコード 管理ツールを移行するチームが一時的に使う方式として検討する余地があります。 単に SVN をGitで代替し、基本的なコマンドの使い方や動作を理解する期間を設けることでツールの移行に伴う生産性の低下を緩やかにできます。 一方でシングルブランチモデルはソフトウェアの設計や、作業分担次第ではあるものの非常にコンフリクトが発生し易い使い方です。 push前に必ずrebaseしてローカル ディレクト リ内でコンフリクトを解決する。もしくは、同じファイルを同時に複数の人が修正しないよう作業範囲を調整しなければなりません。 つまり、この方式を採用してプロジェクト運営を行うならプロジェクトマネージャーが、仕様の変更や追加に伴うファイルの変更範囲を厳密に理解している必要があります。 チームが2,3人で担当範囲が明確なら、この方式でGitを使い始めるのがいいでしょう。 コンフリクトさえ発生しなければ、覚えなければならないGit固有の知識が最小限で済みます。 参考文献 Centralized Workflow トランクベース開発 GitHub Flow GitHub Flowでは、サーバの リポジトリ をcloneしたらmainブランチから作業用のフィーチャブランチへswitchします。 フィーチャブランチでの作業が終わったら、それをサーバにpushしPRを使ってmainブランチにマージします。 この方式では、gitの3-way mergeが上手く働くのでシングルブランチモデルよりも断然コンフリクトが発生し辛くなります。 原義的な GitHub Flowではmainブランチにマージされた変更は本番環境を含む何らかの実行環境にデプロイすることが重要なのですが、そこまでやらなくても十分に利便性を享受できます。 参考文献 GitHub Flow 翻訳 GitHub公式 GitLab Flow ブランチの管理とデプロイをより丁寧に対応付ける形のワークフローがGitLab Flowです。 開発者がPRを使ってmainブランチに変更をマージしていくことは GitHub Flowと同じです。 GitLab Flowでは、デプロイ作業を実施するブランチを専用に設けることで、開発におけるPRのマージとデプロイの周期やタイミングを分離します。 mainブランチにマージされたアプリケーションがテスト環境にデプロイされ、そこで問題が見つからなければ本番環境にデプロイされるというようなワークフローを想定すると、このような形になるでしょう。 大規模な開発では、テスト環境が複数用意されているものです。 テストの目的や、担当者の責任範囲によって環境が分離されているようなら環境に対応したブランチを用意した上で、それらのブランチへのマージをトリガーにデプロイを実行するでしょう。 パッケージソフト ウェアのビジネスでは、リリース済みで新機能の追加がないとしても、セキュリティの問題や深刻なバグへの対応を実施するためパッチを適用して保守していくことが望まれます。 このような状況にGitLab Flowの考え方を適用すると、それぞれのリリースに応じてブランチを作り、mainブランチから必要な変更だけを取り出してマージしていく(チェリーピック)ことになります。 このバージョンブランチを伸ばしていく保守方針は変更コストが高くなり易いので、なるべく選ばない方がいいでしょう。 しかし、これはソフトウェアの収益モデルと直接関連するので、ビジネスオーナーと開発者が丁寧な議論を実施すべき話題です。 参考文献 GitLab Flow GitLab flowから学ぶワークフローの実践 Gitflow (A successful Git branching model) Gitflowはここまで説明してきたワークフローの集大成となるものです。 開発者は、サーバの リポジトリ をcloneしたらdevlopブランチから作業用のフィーチャブランチへswitchします。 フィーチャブランチでの作業が終わったら、それをサーバにpushしPRを使ってdevelopブランチにマージします。 必要な変更作業が終わったら、リリースの担当者がdevelopブランチから新しいreleaseブランチをswitchし、アプリケーションのリリースに伴う作業を実施した成果をコミットします。ここでコミットされるものは、例えば、軽微な作業ミスの修正やバグの対応、ドキュメントの修正などです。 そういった作業が全て終了したら、releaseブランチをサーバにpushしPRを使ってmainブランチにマージしてタグを打ちます。 そうです、Gitflowにおけるmainブランチはアプリケーションの静止点を記録するためだけのブランチなのです。 ただし、緊急対応を行うホットフィックスブランチだけは例外です。 緊急対応を行う開発者は、mainブランチから作業用のホットフィックスブランチへswitchします。 作業が終わったらホットフィックスブランチをサーバにpushしPRを使ってdevelopブランチとmainブランチの両方へマージします。 その上で、mainブランチにタグを打ちアプリケーションをリリースします。 Gitflowでは非常に複雑な状況が想定されています。またそれぞれの開発者がGitに対してかなり習熟していることが前提のワークフローです。 中身を詳細に理解した上で、全ての要素が必要であると判断できるなら、リリースまでの作業を何度か実施してみてください。それでもなおプロジェクトの運営方針と合致していると確信できる場合のみGitflowを採用してください。 参考文献 A successful Git branching model 翻訳 まとめ 筆者は、Gitワークフローの設計で最も重要なのは 作りこみ過ぎない ことだと考えています。 そのために、Gitワークフローにおける検討事項を適切に把握しましょう。 みなさんのプロジェクトにおいて対応すべき課題を丁寧に把握した上でプロジェクトメンバーの生産性を最大限に発揮できるワークフローを設計してください。 具体的にどのワークフローを使えばいいか分からないなら、まずは GitHub Flowを試してみてください。 GitHub Actionsを使って簡単なビルドを行いつつ、コードレビューをするとGitを使うことの利便性を享受できます。 この記事によって、読者の皆さんがご自分のチーム内におけるGitワークフローを改善するきっかけになれば、これ以上に嬉しいことはありません。 非常に長いエントリでしたが、最後までお読みいただきありがとうございます。 執筆: @sato.taichi 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
みなさんこんにちは、 電通国際情報サービス (ISID)X イノベーション 本部ソフトウェアデザインセンターの佐藤太一です。 この記事では、Git を使った仕事のやり方(以降は Git ワークフローと記載)を設計する上での検討事項を説明します。 これによって、読者の皆さんがGitワークフローを適切に定義できるようになることを主たる目的としています。 また、筆者の能力不足によって記載しきれなかった考慮事項について、より深く Git を使いこなしている識者からの指摘を受ける機会を得ることを副次的な目的とします。 この記事には書かれていないものの、検討すべき事項について知見のある方はブログ記事を書いたり、 Twitter 等の SNS で指摘してくださるとありがたいです。 はじめに 基本的な考え方 Git ワークフロー設計における考慮事項 チームの人数 monorepoの検討 参考文献 プロジェクト管理ツールとの連携 Pull Request ブランチ名 頻出する定型業務の自動化(CI/CD サービスとの統合) ビルド 静的解析 ソースコードの自動生成 単体テスト 結合テスト ドキュメントの自動生成 パッケージング 依存ライブラリのアップデート システムテスト アプリケーション運用との統合 Git ワークフローの実際 シングルブランチモデル 参考文献 GitHub Flow 参考文献 GitLab Flow 参考文献 Gitflow (A successful Git branching model) 参考文献 まとめ はじめに この記事は、Git や GitHub の基本的な使い方を読者が理解していることを前提に書いています。 つまり、Git サーバとしての GitHub だけでなく、その機能を十分に使うことを想定しています。 もし基本的な使い方に不安があるなら、まずは以下のサイトや書籍で学習してください。 Git 入門 30分程度でgitについて把握できる Microsoft の教育コンテンツです。 サル先生の Git 入門 Git の初歩の初歩を図解しながら学習できるサイトです 改訂 2 版 わかばちゃんと学ぶ Git 使い方入門 漫画で Git を学習するならこの本がおすすめです 新しい領域として Git を学ぶならとっつき易さで選ぶのがいいと個人的には考えています いちばんやさしいGit&GitHubの教本 第3版 人気講師が教えるバージョン管理&共有入門 Git と GitHub をまとめて学習するならこの本がおすすめです 基本的な考え方 Gitのワークフローを決めるのは仕事の質を上げるためです。失敗の可能性を低減し、また失敗したとしても労せずやり直せることで効率よく働けるようにしましょう。 Gitのワークフローを検討する際、常に意識してほしいのは 作りこみ過ぎない ということです。 まず、起きてもいない問題のすべてを事前に対応しようとしないでください。また、プロジェクトメンバーがワークフローの習熟に使う時間をできるかぎり減らしましょう。 プロジェクトメンバーが複雑すぎるワークフローに振り回されると仕事の生産性は確実に低減し、その結果として仕事の質が落ちていきます。 Gitのワークフローは自分のプロジェクトにおいて問題が起きてもやり直せるギリギリまで軽量化しましょう。 Git ワークフロー設計における考慮事項 この記事の主題となるGitワークフロー設計における考慮事項を説明します。 ここでは、Gitを中心にした仕事のやり方全体を指してGit ワークフローと呼んでいます。 少し抽象的な話が続きますので、Gitワークフローについて考えたことがないなら、記事の後半にある Git ワークフローの実際を読んでから戻ってくると少しわかり易くなるかもしれません。 チームの人数 仕事のやり方を考えるのですから、まずチームがどのようなメンバーで構成されているのか検討しましょう。 例えば、2,3人のチームでは労せずお互いのやっていることを正確に把握できるので、複雑なワークフローは必要ありません。ミスはするものとしてお互い補いあう想定で働くと効率がいいです。 こういった少人数であれば、最低限の ケアレスミス を防ぐためだけにワークフローを定義しましょう。 メンバーが10人を超えたあたりで、お互いの仕事内容を正確に把握しあうのは難しくなります。 これくらいの人数から、Gitワークフローを検討する上でメンバーの入れ替わり頻度について考慮しましょう。 数年間固定されていて気心の知れたメンバー10人と、この3か月で集まった10人では採用すべきワークフローに多少の違いはでるでしょう。 メンバーが固定されているチームでは少人数のチームと同じようなGitワークフローで十分です。変に手順を難しくするのはやめましょう。 メンバーが流動的なチームでは作業記録を残しやすい形でワークフローを定義すると、問題が発生した際に対処し易くなります。 メンバーが30人を超えると大抵のプロジェクトでお互いの仕事内容を正確に把握しあうのは不可能です。 チームを2つか3つに分割した上で役割と責任範囲を明確にした作業分担が必要になるでしょう。 メンバーは流動的で少なくとも四半期に一回程度は誰かが抜けて、誰かが増えます。 つまり、ワークフローに習熟していないメンバーがいつでも一定数存在することを前提にワークフロー操作が失敗しても他のチームにその影響が派生しないことを重視しましょう。 こういう状況ではチーム同士の成果を結合するワークフローはどうしても複雑なものになりがちです。 monorepoの検討 Gitでは小さい単位の開発成果物ごとに リポジトリ を作る方が大抵の場合うまくいきます。 例えば、サーバアプリケーションとクライアントアプリケーション、デプロイ スクリプト 、マニュアル類、設計ドキュメントなどをそれぞれを別な リポジトリ として管理するとよいでしょう。 成果物の種類毎に リポジトリ が分かれていれば、それらを更新するメンバーの役割と責任範囲が自然と明確で分かり易くなります。 例えば、クライアントアプリケーションの開発メンバーには、サーバアプリケーションの リポジトリ に対して読み取り専用の権限を付与することを考えてみましょう。まず、コードの読むことはできるので問題発生時に分析はできます。 一方で、クライアントアプリケーションの修正のつもりでサーバアプリケーションに修正を加えてしまうといったミスは発生しなくなります。 一方で、 リポジトリ が分かれていると問題になるケースもあります。 考えてみてください。.protoや JSON Schemaのように、サーバアプリケーションとクライアントアプリケーションが通信するための プロトコル を定義するIDLは、どちらの リポジトリ に置くのがいいでしょうか?それとも、 通信プロトコル の定義は設計ドキュメントとセットで新しい リポジトリ を作るべきでしょうか?また、クライアントアプリケーションとしてモバイルアプリケーションを実装することにしました、 Android と iOS では リポジトリ を分けるべきでしょうか? このように何かあるたびに リポジトリ を増やしていくと、 リポジトリ の増加は止まりません。 リポジトリ が増えると手元の開発環境を適切な状態に維持するための手順は煩雑になります。また、CIやCDなどのビルドプロセスも複雑になります。 これに対して、開発成果物を全て単一の リポジトリ 内に収めてしまうのがmonorepoです。 単一の リポジトリ 内にあらゆる成果物を入れて管理すれば、メンバーの新規追加時における難しさは大きく低減します。 monorepoを採用することによって、まず新しいメンバーが既存の成果物を探し回るために必要なコストが低減します。 そして、ドキュメントや ソースコード の間で 相対パス を使ったリンクを張れます。種類の成果物同士で情報の連携が取り易いことは、仕様に対する理解の速度を早めます。 Google のように依存ライブラリも含めて全てmonorepoに入れてしまうような組織もあります。 対応を検討すべきデメリットはいくつかあります。 ファイル数やコミット数が多くなるので、単純に リポジトリ が大きくなる GitHub の リポジトリ は5GB程度に収めるよう推奨されている Repository size limits cloneやfetchに時間がかかり易くなる 対応策は、 パーシャルクローンとシャロークローンを活用しよう タグの対象範囲が分かりづらくなり易い タグの 命名規則 をしっかり考える必要がある メンバーが編集できる成果物の範囲を限定するのが難しい GitHub にはCODEOWNERSという仕組みで ディレクト リ単位やファイル単位の必須レビュアーを設定できる コードオーナーについて 参考文献 Git で monorepo を扱う際の課題とヒント Why Google Stores Billions of Lines of Code in a Single Repository monorepo.tools プロジェクト管理ツールとの連携 ソフトウェア開発をするためにGitを使うのですから、プロジェクト管理ツールとGitワークフローは密接に関連します。 GitHub にはタスク管理機能が備わっていますが、ある一定以上の規模で利用しようとするには機能不足です。 ボトムアップ な管理をするにも、例えばタスクの分割と集約の機能がなかったり、チケットの検索クエリが貧弱だったりします。 トップダウン な管理という意味では、例えばタスクの進捗状況を俯瞰するのは非常に難しいですし、承認フローを構築したり WBS を引いたりといったことも出来ません。 ステークホルダー となる組織が複数あり、プロジェクトとの関係性において一定の複雑さがあるならプロジェクト管理ツールを導入すべきです。 理想的には、ソフトウェア開発系のタスクとそれ以外のタスクにおける情報連携を自動的に実施するのが望ましいでしょう。つまり、プロジェクト管理ツールに定義したワークフローとGitを使ったワークフローを上手く連携させるのが望ましいというわけです。 基本的にはプロジェクト管理ツール側に GitHub 上のPull Requestやブランチの情報を取込んでいく形になります。 例えば、広く使われているプロジェクト管理ツールであるJiraには スマートコミット という仕組みがあり、コミットメッセージを所定の書式で記述するとJiraのIssueが持つ状態を操作できます。 プロジェクト管理ツール側がWebhookに対応しているならPull Requestから情報を送信できます。 また、 GitHub Actions を使うならPull Requestでなくても情報をプロジェクト管理ツールに送信できます。 Pull Request Gitはそれぞれの開発者が持っている リポジトリ は完全に独立しており対等なものです。 SVN や CVS といった構成管理ツールを使う場合、開発者のローカルにチェックアウトされた成果物は作業用のコピーであることとは対照的です。 一方で、内部統制がしっかりと効いた開発を行うなら、プロジェクトの進捗と厳密に同期していると言える リポジトリ を一つ決めます。 その中心となる リポジトリ を GitHub 上に置くと使える仕組みがPull Request(PR)です。他のツールではMerge Requestと呼ばれることもあります。 PRを使うと、コードレビューとそれにひもづく定型作業の自動化(CI)をサーバ上で実行できます。 GitHub を使うプロジェクトでは、Gitワークフローを設計するならPRをどうやって使うかが議論の中心になるでしょう。 もしPRを使うのであれば、Issue Templateを用意するとPR自体の質を底上げできます。 About issue and pull request templates ブランチ名 Gitを利用する際にブランチ名を工夫すると、CIで作業を自動化し易くなります。 ワークフローの設計次第ではもう少しルールを追加しますが、基本的にはこれだけです。 作業内容を想像し易い単語をブランチ名に含める ブランチ名は基本的に半角英数だけを使う 意味の区切りは / を使う 区切り文字として / を使うのは、GitKrakenを始めとした GUI クライアントが / 区切りでブランチを階層表示してくれるからです。 この画像では dev/my-task 、 dev/your-task に加えて折りたたまれた test/awesome ブランチがあります。 頻出する定型業務の自動化(CI/CD サービスとの統合) Gitのワークフローを作りこむ理由は、定型業務を自動化してより効率よく働くためです。 どんな作業をCIの中に組込みたいのかをしっかりと検討することはワークフロー設計をする上で大切です。 ここでは、参考のためにCIへ組み込むと便利な定型業務を列挙しますが、最初から全てに取り組もうとしないでください。 ご自分のプロジェクトにおいて明らかに恩恵があると思われるものを少量取り入れるのがおすすめです。 PRに伴って動作するCIワークフローのパフォーマンスが悪いと開発効率は確実に低減しますので、あれもこれもと盛り込み過ぎないようにしましょう。 また、筆者が知らなかったり、列挙できなかった定型業務は非常に多岐にわたることが予想されます。 読者の皆さんがご自分のプロジェクトで定型化している業務を SNS やブログで共有して下さることを期待しています。 ビルド CIに組み込む作業として一番最初に思い浮かぶのがビルドです。 ここでいうビルドは、 ソースコード から実行可能な状態に変換する過程で発生する定型的な業務全般を指しています。 静的解析 コンパイル 型の プログラミング言語 なら コンパイラ がある程度の静的解析に基づくエラーを出力してくれます。 大抵の プログラミング言語 には、Lintと呼ばれるタイプの ソースコード の妥当性を検証するツールがあります。 特に筆者が好きなのは、ベストプ ラク ティスに基づいたコードへ矯正するタイプの静的解析ツールです。 使うことが明確に非推奨というわけではないけども、新しいコードではそのような書き方をしない方がよいという知識を複数の プログラミング言語 にわたって学習し続けるのは非常に難しいです。 例えば、ReactのuseEffectに [] を指定すべきケースは限定的で基本的に何か値を設定したほうがいいとか。 goは1.13でerrorの望ましい書き方が変わったとか。 アプリケーションとしては動作する以上、望ましくないスタイルのコードを書いていても気が付きませんよね。しかし、望ましくないスタイルのコードはソフトウェアの品質を確実に低減させます。 こういったことを学習するきっかけをえるためにLintツールはPRからトリガーされるタスクに組み込むのが望ましいでしょう。 ソースコード の自動生成 ソースコード の自動生成は、それを実施すべきかどうかを判断するのが難しいタスクです。 ジェネリック スやテンプレートなど言語の機能を利用することで、継続的な ソースコード の自動生成は不要にできる場合もあります。 また、自動生成した ソースコード をGitのような構成管理ツールにチェックインするかどうかも注意深く検討してください。 自動生成した ソースコード をチェックインする場合には、 リポジトリ のサイズが大きくなり易い代わりに、CIの実行時間を抑えられます。 一方で、自動生成した ソースコード をチェックインしない場合には、 リポジトリ のサイズを小さく保てる代わりにCIの実行時間が長くなります。 筆者としては、 ソースコード の自動生成は開発者のローカルマシンで実行しPRを出して リポジトリ にチェックインする方式を好んで使っています。それは、基本的には望ましくないことだと分かっていても、生成物に対して手を入れたいという要求を完全に消すことはできないからです。 単体テスト データベースや アプリケーションサーバ を使わない 単体テスト は、PRからトリガーされるタスクとして実行したいタスクです。開発者が実施するテストのあり方を学びたい人には 和田さんのスライド をおすすめしておきます。 QuickCheckのようなパラメタライズドテストや、コード カバレッジ の計測は実行コストが高いのでそれほど頻繁に行わなくてもいいかもしれません。そういう高コストなテストは、Gitワークフローからは切り離して夜中や早朝に定期実行するようなアプローチをおすすめします。 結合テスト ここではデータベースや アプリケーションサーバ 、リバースプロキシなどを構成した上で実施するテストを 結合テスト とします。 テストに時間がかかりすぎるとか、テストが終わらなくて タイムアウト するとか言った問題はスローテスト問題と呼ばれています。 長期間にわたって価値を生み出すソフトウェアを実装していくにあたって、スローテスト問題は避けられません。 スローテスト問題に対する基本的なアプローチは、品質を担保できるギリギリの頻度で実行し、テストの実行コストと品質の トレードオフ における妥当な割合を見つけることです。 機械学習 によってテストの実行対象を賢く選択してくれる Launchable は、この問題に対する画期的な解決策を提供してくれるかもしれないと期待しています。 筆者の個人的な好みは、 ナイトリー ビルドのような形で一日に数回は 結合テスト を実行するものの、日常的なPRトリガーのワークフローからは外すことです。 ドキュメントの自動生成 Javadoc のようなドキュメントコメントから開発者向けドキュメントを自動生成するタスクもまたビルドタスクの一部としてよく組込みます。 フレームワーク やライブラリのように繰り返し再利用されるものを実装する際にはドキュメントコメントをしっかり書くのが望ましいでしょう。一方で、アプリケーションコードのドキュメントコメントは必要最小限に留めておくのがおすすめです。 筆者の経験上、コメント部分は動作しないためかコードと整合性をもってメンテナンスされづらく、その結果コメントとコードの内容にズレがおきることでコードを理解する妨げになる事の方が多いからです。 ドキュメントの自動生成は多くても一日に一回程度、大抵の場合は一週間に一回くらいの頻度で実施すれば事足りるので、PRから実行されるCIタスクには組み込まないことが多いです。 代わりに、クーロンジョブのような形で定期的に実行してPRを自動生成する方が低コストです。 今の GitHub Pagesにはアクセス制御機能があり、許可された開発者だけが閲覧できるサイトを簡単に構築できることは知っておいてください。 Changing the visibility of your GitHub Pages site パッケージング パッケージングは、Dockerコンテナイメージや、jarのような形式で アーカイブ されたファイルを作ることです。 単に アーカイブ すればいいものから署名が必要なものまで、言語やデプロイ先によって実行すべきタスクは様々です。 ただ、大きな傾向としてパッケージングはリリース作業の一部として実行するタスクです。 パッケージングとして実施すべきタスクの内容は極めて複雑で多岐にわたるため、今回はそういうものがあるとだけ言っておきます。 依存ライブラリのアップデート 現代的なソフトウェア開発において オープンソース ソフトウェア( OSS )の利用は避けられません。 そして、 OSS のライブラリは高頻度にアップデートされます。特に緊急性の高いセキュリティパッチと呼ばれるようなアップデートは実施しなければなりません。 とはいえ、定期的な依存ライブラリのアップデートを手動でやっていると時間がどれだけあっても不足します。つまり、自動化するしかありません。そのためには、比較的高品質な 回帰テスト が必要になります。 依存ライブラリのアップデートを自動的にやってくれるサービスとしては、 Renovate や、 GitHub に標準で組み込まれている Dependabot を使うのが良いでしょう。これらのサービスを利用するとライブラリをアップデートするPRを自動的に作ってくれます。 自動的に作られるPRをGitワークフローの中でどういう位置づけにするのかは、丁寧に検討してください。深く考えずに導入すると、無限に生成されるPRに押しつぶされてしまいますよ。 これは、筆者がメンテナンスしている社内ツールの 回帰テスト が不安定なせいで自動マージが上手く動作せず蓄積してしまったPRの山です。 システムテスト Gitのワークフローを検討する上で、 システムテスト の位置づけは整理するのが望ましいでしょう。 まずは、ワークフローの一部に システムテスト を組み込むのかどうかを検討してください。 システムテスト の自動化は極めて深遠な話題ですので、大抵のプロジェクトでは組み込まないという結論になるでしょう。 ソフトウェアが価値を生み長生きするならば、規模は大きくなり複雑になっていきます。 その中で システムテスト の自動化は取り組むべき課題の一つです。 Gitワークフローの一部として システムテスト を実行すべき状況になっていないかを、ときどき気にかけてください。 アプリケーション運用との統合 Gitワークフローとアプリケーション運用の統合については、パッケージングの話題でありデプロイの話題です。 少なくとも、ワークフローの終了ステータスをどのような形で、いつ誰に通知するのかは検討してください。 伝統的にはショートメッセージサービス(SMS)や電子メールを使っていますよね。 現代的にはSlackやTeams、LINEなどチャットツールへのメッセージ送信となるでしょう。 また、アプリケーション運用側からGitに対する情報提供をどのように実施するのかを検討するのも忘れないでください。 基本的にはプロジェクト管理ツールに対するタスクの起票( GitHub Issuesなど)を介して行うことになるはずです。 Git ワークフローの実際 ここからは、ワークフロー設計の参考になるようなGitワークフローをいくつか紹介します。 ただし、具体的な作業手順については説明していません。参考文献に詳細な説明のあるページをリンクしておきましたので参考にしてください。 ここで紹介しているものは、あくまでも基本的な型であって、これをそのまま導入すべきものではありません。 とはいえ、一定の複雑さがあるワークフローを導入する際、最初のうちは基本的な型のまま何も変えずに導入することがGitワークフローのあり方をチームとして理解する助けになるでしょう。 この説明では、デフォルトのブランチをmainブランチと記載しています。 以前は、 GitHub におけるデフォルトのブランチ名はmasterでしたが、世相を反映して現在はmainをデフォルトのブランチ名とすることが推奨されています。 GitHub、これから作成するリポジトリのデフォルトブランチ名が「main」に。「master」から「main」へ変更 シングルブランチモデル シングルブランチモデルは、開発者はサーバの リポジトリ をcloneしたらmainブランチに変更作業をコミットしていき、作業が終わり次第mainブランチをサーバにpushする方式です。 これは、 SVN からGitへ ソースコード 管理ツールを移行するチームが一時的に使う方式として検討する余地があります。 単に SVN をGitで代替し、基本的なコマンドの使い方や動作を理解する期間を設けることでツールの移行に伴う生産性の低下を緩やかにできます。 一方でシングルブランチモデルはソフトウェアの設計や、作業分担次第ではあるものの非常にコンフリクトが発生し易い使い方です。 push前に必ずrebaseしてローカル ディレクト リ内でコンフリクトを解決する。もしくは、同じファイルを同時に複数の人が修正しないよう作業範囲を調整しなければなりません。 つまり、この方式を採用してプロジェクト運営を行うならプロジェクトマネージャーが、仕様の変更や追加に伴うファイルの変更範囲を厳密に理解している必要があります。 チームが2,3人で担当範囲が明確なら、この方式でGitを使い始めるのがいいでしょう。 コンフリクトさえ発生しなければ、覚えなければならないGit固有の知識が最小限で済みます。 参考文献 Centralized Workflow トランクベース開発 GitHub Flow GitHub Flowでは、サーバの リポジトリ をcloneしたらmainブランチから作業用のフィーチャブランチへswitchします。 フィーチャブランチでの作業が終わったら、それをサーバにpushしPRを使ってmainブランチにマージします。 この方式では、gitの3-way mergeが上手く働くのでシングルブランチモデルよりも断然コンフリクトが発生し辛くなります。 原義的な GitHub Flowではmainブランチにマージされた変更は本番環境を含む何らかの実行環境にデプロイすることが重要なのですが、そこまでやらなくても十分に利便性を享受できます。 参考文献 GitHub Flow 翻訳 GitHub公式 GitLab Flow ブランチの管理とデプロイをより丁寧に対応付ける形のワークフローがGitLab Flowです。 開発者がPRを使ってmainブランチに変更をマージしていくことは GitHub Flowと同じです。 GitLab Flowでは、デプロイ作業を実施するブランチを専用に設けることで、開発におけるPRのマージとデプロイの周期やタイミングを分離します。 mainブランチにマージされたアプリケーションがテスト環境にデプロイされ、そこで問題が見つからなければ本番環境にデプロイされるというようなワークフローを想定すると、このような形になるでしょう。 大規模な開発では、テスト環境が複数用意されているものです。 テストの目的や、担当者の責任範囲によって環境が分離されているようなら環境に対応したブランチを用意した上で、それらのブランチへのマージをトリガーにデプロイを実行するでしょう。 パッケージソフト ウェアのビジネスでは、リリース済みで新機能の追加がないとしても、セキュリティの問題や深刻なバグへの対応を実施するためパッチを適用して保守していくことが望まれます。 このような状況にGitLab Flowの考え方を適用すると、それぞれのリリースに応じてブランチを作り、mainブランチから必要な変更だけを取り出してマージしていく(チェリーピック)ことになります。 このバージョンブランチを伸ばしていく保守方針は変更コストが高くなり易いので、なるべく選ばない方がいいでしょう。 しかし、これはソフトウェアの収益モデルと直接関連するので、ビジネスオーナーと開発者が丁寧な議論を実施すべき話題です。 参考文献 GitLab Flow GitLab flowから学ぶワークフローの実践 Gitflow (A successful Git branching model) Gitflowはここまで説明してきたワークフローの集大成となるものです。 開発者は、サーバの リポジトリ をcloneしたらdevlopブランチから作業用のフィーチャブランチへswitchします。 フィーチャブランチでの作業が終わったら、それをサーバにpushしPRを使ってdevelopブランチにマージします。 必要な変更作業が終わったら、リリースの担当者がdevelopブランチから新しいreleaseブランチをswitchし、アプリケーションのリリースに伴う作業を実施した成果をコミットします。ここでコミットされるものは、例えば、軽微な作業ミスの修正やバグの対応、ドキュメントの修正などです。 そういった作業が全て終了したら、releaseブランチをサーバにpushしPRを使ってmainブランチにマージしてタグを打ちます。 そうです、Gitflowにおけるmainブランチはアプリケーションの静止点を記録するためだけのブランチなのです。 ただし、緊急対応を行うホットフィックスブランチだけは例外です。 緊急対応を行う開発者は、mainブランチから作業用のホットフィックスブランチへswitchします。 作業が終わったらホットフィックスブランチをサーバにpushしPRを使ってdevelopブランチとmainブランチの両方へマージします。 その上で、mainブランチにタグを打ちアプリケーションをリリースします。 Gitflowでは非常に複雑な状況が想定されています。またそれぞれの開発者がGitに対してかなり習熟していることが前提のワークフローです。 中身を詳細に理解した上で、全ての要素が必要であると判断できるなら、リリースまでの作業を何度か実施してみてください。それでもなおプロジェクトの運営方針と合致していると確信できる場合のみGitflowを採用してください。 参考文献 A successful Git branching model 翻訳 まとめ 筆者は、Gitワークフローの設計で最も重要なのは 作りこみ過ぎない ことだと考えています。 そのために、Gitワークフローにおける検討事項を適切に把握しましょう。 みなさんのプロジェクトにおいて対応すべき課題を丁寧に把握した上でプロジェクトメンバーの生産性を最大限に発揮できるワークフローを設計してください。 具体的にどのワークフローを使えばいいか分からないなら、まずは GitHub Flowを試してみてください。 GitHub Actionsを使って簡単なビルドを行いつつ、コードレビューをするとGitを使うことの利便性を享受できます。 この記事によって、読者の皆さんがご自分のチーム内におけるGitワークフローを改善するきっかけになれば、これ以上に嬉しいことはありません。 非常に長いエントリでしたが、最後までお読みいただきありがとうございます。 執筆: @sato.taichi 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
こんにちは! 電通国際情報サービス 金融ソリューション事業部の山下です。 今回は、 ゲームエンジン の Unreal Engine と、 iOS アプリの LiveLinkFace を用いたデジタルヒューマンのリアルタイムフェイスリグを行う手順を紹介します。 手順 MetaHuman Creator でデジタルヒューマンを作成する デジタルヒューマンを Unreal Projectにエクスポートする LiveLinkFaceアプリでデジタルヒューマンを動かす 実行環境 PC: macOS 12.1 Monterey Unreal Engine 4.27(β版のUE5でも動作可能ですが、今回は UE4 での手順を説明します) Quixel Bridge 2021.0.1 Mobile: iPhone X (ARKitとdepthAPIがサポートされているFaceID搭載機種が必要になります) iOS 15.2.1 LiveLinkFace 1.1.2 Network: PCとMobileが同じネットワークに接続していること(同一 Wifi 環境 or Lightning Ethernet など) 手順1. デジタルヒューマンの作成 まず、 MetaHuman Creator でデジタルヒューマンを作成します。 MetaHuman Creator は、 Epic Games が提供する高精細なデジタルヒューマンを誰でも簡単に作成できる クラウド サービスです。 プリセットされたデジタルヒューマンをベースにした顔や髪型などの モデリング が可能ですが、本記事における詳細な操作説明は割愛します。 今回は、以下のデジタルヒューマンを作成しました。 手順2. デジタルヒューマンを Unreal Projectにエクスポートする Unreal Engine にて、Blankプロジェクトを作成してプロジェクトを立ち上げます。 以下の プラグイン をインストールしましょう。 - 「Edit」->「Plugins」 - Apple ARKit - Apple ARKit Face Support - Live Link 次に、 Quixel Bridge アプリを立ち上げます。(UE5の場合はQuixel Bridgeが Unreal Engine 自体に組み込まれていますが、 UE4 では別途インストールが必要となります) MetaHuman Creator で利用したものと同じEpicGamesアカウントを用いてログインすると、先ほど作成したデジタルヒューマンを確認できます。 エクスポートを行う為に、以下の設定を行います。 - 「Settings」->「Download Settings」->「Modelsタブ」 - 項目「MetaHumans」 :「UAsset + Source Asset」 - 「Download」実行(完了まで数十分かかります) - Settings」->「Export Settings」->「Export Target」 - 項目「ExportTarget」 :「 Unreal Engine 」 - 項目「Engine Version」:自身の Unreal Engine バージョン - 項目「Plugin Location」:自身の Unreal Engine Plugin ディレクト リパス - 「Export」実行 - 初めて実行する場合は Unreal Engine 側で不足している プラグイン のWarningが表示されますが、全て有効(Enable Missing)を選択して必要 プラグイン をインストールして下さい Exportが完了すると、 Unreal Engine プロジェクトの「Content Browser」パネルにデジタルヒューマンのアセットが追加されます。 Unreal Engine プロジェクトに、先ほど追加されたアセットを ドラッグアンドドロップ で配置します。 Unreal Projectへデジタルヒューマンのエクスポートができました。 手順3. LiveLinkFaceアプリでデジタルヒューマンを動かす まず、Mobile側でLiveLinkFace iOS アプリを立ち上げ、以下の設定を行います。 - 「Setting」->「Live Link」-> 「TARGETS」 -> 「Add Target」 - 「IP Address」:接続先PCのプライベート IPアドレス を設定 頭の回転も連携させる為に、「Setting」->「Stream Head Rotation」をONにします。 Mobile側の設定は以上です。 次に、PC側の Unreal Engine 側で、Mobileデ バイス がSource Subjectとして追加されていることを確認します。 - 「Window」->「Live Link」 最後に、World Outlinerパネルからデジタルヒューマンのアセットを選択して、以下を設定します。 - 「LLink Face Subj」:自身が追加したSource Subjectを選択 - 「LLink Face Head」:ON 完成 Unreal Engine のプレイボタンを押すと、リアルタイムフェイスリグが確認できます! いかがでしたでしょうか。 EpicGames社が提供するUnrealEngine、MetaHuman、LiveLinkFaceを用いて、ノーコードでデジタルヒューマンのフェイスリグを行いました。 実はこのMetaHuman、既に映画やゲームなどの開発にも利用されており、先日公開されたPS5/ Xbox 向けデモ The Matrix Awakens: An Unreal Engine 5 Experience では、実際にハリウッド映画並のクオリティでリアルタイムに動くゲームが公開されており、その作品の主人公もMetaHumanで作成されています。 メタバース に対する注目が集まっている中、基盤となる ゲームエンジン や周辺システムも個人レベルで簡単にアクセスできるようになってきており、ますますこの領域に目が離せません。 現在ISID金融ソリューション事業部では ブロックチェーン や NFT関連の研究開発 を行っております。 既に Decentraland や Sandbox などの VR サービス内ではNFTアイテムが取引可能になっており、将来的なオープンな メタバース と ブロックチェーン の親和性は高いと考えられます。引き続き3DCG領域の技術もウォッチしていきたいと思います! 参考 Getting Started with MetaHumans in Unreal Engine Using the MetaHuman Facial Rig Recording Facial Animation from an iOS Device Introducing The Matrix Awakens: An Unreal Engine 5 Experience 執筆: @yamashita.yuki 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター