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
環境
バージョン | |
---|---|
Mac | Ventura 13.2.1 |
Packer | v1.10.0 |
Ansible | 2.10.17 |
Docker cli | 24.0.7 |
インストール
バージョン管理に rtx を使用します。
Packer
$ rtx plugin install packer
# 最新のバージョンを確認する$ rtx ls-remote packer$ rtx install packer 1.10.0$ rtx local packer 1.10.0
Ansible
$ 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 をそのままコピペで動かしてみます。
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"] } }}
$ packer init .$ packer fmt .$ packer build docker.pkr.hcl
packer build docker.pkr.hcl
$ packer build docker.pkr.hcldocker.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.1Build '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
が作成されています。
$ docker image lsREPOSITORY TAG IMAGE ID CREATED SIZEmyrepo/example 0.1 3a5a5122eb1a 57 seconds ago 69.3MBubuntu latest da935f064913 2 weeks ago 69.3MB
ファイル分割
前回、Packer を勉強した際使用しませんでしたが、HCL2 tmplate を使っているのでファイル分割と変数ファイルを作成します。
Packer はディレクトリ内の .pkr.hcl
で終わるすべてのファイルをロードするので、ファイル名は何でもよいです。
Terraform と同じで変数を使用する場合は、あらかじめ variable
を定義する必要があり、変数の参照には、var.
、 local.
でアクセスできます。
先ほど作成した docker.pkr.hcl
を example1/plugin.pkr.hcl
と example1/docker.pkr.hcl
の2つに分割します。
ディレクトリ構造
$ tree.└── example1 ├── docker.pkr.hcl └── plugin.pkr.hcl
packer { required_version = ">= 1.10.0"
required_plugins { docker = { source = "github.com/hashicorp/docker" version = "~> 1" } }}
ついでに changes を追記します。
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 オプションで検索しやすくなりました。
$ packer build example1
$ docker image ls -f label=build=packerREPOSITORY TAG IMAGE ID CREATED SIZEmyrepo/example 0.1 0e464f623c7a About a minute ago 69.3MB
変数を設定する方法
packer build
コマンドのオプション-var
を使用する。- (例)
packer build -var "weekday=Sunday" -var "flavor=chocolate" .
- (例)
- varファイルを作成し、
packer build
コマンドのオプション-var-file
を使用する。- (例)
packer build -var-file="variables.pkrvars.hcl" .
- (例)
- 変数を自動で渡してくれる
*.auto.pkrvars.hcl
ファイルを使用する。
- 環境変数
PKR_VAR_xxx
を使用する。- (例)
export PKR_VAR_weekday=Monday
- (例)
引用:Input Variables and local variables - Packer Documentation
変数ファイルの作成
variables.auto.pkrvars.hcl
と variables.pkr.hcl
を作成します。
$ tree.└── example1 ├── docker.pkr.hcl ├── plugin.pkr.hcl ├── variables.auto.pkrvars.hcl └── 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 にあげてみます
image = "ubuntu:22.04"repository = "myrepo/example1"tag = "0.2"
build を実行した後、一覧を取得してみると tag が 0.2 に上がっています。これで値を変数にすることができました。
$ packer build example1
$ docker image ls -f label=build=packerREPOSITORY TAG IMAGE ID CREATED SIZEmyrepo/example1 0.2 4741eb6545ee 49 seconds ago 69.3MBmyrepo/example 0.1 0e464f623c7a 3 hours ago 69.3MB
最後にいらなくなったイメージを削除します。
$ 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 で定義できるようにファイルを追加します。
$ tree example2example2├── 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
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
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
---- hosts: all
tasks: - name: Install Nginx ansible.builtin.apt: name: nginx
ansible/ansible.cfg
``` [defaults] interpreter_python=/usr/bin/python3 ```$ packer init example2$ packer fmt example2$ packer build example2
$ docker image ls -f label=build=packerREPOSITORY TAG IMAGE ID CREATED SIZEmyrepo/example2 0.1 9594615e6fc4 13 minutes ago 226MB
example2コンテナを作成する
まず nginx をインストールできたか確認してみる
$ docker run -it --rm myrepo/example2:0.1 /bin/bashroot@7650b88a13e1:/# which nginx/usr/sbin/nginx
先にコンテナをバックグラウンドで立ち上げます。
$ docker run --privileged -d --name example2 --rm myrepo/example2:0.1 /sbin/init
$ docker container lsCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES0bac12205b1d myrepo/example2:0.1 "/sbin/init" 7 seconds ago Up 6 seconds example2
nginx が起動していることを確認できました。
$ 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
# シンタックスチェック$ ansible-playbook example3/ansible/playbook.yml --syntax-check -i localhost,
$ packer build example3
$ docker image ls -f label=build=packerREPOSITORY TAG IMAGE ID CREATED SIZEmyrepo/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も)
一般的なディレクトリ構造は以下に示す通りです。
roles/ common/ tasks/ handlers/ library/ files/ templates/ vars/ defaults/ meta/
example4 では以下のように ansible/roles
配下を作成し role を定義しています。
$ tree example4example4├── 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 の時と結果は同じです。
$ packer build example4
$ docker image ls -f label=build=packerREPOSITORY TAG IMAGE ID CREATED SIZEmyrepo/example4 0.1 66249971d26f 3 minutes ago 2.84GB
さいごに
NICE DCV が M1 Mac ではインストールできなかったので、完全に自動化できませんでした。 しかし Ansible + Packer を使ってある程度、環境を再現できることがわかったので、たとえば、Ansible の挙動を確かめたい場合などで使えるようになりました。
おまけ
ansible_facts を出力する
Docker の Ubuntu Image を使用して ansible_facts
を出力してみた結果
---- hosts: all become: true
tasks: - name: Display ansible_system variable ansible.builtin.debug: var: ansible_facts
print ansible_facts
変数が多かったため、一部削除
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": 542docker.example: },docker.example: "real": {docker.example: "free": 1559,docker.example: "total": 3910,docker.example: "used": 2351docker.example: },docker.example: "swap": {docker.example: "cached": 0,docker.example: "free": 0,docker.example: "total": 0,docker.example: "used": 0docker.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: }