Skip to content

PackerとAnsibleでDockerイメージを作成する

はじめに

前回 Ansible と Packer に入門する というブログを書きました。

Packer と Ansible を使って AMI を作成したいとき、毎回 EC2 インスタンスの起動に時間がかかるため、検証に時間がかかります。 今回は Ansible の実行先を EC2 ではなく、Dcoker にすることで検証しやすい環境を作成したいと思います。

成果物

https://github.com/kntks/blog-code/tree/main/2023/12/ansible-packer-docker

環境

バージョン
MacVentura 13.2.1
Packerv1.10.0
Ansible2.10.17
Docker cli24.0.7

インストール

バージョン管理に rtx を使用します。

Packer

Terminal window
$ rtx plugin install packer
# 最新のバージョンを確認する
$ rtx ls-remote packer
$ rtx install packer 1.10.0
$ rtx local packer 1.10.0

Ansible

Terminal window
$ rtx plugin install ansible-base
# 最新のバージョンを確認する
$ rtx ls-remote ansible-base
$ rtx install ansible-base 2.10.17
$ rtx local ansible-base 2.10.17

Example1: Packer で Docker Imageを作成する

Docker Plugin を使用する

ドキュメントに書かれている Example をそのままコピペで動かしてみます。

docker.pkr.hcl
packer {
required_version = ">= 1.10.0"
required_plugins {
docker = {
source = "github.com/hashicorp/docker"
version = "~> 1"
}
}
}
source "docker" "example" {
image = "ubuntu"
commit = true
}
build {
sources = ["source.docker.example"]
post-processors {
post-processor "docker-tag" {
repository = "myrepo/example"
tags = ["0.1"]
}
}
}

Docker - Packer Documentation

Terminal window
$ packer init .
$ packer fmt .
$ packer build docker.pkr.hcl
packer build docker.pkr.hcl
Terminal window
$ packer build docker.pkr.hcl
docker.example: output will be in this color.
==> docker.example: Creating a temporary directory for sharing data...
==> docker.example: Pulling Docker image: ubuntu
docker.example: Using default tag: latest
docker.example: latest: Pulling from library/ubuntu
docker.example: 005e2837585d: Pulling fs layer
docker.example: 005e2837585d: Verifying Checksum
docker.example: 005e2837585d: Download complete
docker.example: 005e2837585d: Pull complete
docker.example: Digest: sha256:6042500cf4b44023ea1894effe7890666b0c5c7871ed83a97c36c76ae560bb9b
docker.example: Status: Downloaded newer image for ubuntu:latest
docker.example: docker.io/library/ubuntu:latest
==> docker.example: Starting docker container...
docker.example: Run command: docker run -v /Users/xxxxxx/.config/packer/tmp1555770819:/packer-files -d -i -t --entrypoint=/bin/sh -- ubuntu
docker.example: Container ID: 3f5ec89f8a2325ce79dce2d25dd7bfe3f36984d36ef7427daa8e9fcc7b03e4a8
==> docker.example: Using docker communicator to connect: 172.17.0.2
==> docker.example: Committing the container
docker.example: Image ID: sha256:3a5a5122eb1a5badec39e1030647c5afaacd7c44fce7ceabd2dea2bbc7cdf072
==> docker.example: Killing the container: 3f5ec89f8a2325ce79dce2d25dd7bfe3f36984d36ef7427daa8e9fcc7b03e4a8
==> docker.example: Running post-processor: (type docker-tag)
docker.example (docker-tag): Tagging image: sha256:3a5a5122eb1a5badec39e1030647c5afaacd7c44fce7ceabd2dea2bbc7cdf072
docker.example (docker-tag): Repository: myrepo/example:0.1
Build 'docker.example' finished after 15 seconds 390 milliseconds.
==> Wait completed after 15 seconds 390 milliseconds
==> Builds finished. The artifacts of successful builds are:
--> docker.example: Imported Docker image: sha256:3a5a5122eb1a5badec39e1030647c5afaacd7c44fce7ceabd2dea2bbc7cdf072
--> docker.example: Imported Docker image: myrepo/example:0.1 with tags myrepo/example:0.1

packer build が成功すると、pull してきた ubuntu イメージとコミットした myrepo/example:0.1 が作成されています。

Terminal window
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
myrepo/example 0.1 3a5a5122eb1a 57 seconds ago 69.3MB
ubuntu latest da935f064913 2 weeks ago 69.3MB

ファイル分割

前回、Packer を勉強した際使用しませんでしたが、HCL2 tmplate を使っているのでファイル分割と変数ファイルを作成します。

Packer はディレクトリ内の .pkr.hcl で終わるすべてのファイルをロードするので、ファイル名は何でもよいです。
Terraform と同じで変数を使用する場合は、あらかじめ variable を定義する必要があり、変数の参照には、var.local. でアクセスできます。

先ほど作成した docker.pkr.hclexample1/plugin.pkr.hclexample1/docker.pkr.hcl の2つに分割します。

ディレクトリ構造

Terminal window
$ tree
.
└── example1
├── docker.pkr.hcl
└── plugin.pkr.hcl
example1/plugin.pkr.hcl
packer {
required_version = ">= 1.10.0"
required_plugins {
docker = {
source = "github.com/hashicorp/docker"
version = "~> 1"
}
}
}

ついでに changes を追記します。

example1/docker.pkr.hcl
source "docker" "example" {
image = "ubuntu"
commit = true
changes = [
"LABEL build=packer"
]
}
build {
sources = ["source.docker.example"]
post-processors {
post-processor "docker-tag" {
repository = "myrepo/example"
tags = ["0.1"]
}
}
}

実行は以下のコマンドでできます。LABEL をつけたことにより、イメージ一覧の filter オプションで検索しやすくなりました。

Terminal window
$ packer build example1
$ docker image ls -f label=build=packer
REPOSITORY TAG IMAGE ID CREATED SIZE
myrepo/example 0.1 0e464f623c7a About a minute ago 69.3MB

変数を設定する方法

  1. packer build コマンドのオプション -var を使用する。
    • (例)packer build -var "weekday=Sunday" -var "flavor=chocolate" .
  2. varファイルを作成し、packer build コマンドのオプション -var-file を使用する。
    • (例) packer build -var-file="variables.pkrvars.hcl" .
  3. 変数を自動で渡してくれる *.auto.pkrvars.hcl ファイルを使用する。
  4. 環境変数 PKR_VAR_xxx を使用する。
    • (例) export PKR_VAR_weekday=Monday

引用:Input Variables and local variables - Packer Documentation

変数ファイルの作成

variables.auto.pkrvars.hclvariables.pkr.hcl を作成します。

Terminal window
$ tree
.
└── example1
├── docker.pkr.hcl
├── plugin.pkr.hcl
├── variables.auto.pkrvars.hcl
└── variables.pkr.hcl
example1/variables.pkr.hcl
variable "image" {
type = string
default = "ubuntu:22.04"
description = "source image"
}
variable "repository" {
type = string
default = null
}
variable "tag" {
type = string
default = "0.1"
description = "build tag"
}

今度は tag を 0.1 から 0.2 にあげてみます

example1/variables.auto.pkrvars.hcl
image = "ubuntu:22.04"
repository = "myrepo/example1"
tag = "0.2"

build を実行した後、一覧を取得してみると tag が 0.2 に上がっています。これで値を変数にすることができました。

Terminal window
$ packer build example1
$ docker image ls -f label=build=packer
REPOSITORY TAG IMAGE ID CREATED SIZE
myrepo/example1 0.2 4741eb6545ee 49 seconds ago 69.3MB
myrepo/example 0.1 0e464f623c7a 3 hours ago 69.3MB

最後にいらなくなったイメージを削除します。

Terminal window
$ docker image rm `docker image ls -f label=build=packer -q`

参考:Input Variables - Packer Documentation

Example2: Ansible Pluginを入れる

先ほどは Packer を使って Docker Image にタグづけをしました。次は Docker Image (Ubuntu) に入れるパッケージを Ansible で定義できるようにファイルを追加します。

Terminal window
$ tree example2
example2
├── ansible
├── ansible.cfg
└── playbook.yml
├── docker.pkr.hcl
├── plugin.pkr.hcl
├── variables.auto.pkrvars.hcl
└── variables.pkr.hcl

ファイルを用意する

variables.*.pkr.hcl は example1 のときと同じなので割愛

example2/plugin.pkr.hcl
example2/plugin.pkr.hcl
packer {
required_version = ">= 1.10.0"
required_plugins {
docker = {
source = "github.com/hashicorp/docker"
version = "~> 1"
}
ansible = {
source = "github.com/hashicorp/ansible"
version = ">= 1.1.1"
}
}
}
example2/docker.pkr.hcl
example2/docker.pkr.hcl
source "docker" "example" {
image = var.image
commit = true
changes = [
"LABEL build=packer"
]
run_command = [ "-d", "-i", "-t", "--", "{{.Image}}" ]
}
build {
sources = ["source.docker.example"]
provisioner "shell" {
inline = [
"apt update",
"apt install -y python3 sudo init systemd"
]
}
provisioner "ansible" {
user = "root"
playbook_file = "example2/ansible/playbook.yml"
extra_arguments = [
"--scp-extra-args",
"'-O'"
]
}
post-processors {
post-processor "docker-tag" {
repository = var.repository
tags = [var.tag]
}
}
}
ansible/playbook.yml
ansible/playbook.yml
---
- hosts: all
tasks:
- name: Install Nginx
ansible.builtin.apt:
name: nginx
ansible/ansible.cfg
[defaults]
interpreter_python=/usr/bin/python3
Terminal window
$ packer init example2
$ packer fmt example2
$ packer build example2
$ docker image ls -f label=build=packer
REPOSITORY TAG IMAGE ID CREATED SIZE
myrepo/example2 0.1 9594615e6fc4 13 minutes ago 226MB

example2コンテナを作成する

まず nginx をインストールできたか確認してみる

Terminal window
$ docker run -it --rm myrepo/example2:0.1 /bin/bash
root@7650b88a13e1:/# which nginx
/usr/sbin/nginx

先にコンテナをバックグラウンドで立ち上げます。

Terminal window
$ docker run --privileged -d --name example2 --rm myrepo/example2:0.1 /sbin/init
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0bac12205b1d myrepo/example2:0.1 "/sbin/init" 7 seconds ago Up 6 seconds example2

nginx が起動していることを確認できました。

Terminal window
$ docker exec -it example2 /bin/bash
/# systemctl status nginx
nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2023-12-27 13:25:52 UTC; 1min 27s agos

参考: 【ubuntu】Dockerでsystemctlを使えるようにする - Zenn Use custom configuration file for ansible provisioner - stackoverflow

Example3: Ansible で NICE DCV の設定をする

先日、NICE DCV をインストールした EC2 インスタンスに Web ブラウザからアクセスする、という記事を書きました。この記事は、AWSのドキュメントを参考にしながらコマンドを実行しただけの記事です。そのため実行したコマンドを Ansible の Playbook にすることで Image の作成を自動化します。

コードは以下にあります。 https://github.com/kntks/blog-code/tree/main/2023/12/ansible-packer-docker/example3

Terminal window
# シンタックスチェック
$ ansible-playbook example3/ansible/playbook.yml --syntax-check -i localhost,
$ packer build example3
$ docker image ls -f label=build=packer
REPOSITORY TAG IMAGE ID CREATED SIZE
myrepo/example3 0.1 6c95adc7da98 5 minutes ago 2.8GB
$ docker run --privileged -d --name example3 --rm myrepo/example3:0.1 /sbin/init

Example4: Ansible リファクタ (roleの作成)

role は再利用できる単位で定義します。今回は NICE DCV に関するイントールが主な作業だったので、role は1つしか作成しませんが、AMI を作成するときに別の role を作成しやすいようにします。

Once you group your content in roles, you can easily reuse them and share them with other users.

訳)コンテンツをロールでグループ化すれば、簡単に再利用したり、他のユーザーと共有したりできます。

Ansible will look in each directory within a role for a main.yml file for relevant content (also main.yaml and main):

訳)デフォルトでは、Ansibleはロール内の各ディレクトリで、関連するコンテンツのmain.ymlファイルを探します(main.yamlとmainも)

一般的なディレクトリ構造は以下に示す通りです。

Terminal window
roles/
common/
tasks/
handlers/
library/
files/
templates/
vars/
defaults/
meta/

Roles - Ansible Documentation

example4 では以下のように ansible/roles 配下を作成し role を定義しています。

Terminal window
$ tree example4
example4
├── ansible
│   ├── ansible.cfg
│   ├── playbook.yml
│   └── roles
│   ├── common
│   │   └── tasks
│   │   └── main.yml
│   └── nice-dcv
│   ├── files
│   │   └── xorg.conf
│   └── tasks
│   └── main.yml
├── docker.pkr.hcl
├── plugin.pkr.hcl
├── variables.auto.pkrvars.hcl
└── variables.pkr.hcl

コードは以下にあります。 https://github.com/kntks/blog-code/tree/main/2023/12/ansible-packer-docker/example4

ただ task を移動させただけなので、 example3 の時と結果は同じです。

Terminal window
$ packer build example4
$ docker image ls -f label=build=packer
REPOSITORY TAG IMAGE ID CREATED SIZE
myrepo/example4 0.1 66249971d26f 3 minutes ago 2.84GB

さいごに

NICE DCV が M1 Mac ではインストールできなかったので、完全に自動化できませんでした。 しかし Ansible + Packer を使ってある程度、環境を再現できることがわかったので、たとえば、Ansible の挙動を確かめたい場合などで使えるようになりました。

おまけ

ansible_facts を出力する

Docker の Ubuntu Image を使用して ansible_facts を出力してみた結果

playbool.yml
---
- hosts: all
become: true
tasks:
- name: Display ansible_system variable
ansible.builtin.debug:
var: ansible_facts
print ansible_facts

変数が多かったため、一部削除

Terminal window
docker.example: TASK [Display ansible_system variable] *****************************************
docker.example: ok: [default] => {
docker.example: "ansible_facts": {
docker.example: "ansible_local": {},
docker.example: "apparmor": {
docker.example: "status": "disabled"
docker.example: },
docker.example: "architecture": "aarch64",
docker.example: "discovered_interpreter_python": "/usr/bin/python3",
docker.example: "distribution": "Ubuntu",
docker.example: "distribution_file_parsed": true,
docker.example: "distribution_file_path": "/etc/os-release",
docker.example: "distribution_file_variety": "Debian",
docker.example: "distribution_major_version": "22",
docker.example: "distribution_release": "jammy",
docker.example: "distribution_version": "22.04",
docker.example: "domain": "",
docker.example: "effective_group_id": 0,
docker.example: "effective_user_id": 0,
docker.example: "env": {
docker.example: "HOME": "/root",
docker.example: "HOSTNAME": "xxxxxxx",
docker.example: "LC_CTYPE": "C.UTF-8",
docker.example: "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
docker.example: "PWD": "/"
docker.example: },
docker.example: "fqdn": "xxxxxxx",
docker.example: "gather_subset": [
docker.example: "all"
docker.example: ],
docker.example: "hostname": "xxxxxxx",
docker.example: "hostnqn": "",
docker.example: "is_chroot": false,
docker.example: "kernel": "5.15.0-89-generic",
docker.example: "kernel_version": "#99-Ubuntu SMP Mon Oct 30 23:43:36 UTC 2023",
docker.example: "lsb": {
docker.example: "codename": "jammy",
docker.example: "description": "Ubuntu 22.04.3 LTS",
docker.example: "id": "Ubuntu",
docker.example: "major_release": "22",
docker.example: "release": "22.04"
docker.example: },
docker.example: "machine": "aarch64",
docker.example: "machine_id": "61e5f03f582944399000743ece727428",
docker.example: "memfree_mb": 1559,
docker.example: "memory_mb": {
docker.example: "nocache": {
docker.example: "free": 3368,
docker.example: "used": 542
docker.example: },
docker.example: "real": {
docker.example: "free": 1559,
docker.example: "total": 3910,
docker.example: "used": 2351
docker.example: },
docker.example: "swap": {
docker.example: "cached": 0,
docker.example: "free": 0,
docker.example: "total": 0,
docker.example: "used": 0
docker.example: }
docker.example: },
docker.example: "memtotal_mb": 3910,
docker.example: "module_setup": true,
docker.example: "nodename": "xxxxxxx",
docker.example: "os_family": "Debian",
docker.example: "pkg_mgr": "apt",
docker.example: "python_version": "3.10.12",
docker.example: "selinux": {
docker.example: "status": "Missing selinux Python library"
docker.example: },
docker.example: "selinux_python_present": false,
docker.example: "service_mgr": "systemd",
docker.example: "system": "Linux",
docker.example: "system_vendor": "QEMU",
docker.example: "uptime_seconds": 292048,
docker.example: "user_dir": "/root",
docker.example: "user_gecos": "root",
docker.example: "user_gid": 0,
docker.example: "user_id": "root",
docker.example: "user_shell": "/bin/bash",
docker.example: "user_uid": 0,
docker.example: "userspace_bits": "64",
docker.example: "virtualization_role": "guest",
docker.example: "virtualization_type": "docker"
docker.example: }
docker.example: }

関連リンク