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 | 
インストール
Section titled “インストール”バージョン管理に rtx を使用します。
Packer
Section titled “Packer”$ rtx plugin install packer
# 最新のバージョンを確認する$ rtx ls-remote packer$ rtx install packer 1.10.0$ rtx local packer 1.10.0Ansible
Section titled “Ansible”$ rtx plugin install ansible-base
# 最新のバージョンを確認する$ rtx ls-remote ansible-base$ rtx install ansible-base 2.10.17$ rtx local ansible-base 2.10.17Example1: Packer で Docker Imageを作成する
Section titled “Example1: Packer で Docker Imageを作成する”Docker Plugin を使用する
Section titled “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.hclpacker 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.1packer 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ファイル分割
Section titled “ファイル分割”前回、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.hclpacker {  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変数を設定する方法
Section titled “変数を設定する方法”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
変数ファイルの作成
Section titled “変数ファイルの作成”variables.auto.pkrvars.hcl と variables.pkr.hcl を作成します。
$ tree.└── example1    ├── docker.pkr.hcl    ├── plugin.pkr.hcl    ├── variables.auto.pkrvars.hcl    └── variables.pkr.hclvariable "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を入れる
Section titled “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ファイルを用意する
Section titled “ファイルを用意する”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: nginxansible/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   226MBexample2コンテナを作成する
Section titled “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             example2nginx が起動していることを確認できました。
$ 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 の設定をする
Section titled “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/initExample4: Ansible リファクタ (roleの作成)
Section titled “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.84GBNICE DCV が M1 Mac ではインストールできなかったので、完全に自動化できませんでした。 しかし Ansible + Packer を使ってある程度、環境を再現できることがわかったので、たとえば、Ansible の挙動を確かめたい場合などで使えるようになりました。
ansible_facts を出力する
Section titled “ansible_facts を出力する”Docker の Ubuntu Image を使用して ansible_facts を出力してみた結果
---- hosts: all  become: true
  tasks:  - name: Display ansible_system variable      ansible.builtin.debug:        var: ansible_factsprint 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: }