更新)

Packer でインスタンスのイメージの更新を自動化したい

カバー

[!] この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

当社の基幹業務システムは、2021 年にオンプレミスサーバーから Oracle Cloud Infrastructure (以下 OCI と表記) へと移行し、運用を始めて 2 年目になりました。当社の基幹業務システムは OCI 上の Kubernetes のサービスを利用し、業務プログラムの展開は CI/CD 化されて、自動化は進んできています。しかし、その展開先となるノードや、システムに関連するツール用のインスタンスはほぼ手動で構築、試験をしています。これらインスタンスの構築作業が定型化してきたため、構成管理や構築、試験の自動化を見据え、その第一歩としてインスタンスのベースイメージにあたる、カスタムイメージの最新化から着手しようと考えました。カスタムイメージの最新化にあたり、Packer というツールを利用し、作成したカスタムイメージを Terraform でインスタンスに展開し、Goss を利用して試験まで実施できたため、今回はその内容を紹介します。

なお、この記事は OCI のインスタンスの構築手順をある程度理解されている方向けに紹介します。OCI やインスタンス等の仕組みに関しては OCI の公式サイト をご参照ください。

利用したツールの紹介

Packer とは

PackerHashiCorp 社 が開発する OSS で、クラウドのインスタンスイメージや VM のイメージ等、サーバーやマシンのイメージを作成するツールです。Packer は設定が HCL 形式 (または JSON 形式) で記載され、コード管理することができます。また、インフラ構築部分にあたるインスタンスやイメージの出力部分と、イメージを構築するためのコマンドの実行部分が分離されているため、他のインフラ環境で同じイメージを作る場合に利用がしやすい作りになっています。イメージの展開先環境も、クラウドや VM 環境、Docker イメージ等、プラグイン として用意され、幅広い環境に適用できるのも利点となっています。

Terraform とは

Terraform は Packer と同じく HashiCorp 社が開発する OSS で、インフラの構成とプロビジョニングをコード管理できるようにした IaC (Infrastructure as Code) ツールです。こちらも Packer 同様に さまざまな環境 に対応しているため、複数のクラウド環境を組み合わせて利用するマルチクラウド環境の運用や、同じような構成の環境を複数作る場合に適したツールになっています。

Goss とは

Goss は YAML ファイルの記述でサーバーの設定を検証できる OSS の試験ツールです。Goss は軽量な実行ファイルと設定ファイルの配置のみでインストールでき、操作も簡単なため、学習コストも少なく、素早く実行できる点が特徴になります。検証できる内容としては、パッケージのインストール状況やバージョンの確認、サービスやプロセスの状態、ポートの開放状態、ネットワークの状態、コマンドの実行結果など、基本的な確認事項はチェックできるツールとなっています。

やってみる

要件定義

今回は Oracle Linux 8 に以下の設定を追加したインスタンス用のカスタムイメージを作成する手順とします (実際の環境は他にもいろいろと設定していますが、ここでは省略します)。

  • 当社の環境ではプロキシーの設定が必要なため、プロキシーの設定を実施
  • 各インスタンスで git コマンドを利用するため、最新の git コマンドのインストールを実施

環境

実行環境は以下のとおりです。

  • Windows 10 の WSL (Ubuntu 20.04) 環境で実施
  • カスタムイメージおよび試験用のインスタンスの展開先は OCI の東京リージョン (ap-tokyo-1)

作成するファイルは以下になります。

/home/<User>/
 ├ .oci/                      : OCI の認証設定
 │ ├ config                   : OCI の設定ファイル
 │ ├ oci_api_key.pem          : OCI の API アクセスキー
 │ └ oci_api_key_public.pem   : OCI ユーザープロファイルに登録する API キー (登録後は不要)
 ├ packer/                    : カスタムイメージの作成用 Packer 設定
 │ ├ file / test.txt          : 例としてカスタムイメージに組み込むテスト表示用テキストファイル (内容は任意)
 │ ├ oci.pkr.hcl              : カスタムイメージ作成用設定ファイル 
 │ └ result.json              : Packer の結果出力
 ├ terraform/                 : 試験用インスタンスの展開用 terraform 設定
 │ ├ key/                     : 試験用インスタンスアクセス用の SSH 鍵ファイル配置先
 │ │ ├ id_rsa                 : インスタンスアクセス時に使う秘密鍵
 │ │ └ id_rsa.pub             : インスタンスに配置する公開鍵
 │ ├ goss / goss.yaml         : 試験用ファイル
 │ ├ compute.tf               : 試験用インスタンスの設定情報
 │ ├ provider.tf              : 試験用インスタンスの認証情報
 │ └ terraform.tfvars         : 試験用インスタンスの変数 (カスタムイメージの OCID)
 ├ image-maker.sh             : イメージ作成~試験までをまとめて実行するスクリプトファイル
 ├ image-maker.log            : スクリプトファイルのログ出力先
 └ new_image.txt              : 最新ベースイメージの名前の保管先

今回実施した内容

今回実施した内容は以下のとおりです。

① OCI 設定

OCI に接続するためのアカウント設定を先に実施します。OCI を操作できるユーザーを作成し、oci コマンドと紐づけておきます。

## 以降、行頭の ## はコメント行、$ はコマンド、コマンドの改行と空行以外はコマンドの出力となる 
## また、コマンドの修正が必要な箇所は <変数名> で表示している

## WSL 環境に OCI CLI をインストールする
## Ubuntu 20.04 環境は以下スクリプトでインストールとなる
$ bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)"

## 確認する
$ oci --version
3.10.1

## アカウントと紐づける
$ oci setup config

## 途中で API キーの作成を聞かれるため、Y を選択する
## Do you want to generate a new API Signing RSA key pair?
## (If you decline you will be asked to supply the path to an existing key.) [Y/n]: Y

## 確認する (デフォルトで作成すると ~/.oci に保管される)
## ここで作成されたファイルは後の手順で必要となる
$ ls ~/.oci
config  oci_api_key.pem  oci_api_key_public.pem

## 出力された公開鍵ファイル (oci_api_key_public.pem) を OCI のコンソールから登録する
## ユーザーのプロファイル → API キー → API キーの追加 → 公開キーの貼り付け、または、公開キーファイルの選択

② 最新のイメージ確認

Oracle Linux 8 系の最新のベースイメージがリリースされていないか確認します。通常の AMD ベースプロセッサのインスタンス用イメージは Oracle-Linux-x.y-YYYY.MM.DD-z となります (aarch64、Gen2-GPU とついてるものは、それぞれ Arm ベースプロセッサ用、GPU シェイプ用となります)。

コマンドからは以下で取得ができます。

## カスタムイメージの一覧を取得する
## 以下コマンドは Oracle Linux 8 系のベースイメージ一覧を取得する手順となる
## <Compartment OCID>: カスタムイメージを作成するコンパートメントの OCID を入力する
## jq で Oracle Linux 8 系のインスタンス用に該当するイメージのみに絞っている
$ oci compute image list --all --compartment-id <Compartment OCID> \
  --operating-system "Oracle Linux" \
  --operating-system-version "8" \
  | jq -r '[.data[] | select (."display-name" | test("Oracle-Linux-8\\.\\d+-\\d+\\.\\d+\\.\\d+-\\d+"))][0] | ."display-name"'

Oracle-Linux-8.6-2022.05.31-0

③ Packer でカスタムイメージ作成

まず、Packer をインストールします。インストールの手順は 公式のインストール手順 を参照してください。

## Packer のインストール
$ curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
$ sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
$ sudo apt-get update && sudo apt-get install packer

## 確認する
$ packer --version
1.8.1

次に Packer の設定ファイルを作成します。以下のドキュメントに従い、パラメーターを設定していきます。なお、設定ファイルは .hcl ファイルで、HCL 形式 (HCL の version 2 で HCL2) という HashiCorp 社の独自の形式になります。

## Packer 用のディレクトリーを作成する
$ cd ~/
$ mkdir ./packer/
$ cd ./packer/

## Packer のファイルを作成する
## 以下 <> で括られた変数は適宜、環境に合わせて変更
$ vim ./oci.pkr.hcl

## ---- oci.pkr.hcl ---- ##
## イメージを作成する環境の設定
## https://www.packer.io/docs/templates/hcl_templates/blocks/source
source "oracle-oci" "instance-image" {
  ## イメージ作成用インスタンスの設定
  ## 展開先コンパートメントの OCID (必須)
  compartment_ocid = "<Compartment OCID>"
  ## 展開先 Availability Domain (必須): ap-tokyo-1 の場合
  availability_domain = "xxxx:AP-TOKYO-1-AD-1"
  ## 展開先サブネット OCID (必須)
  subnet_ocid = "<Subnet OCID>"
  ## インスタンス名 (未指定の場合 instanceYYYYMMDDhhmmss になる)
  instance_name = "instance-for-custom-image-from-packer"
  ## インスタンスのシェイプ (必須)
  shape = "VM.Standard.E4.Flex"
  ## インスタンスシェイプのサイズ
  shape_config {
    ## OCPU のサイズ
    ocpus = "1"
    ## メモリーサイズ
    memory_in_gbs = "4"
  }
  ## インスタンスのディスクサイズ
  disk_size = 50
  ## インスタンスの接続ユーザー
  ssh_username = "opc"
  ## プライベート IP の利用
  ## 当社環境はプライベート IP でアクセスできるようになっているため、 true を指定
  use_private_ip = true
  ## ベースイメージは最新のイメージを探す
  base_image_filter {
    ## ベースイメージのコンパートメント OCID (未指定の場合、インスタンスの compartment_ocid と同じ)
    compartment_id = "<Compartment OCID>"
    ## ベースイメージ OS の名前
    operating_system = "Oracle Linux"
    ## ベースイメージ OS のバージョン
    operating_system_version = "8"
    ## イメージ名で正規表現の一致するもののみ取得
    ## 汎用の Oracle Linux 8 系は Oracle-Linux-8.x-YYYY.MM.DD-y のため、合致する正規表現を記載
    ## \ はエスケープする
    display_name_search = "^Oracle-Linux-8\\.\\d+-\\d{4}\\.\\d{2}\\.\\d{2}-\\d+$"
  }

  ## ユーザー認証情報
  ## リージョン: ap-tokyo-1
  region = "ap-tokyo-1"
  ## テナンシーの OCID
  tenancy_ocid = "<Tenancy OCID>"
  ## ユーザーの OCID (① で設定したユーザーの OCID)
  user_ocid = "<User OCID>"
  ## API キーのパス (① で作成した API キー)
  key_file = "~/.oci/oci_api_key.pem"
  ## API キーのフィンガープリント
  ## フィンガープリントは ① の config ファイルに記載されている
  fingerprint = "<API FingerPrint>"

  ## 出力イメージの設定
  ## イメージ配置先コンパートメントの OCID (未指定の場合、インスタンスの compartment_ocid と同じ)
  image_compartment_ocid = "<Compartment OCID>"
  ## 出力イメージ名
  image_name = "oracle-linux-8-customimage-for-instance"
}

## イメージの作成
## https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
  ## 上で設定した source を利用
  sources = [
    ## source "oracle-oci" "instance-image" の場合
    "source.oracle-oci.instance-image"
  ]

  ## provisioner にカスタムイメージで実行する内容を設定
  ## https://www.packer.io/docs/templates/hcl_templates/blocks/build/provisioner

  ## ファイルの配置
  ## 今回の要件としては不要、ファイルの配置ができることを確認するために実施
  provisioner "file" {
    ## コピー元 (ローカル)
    source = "./file/test.txt"
    ## コピー先 (リモートのカスタムイメージ作成用インスタンス)
    destination = "/home/opc/test.txt"
  }

  ## コマンドを実行
  ## 今回は例として、プロキシーの設定、git コマンドのインストールを実行、送信したテキスト内容の表示を実施する
  provisioner "shell" {
    inline = [
      ## プロキシーの設定
      "sudo touch /etc/environment",
      "echo \"http_proxy=<HTTPProxy>:<HTTPProxy Port>\" | sudo tee -a /etc/environment > /dev/null",
      "echo \"https_proxy=<HTTPSProxy>:<HTTPSProxy Port>\" | sudo tee -a /etc/environment > /dev/null",
      "echo \"no_proxy=<NoProxy>\" | sudo tee -a /etc/environment > /dev/null",
      "echo \"HTTP_PROXY=<HTTPProxy>:<HTTPProxy Port>\" | sudo tee -a /etc/environment > /dev/null",
      "echo \"HTTPS_PROXY=<HTTPSProxy>:<HTTPSProxy Port>\" | sudo tee -a /etc/environment > /dev/null",
      "echo \"NO_PROXY=<NoProxy>\" | sudo tee -a /etc/environment > /dev/null",
      ## ログアウトまでプロキシー設定が反映されないため、一時的にプロキシーを設定
      "export http_proxy=<HTTPProxy>:<HTTPProxy Port>",
      "export https_proxy=<HTTPSProxy>:<HTTPSProxy Port>",
      "export no_proxy=<NoProxy>",
      ## dnf のプロキシー除外設定
      "sudo ls /etc/yum.repos.d/*.repo | sudo xargs -i sed -i -e \"/^baseurl=https:\\/\\/yum\\$ociregion\\.\\$ocidomain\\//a proxy=_none_\" {}",

      ## git コマンドのインストール
      "sudo dnf install -y git",

      ## test.txt の内容を表示する
      ## 今回の要件としては不要、試験的に配置したファイルの内容を表示する
      "cat /home/opc/test.txt"
    ]
  }

  ## イメージ作成完了後に情報を出力する
  post-processor "manifest" {
    output = "./result.json"
    strip_path = true
  }
}
## ---- oci.pkr.hcl ---- ##

## 試験的に内容を表示するファイルを作成
$ mkdir ./file/
$ echo "this is test file." > ./file/test.txt

## ファイルの構文に問題がないか確認する
$ packer validate ./oci.pkr.hcl 
The configuration is valid.

次に、packer build コマンドでイメージを作成します。

## packer コマンドを実行し、イメージを作成する
$ packer build ./oci.pkr.hcl
oracle-oci.instance-image: output will be in this color.
## インスタンスの作成
==> oracle-oci.instance-image: Creating temporary ssh key for instance...
==> oracle-oci.instance-image: Creating instance...
==> oracle-oci.instance-image: Created instance (ocid1.instance.oc1.ap-tokyo-1.xxxxxxxx...).
…
==> oracle-oci.instance-image: Connected to SSH!
## ファイル (test.txt) の送信
==> oracle-oci.instance-image: Uploading ./file/test.txt => /home/opc/test.txt
…
## コマンドの実行
==> oracle-oci.instance-image: Provisioning with shell script: /tmp/packer-shellxxxxxxxxxx
…
    oracle-oci.instance-image: Installed:
    oracle-oci.instance-image:   git-2.31.1-2.el8.x86_64               git-core-2.31.1-2.el8.x86_64
    oracle-oci.instance-image:   git-core-doc-2.31.1-2.el8.noarch      perl-Error-1:0.17025-2.el8.noarch
    oracle-oci.instance-image:   perl-Git-2.31.1-2.el8.noarch          perl-TermReadKey-2.37-7.el8.x86_64
    oracle-oci.instance-image: Complete!
    oracle-oci.instance-image: this is test file.
## イメージを作成
==> oracle-oci.instance-image: Creating image from instance...
==> oracle-oci.instance-image: Created image (ocid1.image.oc1.ap-tokyo-1.xxxxxxxx...).
## イメージが作成できたため、インスタンスを削除 (イメージの作成に失敗した場合も削除される)
==> oracle-oci.instance-image: Terminating instance (ocid1.instance.oc1.ap-tokyo-1.xxxxxxxx...)...
==> oracle-oci.instance-image: Terminated instance.
## 作成完了後の情報を出力
==> oracle-oci.instance-image: Running post-processor:  (type manifest)
Build 'oracle-oci.instance-image' finished after 5 minutes 27 seconds.
==> Builds finished. The artifacts of successful builds are:
--> oracle-oci.instance-image: An image was created: 'oracle-linux-8-customimage-for-instance' (OCID: ocid1.image.oc1.ap-tokyo-1.xxxxxxxx...) in region 'ap-tokyo-1'

## 結果は返却値からも確認できる (0: 成功、0 以外: 失敗)
$ echo $?
0

指定したコンパートメントに oracle-linux-8-customimage-for-instance という名前でカスタムイメージが作成され、イメージの情報が result.json に書き込まれます。また、カスタムイメージの作成で利用したインスタンス (instance-for-custom-image-from-packer) は自動で削除されます。

④ 展開して試験を実施

③ で作成したカスタムイメージが正しく設定されているかを確認するために、インスタンスに展開し、試験ツールを実行して確認したのち、作成したインスタンスを削除する手順を実施します。試験用インスタンスの作成には Terraform を利用します。まず、Terraform のインストール手順を以下に示します。インストールの手順は 公式のインストール手順 を参照してください。

## 依存パッケージのインストール
$ sudo apt-get update && sudo apt-get install -y gnupg software-properties-common curl
## Terraform のインストール
$ curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
$ sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
$ sudo apt-get update && sudo apt-get install terraform

## 確認する
$ terraform --version
Terraform v1.2.3
on linux_amd64

次に、展開する Terraform の設定ファイルを作成します。今回は試験ツールの Goss を SSH 経由でインスタンス作成時に実行するため、SSH の鍵を事前に作成し、Terraform に利用する OCI の認証情報とインスタンスの構成ファイルをそれぞれ作成します。

## Terraform 用のディレクトリーを作成する
$ cd ~/
$ mkdir ./terraform/
$ cd ./terraform/

## SSH 鍵ファイルの作成
$ mkdir ./key/
$ cd ./key/
$ ssh-keygen 
Generating public/private rsa key pair.
## カレントディレクトリーに配置するため、ファイルパスを指定する
Enter file in which to save the key (/home/<User>/.ssh/id_rsa): ./id_rsa
## パスフレーズは設定しないためそのまま Enter
Enter passphrase (empty for no passphrase): (Enter)
Enter same passphrase again: (Enter)
Your identification has been saved in ./id_rsa
Your public key has been saved in ./id_rsa.pub
…

## 生成されたことを確認する
## (id_rsa.pub をインスタンス生成時に渡して id_rsa で SSH アクセスできるようにする)
$ ls ./
id_rsa  id_rsa.pub

## 次の作業のために terraform ディレクトリーに戻っておく
$ cd ../

## Terraform でインスタンスを操作するユーザーの情報を記載する
## インスタンスの操作権限が必要なため、とくに分ける必要がなければ ① で作成した config ファイルと同じでよい
$ cat ~/.oci/config
[DEFAULT]
user=<User OCID>
fingerprint=<FingerPrint>
key_file=/home/<User>/.oci/oci_api_key.pem
tenancy=<Tenancy OCID>
region=ap-tokyo-1

## config ファイルから情報を渡す
$ vim ./provider.tf

## ---- provider.tf ---- ##
provider "oci" {
  tenancy_ocid = "<Tenancy OCID>"
  user_ocid = "<User OCID>" 
  private_key_path = "/home/<User>/.oci/oci_api_key.pem"
  fingerprint = "<FingerPrint>"
  region = "ap-tokyo-1"
}
## ---- provider.tf ---- ##

## Terraform で作成するインスタンスの情報を記載する
$ vim ./compute.tf

## ---- compute.tf ---- ##
## カスタムイメージの OCID は変数化する (後述)
## コマンドで指定、または、オプションなしの場合はコンソールから入力
## terraform apply -var image_ocid="<Custom Imgae OCID>"
variable "image_ocid" {}

## インスタンスの情報
resource "oci_core_instance" "test_instance" {
  ## 試験用インスタンス名
  display_name = "test-for-custom-image-from-terraform"
  ## 試験用インスタンスの展開先コンパートメント OCID
  compartment_id  = "<Compartment OCID>"
  ## 試験用インスタンスの展開先 Availability Domain
  availability_domain = "xxxx:AP-TOKYO-1-AD-1"
  ## 試験用インスタンスのシェイプ
  shape = "VM.Standard.E4.Flex"
  ## 試験用インスタンスのシェイプのサイズ
  shape_config {
    ## OCPU のサイズ
    ocpus = "1"
    ## メモリーサイズ
    memory_in_gbs = "4"
  }

  ## 試験用インスタンスの VNIC 情報
  create_vnic_details {
    ## 展開先 VCN サブネットの OCID
    subnet_id = "<Subnet OCID>"
    ## Public IP の割り当て
    ## 当社環境はプライベート IP でアクセスできるようになっているため、false を設定する
    assign_public_ip = false
  }

  ## イメージの設定
  source_details {
    ## ソースタイプにイメージを指定
    source_type = "image"
    ## カスタムイメージの OCID
    ## 変数化しているため、コマンドから渡すことができる
    source_id = "${var.image_ocid}"
  }

  ## OCI のメタデータ
  metadata = {
    ## SSH アクセス用の公開鍵
    ssh_authorized_keys = file("./key/id_rsa.pub")
  }
}
## ---- compute.tf ---- ##

次に、Goss の試験ファイルを作成します。試験内容として、以下を実施します。

  • 環境変数のプロキシー設定 (http_proxy, https_proxy, no_proxy, HTTP_PROXY, HTTPS_PROXY, NO_PROXY) の値の一致を確認
  • dnf のリポジトリーのプロキシー除外設定を確認
  • git コマンドの実行を確認
  • git パッケージのインストール状況を確認
  • ssh のサービスが起動しているか、自動起動が設定されているか確認
  • ssh のポート (tcp/22) が開放されているか確認
## Goss 用のディレクトリーを作成
$ mkdir ./goss/
$ cd ./goss/

## Goss の試験ファイルを作成
$ vim ./goss.yaml

## ---- goss.yaml ---- ##

## より詳細な設定やパラメーターは公式ドキュメントを参照
## https://github.com/aelsabbahy/goss/blob/master/docs/manual.md#available-tests

## コマンドを実行した返却値と出力から試験
## ここでは実行内容 (exec) を除く、全 19 項目が試験の項目数となる
command:
  ## http_proxy の設定
  http_proxy:
    ## 実行するコマンド
    exec: "echo $http_proxy"
    ## コマンドの返却値の試験
    exit-status: 0
    ## Packer で設定したプロキシーの情報と一致しているかどうか
    stdout:
      - "<HTTPProxy>:<HTTPProxy Port>"
  ## https_proxy の設定
  https_proxy:
    exec: "echo $https_proxy"
    exit-status: 0
    stdout:
      - "<HTTPSProxy>:<HTTPSProxy Port>"
  ## no_proxy の設定
  no_proxy:
    exec: "echo $no_proxy"
    exit-status: 0
    stdout:
      - "<NoProxy>"
  ## HTTP_PROXY の設定
  HTTP_PROXY:
    exec: "echo $HTTP_PROXY"
    exit-status: 0
    stdout:
      - "<HTTPProxy>:<HTTPProxy Port>"
  ## HTTPS_PROXY の設定
  HTTPS_PROXY:
    exec: "echo $HTTPS_PROXY"
    exit-status: 0
    stdout:
      - "<HTTPSProxy>:<HTTPSProxy Port>"
  ## NO_PROXY の設定
  NO_PROXY:
    exec: "echo $NO_PROXY"
    exit-status: 0
    stdout:
      - "<NoProxy>"
  ## dnf のリポジトリーのプロキシー除外設定
  dnf_no_proxy:
    ## ヘッダーを取り除いた repolist の数と yum.repo.d 配下のリポジトリーの proxy=_none_ 行の数が一致しているか
    exec: "test $(dnf repolist all | sed -e '1d' | wc -l) -eq $(grep 'proxy=_none_' /etc/yum.repos.d/* | wc -l)"
    ## 一致した場合は 0、しない場合は 0 以外
    exit-status: 0
  ## git コマンドの実行
  git:
    exec: "git --version"
    exit-status: 0
    ## バージョン情報が出力されれば OK とする
    stdout:
      - "/^git version [\\d]+\\.[\\d]+\\.[\\d]+$/"
## dnf のパッケージの情報
package:
  ## git のパッケージ
  git:
    ## インストールされているか
    installed: true
    ## バージョン指定の場合は versions も確認するとよい
    ## versions:
    ## - x.y.z
## サービスの稼働状況
service:
  ## ssh のサービス
  sshd:
    ## サービスが稼働しているか
    running: true
    ## インスタンス起動時の自動実行の設定
    enabled: true
## ポート設定
port:
  ## ssh のポート である tcp の 22 番があいているか (コロンが複数ありややこしいが、パラメーター名が tcp:22 となっている)
  tcp:22:
    ## ポートがあいているか
    listening: true
## ---- goss.yaml ---- ##

次に、試験用のインスタンスに試験ファイルを渡して Goss を実行するよう、compute.tf ファイルを修正します。

## Terraform のcompute.tf を修正して Goss を実行するよう変更
$ vim ./compute.tf

## ---- compute.tf ---- ##
  ## (途中まで省略)

  ## OCI のメタデータ
  metadata = {
    ## SSH アクセス用の公開鍵
    ssh_authorized_keys = file("./key/id_rsa.pub")
  }
}

## 以下を追記する
## SSH で接続して実行する
resource "null_resource" "remote-exec" {
  ## 依存関係
  depends_on = [
    ## 試験用インスタンスの作成が前提
    oci_core_instance.test_instance
  ]

  ## ファイルを配置する
  provisioner "file" {
    ## SSH SCP の情報
    connection {
      ## 当社環境はプライベート IP でアクセスできるため、接続先は上の試験用インスタンスのプライベート IP
      host = "${oci_core_instance.test_instance.private_ip}"
      ## SSH Agent は利用しない
      agent = false
      ## タイムアウトは 30 分
      timeout = "30m"
      ## 接続ユーザー
      user = "opc"
      ## SSH 鍵ファイルの配置先
      private_key = file("./key/id_rsa")
    }
    ## 送信するファイル (Goss の試験ファイル)
    source      = "./goss.yaml"
    ## 配置先 (ホームディレクトリーとする)
    destination = "/home/opc/goss.yaml"
  }

  ## SSH で実行する
  provisioner "remote-exec" {
    ## connection の説明は provisioner "file" と同じため省略
    connection {
      host = "${oci_core_instance.test_instance.private_ip}"
      agent = false
      timeout = "30m"
      user = "opc"
      private_key = file("./key/id_rsa")
    }
    ## コマンドを実行
    inline = [
      ## 作業ディレクトリーを移動
      "cd /home/opc",
      ## Goss のインストール (latest となっていますが、実施時は v0.3.18 を利用しています)
      "sudo curl -L https://github.com/aelsabbahy/goss/releases/latest/download/goss-linux-amd64 -o /usr/local/bin/goss",
      ## 実行権限を変更
      "sudo chmod +rx /usr/local/bin/goss",
      ## 試験を実施
      "goss validate"
    ]
  }
}
## ---- compute.tf ---- ##

次に、Terraform から試験用インスタンスを作成してカスタムイメージが正常に作成されているか確認します。

## goss ディレクトリーから terraform ディレクトリーに移動
$ cd ../

## Terraform の初期設定を実施 (初回のみ)
$ terraform init

## 設定ファイルの確認 (apply でも実行されるため、なくてもよい)
$ terraform plan -var "image_ocid=<Custom Image OCID>"
…
Plan: 2 to add, 0 to change, 0 to destroy.

## 試験用インスタンスを展開する
$ terraform apply -auto-approve -var "image_ocid=<Custom Image OCID>"
…
Plan: 2 to add, 0 to change, 0 to destroy.
## 試験用インスタンスの作成
oci_core_instance.test_instance: Creating...
…
oci_core_instance.test_instance: Creation complete after 37s [id=ocid1.instance.oc1.ap-tokyo-1.xxxxxxxxxx]
null_resource.remote-exec: Creating...
## Goss 設定ファイルの送信
null_resource.remote-exec: Provisioning with 'file'...
…
## コマンド実行
null_resource.remote-exec: Provisioning with 'remote-exec'...
…
## Goss の試験結果 (OK の場合は緑色の . が出力され、NG の場合は赤字で F が出力される)
## 試験結果が一つでも F だった場合、コマンドが失敗扱いとなり、Terraform の構築も失敗とみなされる
null_resource.remote-exec (remote-exec): ..................
null_resource.remote-exec (remote-exec): .
## Goss の試験時間と結果
null_resource.remote-exec (remote-exec): Total Duration: 0.735s
null_resource.remote-exec (remote-exec): Count: 19, Failed: 0, Skipped: 0

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

## 実行結果を確認する
## 構築に失敗した場合は 0 以外を返す (=試験に失敗した場合も 0 以外を返す)
$ echo $?
0

最後に、Terraform は構築ツールのため、作成したリソースは成功/失敗問わず、自動で削除されません。したがって、リソースの削除コマンドを実行し、試験用のインスタンスを削除して完了となります。

## Terraform で展開したリソースの削除
## -var image_ocid の値は空でよい
$ terraform apply -destroy -auto-approve -var "image_ocid="
…
Plan: 0 to add, 0 to change, 2 to destroy.
…
## インスタンスの削除
oci_core_instance.test_instance: Destroying... [id=ocid1.instance.oc1.ap-tokyo-1.xxxxxxxxxx]
…
oci_core_instance.test_instance: Destruction complete after 1m21s
## 削除完了
Apply complete! Resources: 0 added, 0 changed, 2 destroyed.

おわりに

今回は自動化の第一歩としてカスタムイメージの生成と試験を実施しました。ツールとして Packer, Terraform, Goss をそれぞれ利用しましたが、Packer や Goss は構成がシンプルで理解もしやすく、すぐに導入しやすいという印象を受けました。一方で Terraform はできることが多く、複雑な処理をいれたりすると理解に時間がかかりそうなイメージをもちました。今回のようなインスタンスを作成してコマンドを実行する程度の簡単な処理であれば、Terraform もそこまで難しいものではないかなと思います。

今後は、Terraform 等でインフラのコード管理をしつつ、運用環境の管理の仕組みづくりや、試験環境、開発環境を簡単に構築できるような仕組みを考えていきたいです。また、他の構成管理ツールや試験ツールにも興味があるため、今回利用したツールと比較したいと考えています。


TOP
アルファロゴ 株式会社アルファシステムズは、ITサービス事業を展開しています。このブログでは、技術的な取り組みを紹介しています。X(旧Twitter)で更新通知をしています。