【IaCで作るselenium環境】05_Androidコンテナを構築する(Ansibleプロビジョニング編)

――――――――――――――――――――――――――――――――――
【2021-01-20 更新】
VideoRecorderはSelenoidが勝手に起動してセッション張るので、docker-composeに書かなくていいですよ、とご指摘を頂きました。
ありがとうございました。

これによりplaybookに追記を行いました。
――――――――――――――――――――――――――――――――――

直近の関連エントリ

――――――――――――――――――――――――――――――――――

こんにちは。SHIFTのスクラムマスター・テスト自動化エンジニアの石丸です。

前回Azureの登録を行いました。
1ヶ月の無償期間が切れる前にこのシリーズが終わるように、ここからスピードアップしていきます。

今回は実際にたてたVMにAndroidコンテナの準備を施していきます。



■今回のテーマ

今回のテーマは「AnsibleでAzureVMへプロビジョニング」です。
前回構築したAzureの仮想マシン環境にAnsibleでAndroid環境を整えます。

必要なソフトウェアのインストールや設定を自動で行うツールとして、今回は弊社エントリでもちょくちょくテーマに上がっているAnsibleを使用します。


■前提

・DockerDesktop Version 3.0.0(現時点での最新)
・windows環境での構築
【重要】第4回でDNS名設定、SSH疎通ができていること


■前回のおさらい

前回の最後はこんな感じでした。

今回の構成_04

第3回までに準備した環境の外側に、ポツンと空っぽのVMが立っている状態です。

これからVMに対してAnsibleで設定を加えていきます。

■Ansibleって?

通常新しくPCを用意した場合、必要なソフトウェアのインストールやセットアップ、あとPC自体の環境設定等々かなりいろいろな準備をしないとなりません。

これがPC1台ならまだよいのですが、数台、数十台に同様の設定を施すとしたらいかがでしょうか?
また何度も設定しなおす場合はどうでしょう?
私のようなものぐさは想像するだけで恐ろしくなります。

そこで登場するのが構成管理ツールです。
設定ファイルを用意することにより、たとえ何台であっても一気に、何度でも、しかも自動でセットアップすることが可能です(構成管理の自動化)。

Ansibleは構成管理ツールとしてはかなりメジャーなプロダクトです。

無料OSS版のダウンロード方法は以下をご覧ください。


■Ansible導入のポイント

さて、実はAnsibleはwindowsにはインストールできません
(windowsはコントロールノードにできない)
※macは可、brewコマンドであっさりインストールできる

ゆえにちょっと工夫が必要になります。
恐らくWSL2だとあっさり入ると思いますが(未検証)、IaCがテーマなのでWindows上でAnsibleコンテナを準備しようと思います。

==================================================
※注意
Docker Hubに公式版Ansibleイメージがありますが、2020年12月時点で約3年間ホスティングされていない模様です。使うのは避けましょう。
==================================================


■フォルダ構成

今回はちょっとファイルの構成がややこしいです。ファイルの構成を整理します。

・ローカルPC1
C:
|ーUsers
| |ー<ユーザフォルダ>
|   |ー.ssh
|     |ーselenoidvm_key.pem(第4回でDLしたキーペア)

|ーtmp
  |ーansible(場所は任意)
    |ーDockerfile
    |ーhosts
    |ーplaybook_node.yml
    |ーansible.cfg
    |
    |ーselenoid(第1回で作成したフォルダのコピー)
      |ーdocker-compose.yml
      |ーconfig
        |ーbrowsers.json

今回新規に準備したansibleフォルダ配下に第1回で作成したselenoidフォルダをコピーしておきます。


■Ansibleコンテナの準備

では実際に準備を進めていきましょう。
手順が大きく7つあります。

手順1:hostsファイルの準備
hostsファイルは配布先マシンの情報です。
今回はAzureVMの情報を記述します。 

[selenoid]
myselenoidvm.eastus.cloudapp.azure.com

[selenoid:vars]
ansible_port=22
ansible_user=azureuser


手順2:playbookの準備
Ansibleの本体ともいうべきファイルで、マシンに施す設定の中身が書かれています。

ubuntuへのdocker-ceインストール手順は公式ページがあるので、こちらをもとにplaybookを組んでゆきます。

---
# targetセクション
- name: selenoid_VM
  hosts: all
  become: yes
  become_method: sudo

# varsセクション
# 引数として設定値があれば定義しておく
# vars:
#
# tasksオプション
# 構成管理の本体
 tasks:
   - name: Upgrade # apt-get updateはこれ
     apt:
       name: "*"
       state: latest
   - name: 基本インストール # apt-get installはこれ
     apt:
       name:
         - apt-transport-https
         - ca-certificates
         - curl
         - gnupg-agent
         - software-properties-common
       state: latest
       force: yes
   - name: タイムゾーンを日本時間に変更 # timedatectlはこれ
     timezone:
       name: Asia/Tokyo
   - name: docker取得 # apt-key addは
     apt_key:
       url: https://download.docker.com/linux/ubuntu/gpg
       state: present
   - name: リポジトリの追加 # add-apt-repository debはこんな感じ
     apt_repository:
       repo: deb [arch=amd64] https://download.docker.com/linux/{{ansible_distribution|lower}} {{ansible_distribution_release}} stable
   - name: dockerインストール # apt-get install 再び
     apt:
       name:
         - docker-ce=5:20.10.0~3-0~ubuntu-bionic
         - docker-ce-cli=5:20.10.0~3-0~ubuntu-bionic
         - containerd.io
   - name: pipインストール # apt-get install再び
     apt:
       name:
         - python3-pip
       force: yes
       state: latest
   - name: dockerのインストール Ansible用
     pip:
       state: latest
       name:
         - docker
         - docker-compose
   - name: selenoidのコピー
     copy:
       src: ./selenoid
       dest: /usr/local/
       mode: '0644'
   - name: chromeブラウザのpull_84
     docker_image:
       name: selenoid/vnc_chrome:84.0
       source: pull
   - name: モバイルchromeブラウザのpull_75
     docker_image:
       name: selenoid/chrome-mobile:75.0
       source: pull
       state: absent
   - name: androidのpull_8.1
     docker_image:
       name: selenoid/android:8.1
       source: pull
   - name: videorecorderのpull
     docker_image:
       name: selenoid/video-recorder:latest-release
       source: pull       
   - name: docker-compose down
     docker_compose:
       project_src: /usr/local/selenoid/
       state: absent
   - name: docker-compose up
     docker_compose:
       project_src: /usr/local/selenoid/
       build: yes

task内のnameを見て頂ければ、何をやっているのかだいたい把握できるかなと思います。

今回VMをubuntuで建てているので、使用するコマンドは主にaptです。
AnsibleではLinuxコマンドがAnsible用に定義されていて、その書式に合わせて記述をしていきます。
書式に関しては以下をご確認ください。

またこのあたりの概念に関して、株式会社リアルグローブ・オートメーティッド(RGA)の水谷さんのエントリがlatestバージョンの2.10に沿って書かれており大変参考にさせて頂きました。感謝。

(2021-01-20修正)
Selenoidではブラウザコンテナと同様にVideoRecorderのイメージも事前にpullしておく必要があります。


手順3:ansible.cfgの準備
Ansible自体の設定ファイルです。

GitHubにてexampleが公開されています。
こちらを参考にしつつ、今回は以下の部分を記述しておきます。
https://github.com/ansible/ansible/blob/devel/examples/ansible.cfg

[defaults]
# ホスト情報を集める際のタイムアウト設定
gather_timeout = 20

# pythonのバージョン指定(超重要!)
interpreter_python=/usr/bin/python3

# SSH接続タイムアウト設定
timeout = 120

[ssh_connection]
# StrictHostKeyChecking無効
ssh_args =  -o StrictHostKeyChecking=no


手順4:selenoidフォルダのコピー
AnsibleがVMに配置するselenoidを構築します。
ローカルのselenoidフォルダをansibleフォルダにコピーしたうえで、以下のファイルを修正してください。


手順5:browsers.jsonの変更
Androidコンテナ(Android、chrome-mobile)の記述を追加しておきます。

{
   "chrome": {
       "default": "83.0",
       "versions": {
           "84.0": {
               "image": "selenoid/vnc_chrome:84.0",
               "port": "4444",
               "tmpfs": {"/tmp": "size=512m", "/var": "size=128m"},
               "path": "/"
           },
           "83.0": {
               "image": "selenoid/vnc_chrome:83.0",
               "port": "4444",
               "tmpfs": {"/tmp": "size=512m", "/var": "size=128m"},
               "path": "/"
           },
           "mobile-75.0": {
               "image": "selenoid/chrome-mobile:75.0",
               "port": "4444",
               "tmpfs": {"/tmp": "size=512m", "/var": "size=128m"},
               "path": "/wd/hub"
           }
       }
   },
   "android": {
       "default": "8.1",
       "versions": {
           "8.1": {
               "image": "selenoid/android:8.1",
               "port": "4444",
               "tmpfs": {"/tmp": "size=512m", "/var": "size=128m"},
               "path": "/wd/hub"
           }
       }
   }
}


手順6:docker-compose.ymlの変更
Videoのパスとcommandの追記を行います。

特にcommandがキモです。
Androidコンテナは、内部でAVDとAppiumの展開を行っています。
なので今までのdocker-compose.ymlの設定だと構築完了前にタイムアウトしてしまいます。

 (略)
 …
 
 environment:
   - OVERRIDE_VIDEO_OUTPUT_DIR=/home/azureuser/docker/selenoid/video
 command: >
   -conf /etc/selenoid/browsers.json
   -video-output-dir /opt/selenoid/video
   -log-output-dir /opt/selenoid/logs
   -session-attempt-timeout 6m
   -service-startup-timeout 6m
   -mem 6g
   -cpu 2.0
 
 …
 (略)

これでセッション維持時間とサービスが立ち上がるまでの時間のタイムアウトを延長します。
また割り当てるメモリとcpuも、VMの上限めいっぱい設定します。


手順7:Dockerfileの準備
最後にAnsibleコンテナを構築するためのDockerfileです。

# syntax = docker/dockerfile:1.1-experimental
FROM alpine:3.12.1

RUN set -x \
   && apk add --no-cache \
     sudo \
     bash \
     wget \
     python3 \
     py3-pip \
     openssh-client
RUN apk add --no-cache ansible
RUN set -x \
   && mkdir -p /etc/ansible/selenoid \
   && mkdir -p -m 0700 /root/.ssh/ \
   && ssh-keyscan myselenoidvm.eastus.cloudapp.azure.com >> ~/.ssh/known_hosts

COPY hosts /etc/ansible
COPY playbook_node.yml /etc/ansible
COPY ansible.cfg /etc/ansible
COPY ./selenoid /etc/ansible/selenoid

WORKDIR /etc/ansible

RUN ssh-keygen -q  -N '' -t ed25519 -f  ~/.ssh/id_ed25519
RUN --mount=type=secret,id=ssh,target=/root/.ssh/selenoidvm_key.pem \
   cat ~/.ssh/id_ed25519.pub | ssh -i /root/.ssh/selenoidvm_key.pem  azureuser@myselenoidvm.eastus.cloudapp.azure.com 'cat >> .ssh/authorized_keys'
RUN ansible -i hosts myselenoidvm.eastus.cloudapp.azure.com -m ping

CMD ["ansible-playbook", "-i", "hosts", "playbook_node.yml" ]

ポイントはbuildkitの記述です。

AnsibleからVMにsshする公開鍵としてed25519で発行し、それをVMに転送する際にselenoidvm_key.pemを使用する必要があります。

selenoidvm_key.pemをCOPYコマンドで配布するとDockerの履歴に残ってしまうのでCOPYコマンドは使用できません。

そこで--mount=type=secretを使用して鍵情報をマウントし、直接コンテナに格納しなくてもsshできるようにします。

==================================================
※注意
このDockerfileの記述方法ですとhostnameを直接記述しているため、Ansibleの複数ドメインに対応できません。
ですので、あくまで今回の例に限った記述とご理解頂ければ幸いです。
==================================================


■Ansibleコンテナの実行

Dockerイメージの作成
Dockerfileからイメージをビルドします。
PowerShellを管理者権限で起動、Dockerfileの場所までcdしてbuildコマンドを実行します。

PS C:> cd c:\tmp\ansible
PS C:\tmp\ansible> docker image build -t test01/ansible:0.0.1 --secret --no-cache id=ssh,src=$HOME/.ssh/selenoidVM_key.pem .

正常に実行されると以下のように出力されます。

PS C:\tmp\ansible> docker image build -t test01/ansible:0.0.1 --secret id=ssh,src=$HOME/.ssh/selenoidvm_key.pem .
[+] Building 19.9s (20/20) FINISHED
=> [internal] load build definition from Dockerfile                                                                                                                     0.0s
=> => transferring dockerfile: 1.69kB                                                                                                                                   0.0s
=> [internal] load .dockerignore                                                                                                                                        0.0s
=> => transferring context: 2B                                                                                                                                          0.0s
=> resolve image config for docker.io/docker/dockerfile:1.1-experimental                                                                                                2.1s
=> CACHED docker-image://docker.io/docker/dockerfile:1.1-experimental@sha256:de85b2f3a3e8a2f7fe48e8e84a65f6fdd5cd5183afa6412fff9caa6871649c44                           0.0s
=> [internal] load build definition from Dockerfile                                                                                                                     0.0s
=> => transferring dockerfile: 1.69kB                                                                                                                                   0.0s
=> [internal] load metadata for docker.io/library/alpine:3.12.1                                                                                                         1.5s
=> [ 1/12] FROM docker.io/library/alpine:3.12.1@sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a                                                 0.0s
=> [internal] load build context                                                                                                                                        0.0s
=> => transferring context: 405B                                                                                                                                        0.0s
=> CACHED [ 2/12] RUN set -x     && apk add --no-cache       sudo       bash       wget       python3       py3-pip       openssh-client                                0.0s
=> CACHED [ 3/12] RUN apk add --no-cache ansible                                                                                                                        0.0s
=> [ 4/12] RUN set -x     && mkdir -p /etc/ansible/selenoid     && mkdir -p -m 0700 /root/.ssh/     && ssh-keyscan myselenoidvm.eastus.cloudapp.azure.com >> ~/.ssh     2.3s
=> [ 5/12] COPY hosts /etc/ansible                                                                                                                                      0.1s
=> [ 6/12] COPY playbook_node.yml /etc/ansible                                                                                                                          0.1s
=> [ 7/12] COPY ansible.cfg /etc/ansible                                                                                                                                0.1s
=> [ 8/12] COPY ./selenoid /etc/ansible/selenoid                                                                                                                        0.1s
=> [ 9/12] WORKDIR /etc/ansible                                                                                                                                         0.1s
=> [10/12] RUN ssh-keygen -q  -N '' -t ed25519 -f  ~/.ssh/id_ed25519                                                                                                    0.4s
=> [11/12] RUN --mount=type=secret,id=ssh,target=/root/.ssh/selenoidvm_key.pem     cat ~/.ssh/id_ed25519.pub | ssh -i /root/.ssh/selenoidvm_key.pem  azureuser@mysel    2.7s
=> [12/12] RUN ansible -i hosts myselenoidvm.eastus.cloudapp.azure.com -m ping                                                                                          9.6s
=> exporting to image                                                                                                                                                   0.3s
=> => exporting layers                                                                                                                                                  0.3s
=> => writing image sha256:156dabec583c14f32005337b902e6691e91c52caabeba8f0f9cf126b8c8de927                                                                             0.0s
=> => naming to docker.io/test01/ansible:0.0.1

test01/ansible:0.0.1 というイメージが出来ています。

PS C:\tmp\ansible> docker image ls
REPOSITORY              TAG              IMAGE ID       CREATED         SIZE
test01/ansible          0.0.1            156dabec583c   3 minutes ago   217MB

コンテナの実行
出来上がったイメージをもとにコンテナを実行します。

PS C:\tmp\ansible> docker container run -itd --name ansible test01/ansible:0.0.1
87fb010cd9e4fa6ab3e35fcbaec139ab58448bc8cad327364453eb8491ddb220

これでVMに対するプロビジョニングが走り出します。
「ansible」というコンテナが出来ましたのでログを確認します。

PS C:\tmp\ansible> docker container logs -f ansible

PLAY [selenoid_VM] *************************************************************
TASK [Gathering Facts] *********************************************************
ok: [myselenoidvm.eastus.cloudapp.azure.com]
TASK [Upgrade] *****************************************************************
ok: [myselenoidvm.eastus.cloudapp.azure.com]
TASK [基本インストール] ****************************************************************
ok: [myselenoidvm.eastus.cloudapp.azure.com]
TASK [タイムゾーンを日本時間に変更] **********************************************************
changed: [myselenoidvm.eastus.cloudapp.azure.com]
TASK [docker取得] ****************************************************************
changed: [myselenoidvm.eastus.cloudapp.azure.com]
TASK [リポジトリの追加] ****************************************************************
changed: [myselenoidvm.eastus.cloudapp.azure.com]
TASK [dockerインストール] ************************************************************
changed: [myselenoidvm.eastus.cloudapp.azure.com]
TASK [pipインストール] ***************************************************************
changed: [myselenoidvm.eastus.cloudapp.azure.com]
TASK [dockerのインストール Ansible用] **************************************************
changed: [myselenoidvm.eastus.cloudapp.azure.com]
TASK [selenoidのコピー] ************************************************************
changed: [myselenoidvm.eastus.cloudapp.azure.com]
TASK [chromeブラウザのpull_84] ******************************************************
changed: [myselenoidvm.eastus.cloudapp.azure.com]
TASK [モバイルchromeブラウザのpull_75] **************************************************
ok: [myselenoidvm.eastus.cloudapp.azure.com]
TASK [androidのpull_8.1] ********************************************************
changed: [myselenoidvm.eastus.cloudapp.azure.com]
TASK [docker-compose down] *****************************************************
ok: [myselenoidvm.eastus.cloudapp.azure.com]
TASK [docker-compose up] *******************************************************
changed: [myselenoidvm.eastus.cloudapp.azure.com]
PLAY RECAP *********************************************************************
myselenoidvm.eastus.cloudapp.azure.com : ok=15   changed=10   unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
PS C:\tmp\ansible>

AnsibleによってVMの環境が整いました。

今回作成された構成

今回の構成_05


■まとめ

実際の構築手順

1. hostsファイルの準備
2. playbookの準備
3. ansible.cfgの準備
4. selenoidフォルダのコピー
5. browsers.jsonの変更
6. docker-compose.ymlの変更
7. Dockerfileの準備
8. Ansibleコンテナの実行

==================================================
VMの構成管理の自動化、いかがだったでしょうか。
これでVMの設定がいくらか楽になりました。

次回は、やっと本題。
苦労してAzureに準備したAndroidコンテナを動かしてみようと思います。

ではでは。

――――――――――――――――――――――――――――――――――

執筆者プロフィール:石丸圭
スクラムを中心にテストのアジリティーを高めるべく
日々仕事のリードタイム・プロセスタイムの圧縮に奮闘中。
3児のパパ(7歳4歳1歳)。
MUPうさぎクラス。

個人的なご相談はインスタDMにてどうぞー。
Instagram:@theboyalex

【ご案内】
テスト自動化のご相談は以下までお気軽にご連絡ください。
https://forms.office.com/Pages/ResponsePage.aspx?id=IkyjGtUOzUeqMMEbzjGdlSf__O4V1URMn-5BpGP8xd9UNE9ESkRPUEs1Wk9FM0REU1BXODFBSkI0MC4u

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/