🐁

AnsibleでDockerコンテナの管理をしてみた

2020/09/20に公開
2

概要

こちらの内容は以前noteで書いた記事のリメイク的なものになります。
今見直すと色々と粗があるように感じましたので、少し手直してよりインフラのコード化を実現しました。

実施すること

AnsibleのPlaybookで以下のことを実施します。

  • Dockerイメージ取得
  • JSフレームワーク、Nuxt.jsコンテナを構築
  • Docker Hubへのイメージpush

使用したツールおよびAnsibleモジュール

  • Ansible 2.9.10
  • Docker 19.03.11
  • Docker API 1.40
  • Python 3.8.2
  • Docker SDK Python 1.80
  • docker_image
  • docker_container

docker_imagedocker_containerを使用するには、Docker APIのバージョンが1.20以上、Docker SDK Pythonのバージョンが1.80以上という必要条件がありますので、事前にバージョンを上げておきます。

事前準備

まず初めに、Nodeイメージを取得し、簡単にNuxt.jsのプロジェクト構築ができるツールcreate-nuxt-appを事前に使用してappディレクトリをホームディレクトリとするDockerfileを作成します。

Dockerfile
FROM node:latest
WORKDIR /app

このDockerfileをビルドして、Nodeイメージ作成後コンテナへアタッチしNuxt.jsプロジェクトのひな型を構築します。

コンテナ内コマンド
docker run  -it <コンテナID> bash
yarn create nuxt-app

nuxt-appコマンドを実行しますと、対話形式にNuxtの設定を行なえることができ、簡単にNuxtのプロジェクトを作成できます。質問の詳しい内容はこちらのサイトを参考にして、各自の好みに合わしたプロジェクトのひな型を作成します。
ひな型の作成が完了しましたら、コンテナ内から抜けてローカルマシン上へ作成したひな型をコピーします。
docker cp <コンテナID>:/app .
これで準備は整いましたので、DockerfileとPlaybookを作成していきます。

ディレクトリ構成

ディレクトリ構成は以下の通りです。]

$ tree
├── dockerFile
│   ├── Dockerfile
│   └── app/(Nuxt.jsのひな型)
└── build_image.yml
└── create_container.yml

DockerFile作成

Nodeコンテナから持ってきたローカル上のNuxt.jsのひな型をコンテナへ配置してNuxt.jsのイメージが作成されるDockerfileを作成します。

Dockerfile
FROM node:latest
ENV NUXT_HOST=0.0.0.0
ENV NUXT_TELEMETRY_DISABLED=1
WORKDIR /app

COPY ./app/package.json ./app/yarn.lock ./
RUN yarn install

COPY ./app .

CMD ["yarn", "run", "dev"]

Playbook作成

次に作成したDockerfileをビルドするためのPlaybookを作成します。

build_image.yml
- name: Build Nuxt.js image
  hosts: localhost
  tasks:
  - name: build nuxt.js
    docker_image:
      build:
        path: ./dockerFile
      name: yuta28/ansible-test
      tag: Nuxtimage
      push: yes #イメージビルド後自動的にDockerHubへpushする
      source: build
    register: build_result

  - debug: var=build_result

docker_image内のbuildオプションでDockerfileのカレントディレクトリを指定します。
nameオプションは出来上がったDockerイメージの名前を付けています。
sourceオプションでbuildを指定することで、DockerFileからイメージをビルドするようにします。
最後にイメージを基にコンテナを起動するためのPlaybook、create_container.ymlを作成します。

create_container.yml
- name: Create and run container
  hosts: localhost
  tasks:
  - name: Nuxt container
    docker_container:
      name: NuxtContaier
      image: yuta28/ansible-test:Nuxtimage
      state: started
      ports:
        - "127.0.0.1:8080:3000" #コンテナ内のポート3000にNuxt.jsが構築されるのでローカルのポート8080から参照できるようにマッピング
      tty: true
      detach: true
    register: contaier_result

  - debug: var=contaier_result

Ansible実行

準備が完了しましたので、Ansibleを実行します。

$ ansible-playbook build_image.yml 
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [Build Nuxt.js image] ***************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************
ok: [localhost]

TASK [build nuxt.js] *********************************************************************************************************************************************************************************
[WARNING]: The default for build.pull is currently 'yes', but will be changed to 'no' in Ansible 2.12. Please set build.pull explicitly to the value you need.
changed: [localhost]

TASK [debug] *****************************************************************************************************************************************************************************************
ok: [localhost] => {
    "build_result": {
        "actions": [
            "Built image yuta28/ansible-test:Nuxtimage from ./dockerFile",
            "Pushed image yuta28/ansible-test to docker.io/yuta28/ansible-test:Nuxtimage"
        ],
        "changed": true,
        "failed": false,
        "image": {
            "Architecture": "amd64",
            "Author": "",
            "Comment": "",
            "Config": {
                "AttachStderr": false,
                "AttachStdin": false,
                "AttachStdout": false,
                "Cmd": [
                    "yarn",
                    "run",
                    "dev"
                ],
                "Domainname": "",
                "Entrypoint": [
                    "docker-entrypoint.sh"
                ],
                "Env": [
                    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                    "NODE_VERSION=14.8.0",
                    "YARN_VERSION=1.22.4",
                    "NUXT_HOST=0.0.0.0",
                    "NUXT_TELEMETRY_DISABLED=1"
                ],
                "Hostname": "",
                "Image": "sha256:fe9a213a2bb59321425eb62ddcb54c9388e673b3f588ed4108b0b0c0c8ba78bb",
                "Labels": null,
                "OnBuild": null,
                "OpenStdin": false,
                "StdinOnce": false,
                "Tty": false,
                "User": "",
                "Volumes": null,
                "WorkingDir": "/app"
            },
            "Container": "50e3089197639ba613f1ae439d90142668cc5f0c360967fe8099c0093987695e",
            "ContainerConfig": {
                "AttachStderr": false,
                "AttachStdin": false,
                "AttachStdout": false,
                "Cmd": [
                    "/bin/sh",
                    "-c",
                    "#(nop) ",
                    "CMD [\"yarn\" \"run\" \"dev\"]"
                ],
                "Domainname": "",
                "Entrypoint": [
                    "docker-entrypoint.sh"
                ],
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜中略〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
            },
            "Size": 1430378578,
            "VirtualSize": 1430378578,
            "push_status": null
        },
        "warnings": [
            "The default for build.pull is currently 'yes', but will be changed to 'no' in Ansible 2.12. Please set build.pull explicitly to the value you need."
        ]
    }
}

PLAY RECAP *******************************************************************************************************************************************************************************************
localhost                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0      
$ ansible-playbook create_container.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [Create and run container] *********************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************
ok: [localhost]

TASK [Nuxt container] ********************************************************************************************************************************************************************************
changed: [localhost]

TASK [debug] *****************************************************************************************************************************************************************************************
ok: [localhost] => {
    "contaier_result": {
        "ansible_facts": {
            "docker_container": {
                "AppArmorProfile": "docker-default",
                "Args": [
                    "yarn",
                    "run",
                    "dev"
                ],
                "Config": {
                    "AttachStderr": false,
                    "AttachStdin": false,
                    "AttachStdout": false,
                    "Cmd": [
                        "yarn",
                        "run",
                        "dev"
                    ],
                    "Domainname": "",
                    "Entrypoint": [
                        "docker-entrypoint.sh"
                    ],
                    "Env": [
                        "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                        "NODE_VERSION=14.8.0",
                        "YARN_VERSION=1.22.4",
                        "NUXT_HOST=0.0.0.0",
                        "NUXT_TELEMETRY_DISABLED=1"
                    ],
                    "ExposedPorts": {
                        "3000/tcp": {}
                    },
                    "Hostname": "f5597614f5d5",
                    "Image": "yuta28/ansible-test:Nuxtimage",
                    "Labels": {},
                    "OnBuild": null,
                    "OpenStdin": false,
                    "StdinOnce": false,
                    "Tty": true,
                    "User": "",
                    "Volumes": null,
                    "WorkingDir": "/app"
                },
                "Created": "2020-08-16T07:05:36.169002938Z",
                "Driver": "overlay2",
                "ExecIDs": null,
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜中略〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜        },
        "failed": false
    }
}

PLAY RECAP *******************************************************************************************************************************************************************************************
localhost                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

failedになることなくAnsibleが最後まで実行されましたので、コンテナが起動しているか確認し、http://127.0.01:8080からNuxt.jsのサンプルページにアクセスできるか試してみます。

$ docker ps
CONTAINER ID        IMAGE                           COMMAND                  CREATED             STATUS              PORTS                      NAMES
f5597614f5d5        yuta28/ansible-test:Nuxtimage   "docker-entrypoint.s…"   37 minutes ago      Up 37 minutes       127.0.0.1:8080->3000/tcp   NuxtContaier


ちゃんとサンプルページにアクセスできることが確認できました。

Docker Hubへのpush

build_image.ymlで自動pushされるように設定しましたので、ご覧のように私のDocker Hubへイメージがpushされています。

反省点

Dockerのイメージからコンテナ起動まで一通りAnsibleで実行できました。
ただ、nuxt-appの仕様上コマンド実行後に対話形式で設定する必要があり、この部分をどうしてもコンテナ内へ入って手動で入力するしかできなかったことが心残りです。
expectというコマンドを使えば、パスワード変更のような直接入力する形式の対話形式でしたら設定できました。ただし、例えばこの中から好みのフレームワークを選択してくださいという選択形式の対話形式ですと選んだ項目が反映されず、一番上の選択肢が回答として反映されました。
しかも、ベースとなるNodeイメージにはexpectコマンドが入っておらず、yumコマンドでもインストールできなかったので、必要なソースファイルを用意してmakeコマンドで入れる必要があります。この辺の設定をDockerfileで最初から設定するのが大変な手間ですので、自動化するか対話形式のままにするかは各自次第なところだと思います。(もっと簡単な方法ありましたらぜひとも教えてください)

追記

コメントにてアドバイス頂きました。
nodeコンテナはDebian OSでしたのでAPTパッケージでインストールするようです。
ただ選択式はexpectコマンドでも選択できませんでした。
対話形式ではなく設定ファイルをコピーする方法でできないか調査してみます。

次回予告

次回ですが、モジュールを使うのではなく、Ansible-benderを用いてコンテナ管理できるように挑戦してみます。

LT資料

LTのスライド資料です。

GitHubで編集を提案

Discussion

坦々狸坦々狸

expectはyumじゃなくてaptで入りますよ😆

$ docker run -it --rm node bash
root@c939396c0f36:/# head -n1 /etc/os-release
PRETTY_NAME="Debian GNU/Linux 9 (stretch)"
root@c939396c0f36:/# apt-get update 2>&1 > /dev/null
root@c939396c0f36:/# apt-get install -y expect > /dev/null
debconf: delaying package configuration, since apt-utils is not installed
root@c939396c0f36:/# expect -v
expect version 5.45

yumはRedhat系になるのでnodeイメージのベースに使われてるDebian系では使えないです。

ユータユータ

返事が遅くなり申し訳ありません。
教えてくださりありがとうございます。

インストールできまして私のやりたいことができそうです。
助かりました。