CLOVER🍀

That was when it all began.

Jib Maven Pluginで、Dockerコンテナイメージを作る

これは、なにをしたくて書いたもの?

以前、Jib Coreを使ってDockerイメージを作ってみました。

Jib Coreで、Dockerコンテナイメージを作ってみる - CLOVER🍀

これでJibの基本的なところはわかった…ことにして、今度はJib Maven Pluginを使ってDockerイメージを作ってみたいと思います。

Jib Maven Plugin

文字通り、Jibの機能が使えるMaven Pluginで、DockerまたはOCIイメージを作ることができます。

https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin

作成したイメージは、コンテナレジストリにアップロードしたり、Dockerデーモンに送ったり、tarファイルにしたりできます。

それでは、使っていってみましょう。

環境

今回の環境は、こちらです。

$ java --version
openjdk 11.0.6 2020-01-14
OpenJDK Runtime Environment (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1)
OpenJDK 64-Bit Server VM (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.6, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "4.18.0-25-generic", arch: "amd64", family: "unix"

また、Jib Maven Pluginは2.1.0を使用します。

サンプルアプリケーション

先に、お題となるサンプルアプリケーションが必要ですね。

こちらで作ったものを流用します。

Jib Coreで、Dockerコンテナイメージを作ってみる - CLOVER🍀

pom。RESTEasyは、最新版にしておきました。
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>jib-simple-jaxrs-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jdk-http</artifactId>
            <version>4.5.3.Final</version>
        </dependency>
    </dependencies>
</project>

起動クラス+JAX-RSリソースクラス。
src/main/java/org/littlewings/jaxrs/Server.java

package org.littlewings.jaxrs;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import com.sun.net.httpserver.HttpServer;
import org.jboss.logging.Logger;
import org.jboss.resteasy.plugins.server.sun.http.HttpContextBuilder;

public class Server {
    public static void main(String... args) throws IOException {
        Logger logger = Logger.getLogger(Server.class);

        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 10);

        try {
            HttpContextBuilder builder = new HttpContextBuilder();
            builder.getDeployment().getActualResourceClasses().add(HelloResource.class);

            builder.bind(server);

            server.start();

            logger.info("server start.");

            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(1L);
                } catch (InterruptedException e) {
                    // ignore
                }
            }
        } finally {
            server.stop(0);
            logger.info("server stop.");
        }
    }

    @Path("hello")
    public static class HelloResource {
        @GET
        @Produces(MediaType.TEXT_PLAIN)
        public String message(@QueryParam("value") String value) {
            return "Hello " + Optional.ofNullable(value).orElse("World") + "!!";
        }
    }
}

こちらを、Jibを使ってDockerイメージにしていきましょう。

Jib Maven Pluginを組み込む

まずは、なにも考えずにプラグインを組み込んでみます。

    <build>
        <plugins>
            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                    <to>
                        <image>kazuhira/jib-simple-jaxrs-server</image>
                    </to>
                </configuration>
            </plugin>
        </plugins>
    </build>

「jib:dockerBuild」でDockerイメージを作成しましょう。

$ mvn compile jib:dockerBuild

どうやら、「gcr.io/distroless/java:11」をベースにしてイメージを作ったみたいです。

[INFO] Containerizing application to Docker daemon as kazuhira/jib-simple-jaxrs-server...
[WARNING] Base image 'gcr.io/distroless/java:11' does not use a specific image digest - build may not be reproducible
[INFO] Using base image with digest: sha256:c94feda039172152495b5cd60a350a03162fce4f8986b560ea555de4d276ce19
[INFO] 
[INFO] Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, org.littlewings.jaxrs.Server]
[INFO] 
[INFO] Built image to Docker daemon as kazuhira/jib-simple-jaxrs-server
[INFO] Executing tasks:
[INFO] [==============================] 100.0% complete
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  8.328 s
[INFO] Finished at: 2020-04-05T19:43:05+09:00
[INFO] ------------------------------------------------------------------------

確認。50年前のタイムスタンプのDockerイメージができました。

$ docker image ls | grep simple
kazuhira/jib-simple-jaxrs-server   latest              cf8664e3692c        50 years ago        199MB

Dockerイメージの方も確認してみましょう。

$ docker container run -it --rm -p 8080:8080 kazuhira/jib-simple-jaxrs-server:latest 
Apr 05, 2020 10:50:26 AM org.littlewings.jaxrs.Server main
INFO: server start.

OKですね。

$ curl localhost:8080/hello
Hello World!!

では、少しずつ設定を変えていってみましょう。Jib Coreの時に習う感じします。

あと、Exampleも参考にするとよいでしょう。

Example

タグを指定する

これは、単純にイメージ名の隣に「:」で区切ってタグを指定すればOKです。

            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                    <to>
                        <image>kazuhira/jib-simple-jaxrs-server:0.0.1</image>
                    </to>
                </configuration>
            </plugin>

ビルド後。

$ docker image ls | grep simple
kazuhira/jib-simple-jaxrs-server   0.0.1               cf8664e3692c        50 years ago        199MB
ベースイメージを変更する

最初はデフォルトで使われるベースイメージ(Distroless)を使いましたが、ベースイメージを変更してみましょう。

            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                    <from>
                        <image>adoptopenjdk:11-jre-hotspot</image>
                    </from>
                    <to>
                        <image>kazuhira/jib-simple-jaxrs-server:0.0.1</image>
                    </to>
                </configuration>
            </plugin>

from、imageで指定します。今回はAdoptOpenJDKにしました。

Extended Usage

ところで、README.mdを見ていると、なにも指定がない場合のベースイメージは「gcr.io/distroless/java」が使われてると書かれて
いた割には最初にダウンロードされたのは「gcr.io/distroless/java:11」でした。

Setting the Base Image

どうなっているんでしょう?

どうやら、Javaのバージョンを見てベースイメージを決めているようですね。

https://github.com/GoogleContainerTools/jib/blob/v2.1.0-maven/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java#L577-L592

Java VMに渡すオプションを設定する

Jib Maven Pluginを使った場合は、ENTRYPOINTを直接指定することは少ないと思います。

代わりに、jvmFlagやmainClassなどを使用します。

entrypointという設定項目自体はあるのですが、jvmFlagsなどを設定しても無視されるそうな。

The command to start the container with (similar to Docker's ENTRYPOINT instruction). If set, then jvmFlags and mainClass are ignored. You may also set INHERIT to indicate that the entrypoint and args should be inherited from the base image.*

では、jvmFlagを使ってみましょう。「-Xmx」を指定してみます。

            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                    <from>
                        <image>adoptopenjdk:11-jre-hotspot</image>
                    </from>
                    <to>
                        <image>kazuhira/jib-simple-jaxrs-server:0.0.1</image>
                    </to>
                    <container>
                        <jvmFlags>
                          <jvmFlag>-Xmx256m</jvmFlag>
                        </jvmFlags>
                    </container>
                </configuration>
            </plugin>

指定する時は、container配下にするようですね。

起動後にコンテナ内に入って確認してみると、Java VMへの引数が追加されていることが確認できますね。

$ docker container exec -it [コンテナ名] bash
root@5de3f4e084f2:/# ps -ef | grep java
root         1     0 27 11:20 pts/0    00:00:01 java -Xmx256m -cp /app/resources:/app/classes:/app/libs/* org.littlewings.jaxrs.Server
root        36    26  0 11:20 pts/1    00:00:00 grep --color=auto java

https://github.com/GoogleContainerTools/jib/blob/v2.1.0-maven/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java#L558-L564

mainClassについては自動で検出するようですが、自分で指定することも可能です。

https://github.com/GoogleContainerTools/jib/blob/v2.1.0-maven/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java#L554-L556

あと、プログラム自体の起動引数についてはargsで指定します。

ポートをEXPOSEする

ポートをEXPOSEするには、ports、portで指定します。

            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                    <from>
                        <image>adoptopenjdk:11-jre-hotspot</image>
                    </from>
                    <to>
                        <image>kazuhira/jib-simple-jaxrs-server:0.0.1</image>
                    </to>
                    <container>
                        <jvmFlags>
                          <jvmFlag>-Xmx256m</jvmFlag>
                        </jvmFlags>
                        <ports>
                            <port>8080</port>
                        </ports>
                    </container>
                </configuration>
            </plugin>

確認。

$ docker container ps
CONTAINER ID        IMAGE                                    COMMAND                  CREATED             STATUS              PORTS               NAMES
bd8aa0836e4d        kazuhira/jib-simple-jaxrs-server:0.0.1   "java -Xmx256m -cp /…"   5 seconds ago       Up 2 seconds        8080/tcp            busy_carson
イメージの作成時刻を変更する

作成時刻が50年前になっているのを変更するには、creationTimeを使用します。

            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                    <from>
                        <image>adoptopenjdk:11-jre-hotspot</image>
                    </from>
                    <to>
                        <image>kazuhira/jib-simple-jaxrs-server:0.0.1</image>
                    </to>
                    <container>
                        <jvmFlags>
                          <jvmFlag>-Xmx256m</jvmFlag>
                        </jvmFlags>
                        <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
                        <ports>
                            <port>8080</port>
                        </ports>
                    </container>
                </configuration>
            </plugin>

「USE_CURRENT_TIMESTAMP」を使用すると、現在時刻でイメージが作成されます。

$ docker image ls | grep simple
kazuhira/jib-simple-jaxrs-server   0.0.1               36ac8b04db85        2 minutes ago       228MB

ただし、現在時刻を使ってしまうと再現性を失うことになる点に注意してください。

Why is my image created 48+ years ago?

また、ISO 8601フォーマットで指定することもできます。

            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                    <from>
                        <image>adoptopenjdk:11-jre-hotspot</image>
                    </from>
                    <to>
                        <image>kazuhira/jib-simple-jaxrs-server:0.0.1</image>
                    </to>
                    <container>
                        <jvmFlags>
                          <jvmFlag>-Xmx256m</jvmFlag>
                        </jvmFlags>
                        <creationTime>2020-01-15T10:30:45+09:00</creationTime>
                        <ports>
                            <port>8080</port>
                        </ports>
                    </container>
                </configuration>
            </plugin>

実体としては、DateTimeFormatter.ISO_DATE_TIMEでパースできる必要があります。

結果。

$ docker image ls | grep simple
kazuhira/jib-simple-jaxrs-server   0.0.1               bf6479b69280        2 months ago        228MB
ビルドプロセスに組み込む

Mavenプラグインとして使っているのですし、package時にDockerイメージを作って欲しいものです。

以下を参考に、Mavenのビルドプロセスに組み込んでみましょう。

Bind to a lifecycle

Profile「docker」にして、通常のパッケージングとは別にしてみました。

    <profiles>
        <profile>
            <id>docker</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>com.google.cloud.tools</groupId>
                        <artifactId>jib-maven-plugin</artifactId>
                        <version>2.1.0</version>
                        <executions>
                            <execution>
                                <phase>package</phase>
                                <goals>
                                    <goal>dockerBuild</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <from>
                                <image>adoptopenjdk:11-jre-hotspot</image>
                            </from>
                            <to>
                                <image>kazuhira/jib-simple-jaxrs-server:0.0.1</image>
                            </to>
                            <container>
                                <jvmFlags>
                                    <jvmFlag>-Xmx256m</jvmFlag>
                                </jvmFlags>
                                <creationTime>2020-01-15T10:30:45+09:00</creationTime>
                                <ports>
                                    <port>8080</port>
                                </ports>
                            </container>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

「package」ゴール実行時に、「dockerBuild」するようにしてあります。

                        <executions>
                            <execution>
                                <phase>package</phase>
                                <goals>
                                    <goal>dockerBuild</goal>
                                </goals>
                            </execution>
                        </executions>

あとは、Profileを指定してビルドしましょう。

$ mvn -Pdocker package

こんなところでしょうか。

まとめ

Jib Maven Pluginを使って、Dockerイメージを作ってみました。

その他、WARに対して適用できたりしますし、設定も他にたくさんあるのですが、慣れればREADME.mdを見つつ使えそうな気がしますね。

WAR Projects

今回は、こんなところで。

Molecule 3を試す

これは、なにをしたくて書いたもの?

Moleculeが3.0になっていたので、1度試しておこうかなと。

前に試した時は、2.22でした。

Ansible Roleを開発、テストするためのMoleculeを試す - CLOVER🍀

Changelogを見ると、だいぶ変わったみたいです。

Changelog / 3.0

特に大きな変更は、以下あたりです(Changelogの「MAJOR」から抜粋)。

  • Ansibleのバージョンサポート範囲がN/N-1(たとえば2.9と2.8)になる
  • Providerはインストール可能なドライバー、Pythonモジュールになった
  • コアから、Azure、EC2、DigitalOcean、GCE、HetznerCloud、Linode、LXD、OpenStack、Vagrantの各Providerが削除
  • goss verifierを削除
  • playbook.ymlという名前は非推奨になり、converge.ymlに
  • デフォルトのverifierがAnsibleになった
  • Testinfraはデフォルトでインストールされなくなった
  • Scenerio名が設定から削除された
  • Lintingの設定がリファクタリングされた
  • DockerfileのテンプレートがMolecureに組み込み可能になった

Migrating to molecule v3 checklist · Issue #2560 · ansible-community/molecule · GitHub

Providerがだいぶ削除されたり、Testinfraが入らなくなったりしたんですねぇ。

とりあえず、今回は2.22の時に試した内容を、3.0に読み替えてやってみようと思います。

Moleculeとは

少しだけ、振り返っておきます。

Ansible Molecule — Molecule 3.0.3.dev73+gcc0c7634 documentation

Moleculeは、Ansible Roleを作ったりテストするためのプロジェクトです。

Molecule project is designed to aid in the development and testing of Ansible roles.

複数のインスタンス、OS、仮想化プロバイダー、テストに関するサポートを提供するものです。

Molecule provides support for testing with multiple instances, operating systems and distributions, virtualization providers, test frameworks and testing scenarios.

環境

今回の環境は、こちらです。

$ python3 -V
Python 3.6.9


$ pip3 -V
pip 9.0.1 from /path/to/venv/lib/python3.6/site-packages (python 3.6)

インストール

まずは、Moleculeをインストールします。

Installation — Molecule 3.0.3.dev73+gcc0c7634 documentation

$ pip3 install molecule[lint]

インストールしたバージョン。

$ pip3 freeze | grep -E 'ansible|molecule'
ansible==2.9.6
ansible-lint==4.2.0
molecule==3.0.2

試してみる

Getting Startedを見つつ、Moleculeを試していきます。

Getting Started Guide — Molecule 3.0.3.dev73+gcc0c7634 documentation

お題としては、ApacheをインストールするRoleを作ることにしましょう。

Creating a new role

「molecule init」でRoleを作成。

$ molecule init role apache

できあがったファイル。

$ find apache -type f
apache/handlers/main.yml
apache/README.md
apache/vars/main.yml
apache/.travis.yml
apache/meta/main.yml
apache/defaults/main.yml
apache/tests/inventory
apache/tests/test.yml
apache/tasks/main.yml
apache/molecule/default/verify.yml
apache/molecule/default/converge.yml
apache/molecule/default/molecule.yml
apache/molecule/default/INSTALL.rst
apache/.yamllint

いくつか、見ていきましょう。

定義内容は、Configuration Guideを見つつ。

Configuration — Molecule 3.0.3.dev73+gcc0c7634 documentation

Moleculeの中心となる設定ファイルです。
apache/molecule/default/molecule.yml

---
dependency:
  name: galaxy
driver:
  name: docker
platforms:
  - name: instance
    image: docker.io/pycontribs/centos:7
    pre_build_image: true
provisioner:
  name: ansible
verifier:
  name: ansible

Changelogにもあったとおり、playbook.ymlはconverge.ymlになったんですね。
apache/molecule/default/converge.yml

---
- name: Converge
  hosts: all
  tasks:
    - name: "Include apache"
      include_role:
        name: "apache"

デフォルトのVerifierがAnsibleになったので、verify.ymlというファイルが生成されています。
apache/molecule/default/verify.yml

---
# This is an example playbook to execute Ansible tests.

- name: Verify
  hosts: all
  tasks:
  - name: Example assertion
    assert:
      that: true

assertモジュールで検証します、と。

assert – Asserts given expressions are true — Ansible Documentation

作成されたRoleのディレクトリに移動。

$ cd apache

DockerイメージはpysのCentOSでしたが、今回はUbuntu Linuxを使うことにしましょう。

GitHub - pycontribs/pys: Repository used to pre-build python enabled images for popular operating systems

pys / ubuntu

pysというのは、メジャーなOSでPythonを有効化してビルドされたDockerイメージです。

こんな感じに変更。

driver:
  name: docker
platforms:
  - name: instance
    image: docker.io/pycontribs/ubuntu:latest
    pre_build_image: true
    dns_servers:
      - 8.8.8.8

「dns_servers」を指定しているのは、aptでApacheをインストールする時に名前解決ができなくてエラーになったからです…。

Err:1 http://archive.ubuntu.com/ubuntu bionic InRelease                  
  Temporary failure resolving 'archive.ubuntu.com'
Err:2 http://security.ubuntu.com/ubuntu bionic-security InRelease        
  Temporary failure resolving 'security.ubuntu.com'
Err:3 http://archive.ubuntu.com/ubuntu bionic-updates InRelease          
  Temporary failure resolving 'archive.ubuntu.com'
Err:4 http://archive.ubuntu.com/ubuntu bionic-backports InRelease
  Temporary failure resolving 'archive.ubuntu.com'

では、プロビジョニングを行うインスタンスを作成します。

Run test sequence commands

「molecule create」でインスタンスの作成…ですが、エラーになります。

$ molecule create
--> Test matrix
    
└── default
    ├── dependency
    ├── create
    └── prepare
    
--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'create'
--> Sanity checks: 'docker'
ERROR: Missing Docker driver dependency. Please install via 'molecule[docker]' or refer to your INSTALL.rst driver documentation file

「Dockerドライバがない」と言われているので、インストール。

$ pip3 install molecule[docker]

バージョン。

$ pip3 freeze | grep -E 'ansible|molecule|docker'
ansible==2.9.6
ansible-lint==4.2.0
docker==4.2.0
molecule==3.0.2

再度、「molecule create」。

$ molecule create

今度は成功します。

$ molecule create
--> Test matrix
    
└── default
    ├── dependency
    ├── create
    └── prepare
    
--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'create'
--> Sanity checks: 'docker'
    
    PLAY [Create] ******************************************************************
    
    TASK [Log into a Docker registry] **********************************************
    skipping: [localhost] => (item=None) 
    
    TASK [Check presence of custom Dockerfiles] ************************************
    ok: [localhost] => (item=None)
    ok: [localhost]
    
    TASK [Create Dockerfiles from image names] *************************************
    skipping: [localhost] => (item=None) 
    
    TASK [Discover local Docker images] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost]
    
    TASK [Build an Ansible compatible image (new)] *********************************
    skipping: [localhost] => (item=molecule_local/docker.io/pycontribs/ubuntu:latest) 
    
    TASK [Create docker network(s)] ************************************************
    
    TASK [Determine the CMD directives] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost]
    
    TASK [Create molecule instance(s)] *********************************************
    changed: [localhost] => (item=instance)
    
    TASK [Wait for instance(s) creation to complete] *******************************
    FAILED - RETRYING: Wait for instance(s) creation to complete (300 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (299 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (298 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (297 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (296 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (295 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (294 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (293 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (292 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (291 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (290 retries left).
    FAILED - RETRYING: Wait for instance(s) creation to complete (289 retries left).
    changed: [localhost] => (item=None)
    changed: [localhost]
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=5    changed=2    unreachable=0    failed=0    skipped=4    rescued=0    ignored=0
    
--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.

インスタンスの一覧を確認。

$ molecule list
Instance Name    Driver Name    Provisioner Name    Scenario Name    Created    Converged
---------------  -------------  ------------------  ---------------  ---------  -----------
instance         docker         ansible             default          true       false

当然のことながら、dockerコマンドでも確認できます。

$ docker container ps
CONTAINER ID        IMAGE                      COMMAND                  CREATED             STATUS              PORTS               NAMES
ba6732094f07        pycontribs/ubuntu:latest   "bash -c 'while true…"   56 seconds ago      Up 40 seconds                           instance

起動中のインスタンスにログインするには、「molecule login」で。

$ molecule login
root@instance:/# 

Ubuntu Linux 18.04 LTSですね。

# uname -a
Linux instance 4.18.0-25-generic #26~18.04.1-Ubuntu SMP Thu Jun 27 07:28:31 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux


# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:   bionic

インスタンスの破棄。

$ molecule destroy

破棄の様子。

$ molecule destroy
--> Test matrix
    
└── default
    ├── dependency
    ├── cleanup
    └── destroy
    
--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'cleanup'
Skipping, cleanup playbook not configured.
--> Scenario: 'default'
--> Action: 'destroy'
--> Sanity checks: 'docker'
    
    PLAY [Destroy] *****************************************************************
    
    TASK [Destroy molecule instance(s)] ********************************************
    changed: [localhost] => (item=instance)
    
    TASK [Wait for instance(s) deletion to complete] *******************************
    FAILED - RETRYING: Wait for instance(s) deletion to complete (300 retries left).
    changed: [localhost] => (item=None)
    changed: [localhost]
    
    TASK [Delete docker network(s)] ************************************************
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=2    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
    
--> Pruning extra files from scenario ephemeral directory

では、ApacheをインストールするようにAnsible Roleを作成しましょう。tasks/main.ymlに、以下のように記述。
tasks/main.yml

---
- name: install apache2
  apt:
    name: apache2
    state: present
    update_cache: yes

「molecule converge」で、インスタンスの作成とAnsible Playbookの実行を行います。

$ molecule converge

実行されるのは、coverage.ymlの内容ですね。
molecule/default/converge.yml

---
- name: Converge
  hosts: all
  tasks:
    - name: "Include apache"
      include_role:
        name: "apache"

Playbookが実行されている様子。

--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.
--> Scenario: 'default'
--> Action: 'converge'
    
    PLAY [Converge] ****************************************************************
    
    TASK [Gathering Facts] *********************************************************
    ok: [instance]
    
    TASK [Include apache] **********************************************************
    
    TASK [apache : install apache2] ************************************************
    changed: [instance]
    
    PLAY RECAP *********************************************************************
    instance                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

次は、テスト(Verify)を行ってみましょう。

Verifier

ところで、MoleculeのデフォルトのVerifierはTestinfraからAnsibleに変わったようですが、それはどうしてかちょっと見てみました。

Make Ansible default verifier · Issue #2013 · ansible-community/molecule · GitHub

使うテクノロジーを、YAMLに統一したかったみたいですね。でも、AnsibleのassertはTestinfraと比べるとどうなのという話もあり…。

デフォルトのverify.ymlを、もう1度見てみます。
molecule/default/verify.yml

---
# This is an example playbook to execute Ansible tests.

- name: Verify
  hosts: all
  tasks:
  - name: Example assertion
    assert:
      that: true

ここからどう変えていいかちょっとわからなくなったので、今回はTestinfraに切り替えることにしました。

Testinfraとpytestをインストールします。

$ pip3 install testinfra pytest

バージョン。

 pip3 freeze | grep -E 'testinfra|pytest'
pytest==5.4.1
testinfra==5.0.0

molecule.ymlのVerifierの設定を、AnsibleからTestinfraへ切り替えます。

verifier:
#  name: ansible
  name: testinfra

テストコードを置くディレクトリを作成。

$ mkdir molecule/default/tests

作成したテストコード。
molecule/default/tests/test_apache.py

import os

import testinfra.utils.ansible_runner

testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
   os.environ['MOLECULE_INVENTORY_FILE']
).get_hosts('all')

def test_apache_is_installed(host):
    apache2 = host.package('apache2')
    assert apache2.is_installed

インスタンスが起動している状態であれば、「molecule verify」でテストを行うことができます。

$ molecule verify

こんな感じで。

$ molecule verify
--> Test matrix
    
└── default
    └── verify
    
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in /path/to/apache/molecule/default/tests/...
    ============================= test session starts ==============================
    platform linux -- Python 3.6.9, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
    rootdir: /path/to/apache/molecule/default
    plugins: testinfra-5.0.0
collected 1 item                                                               
    
    tests/test_default.py .                                                  [100%]
    
    ============================== 1 passed in 5.26s ===============================
Verifier completed successfully.

終わったら、インスタンスを破棄しておきましょう。

$ molecule destroy

ここまでの、インスタンスの作成、Playbookの実行、テスト、インスタンスの破棄まで一気に行う場合は、「molecule test」を
実行します。

Run a full test sequence

$ molecule test

こんな感じで、Moleculeのイントロ的な内容を、ざっくり確認してみました。

Molecule 2から3で、テストまわりが変わっていたり、ドライバがなくなっていたりでなかなかビックリしました。