LimeSurvey の構築自動化（Dockerfile , ansible-container）

# LimeSurvey 

OSSのWebアンケートシステム

* https://www.d-ip.jp/limesurvey/
* https://github.com/LimeSurvey/LimeSurvey

多機能で見た目も素敵なので活用することにした。
いつものようにコンテナで使おうと思って、今回はDockerfileと久々にansible-containerで作ることにした。

結果として３つの自動化コードを作った。

* Dockerfile
* ansible-container 用 Role
* Dockerfile からの ansible-container へマイグレーションした Role

いずれも、１，２回コマンド叩いてLimeSurveyが利用できるようになる。（コンテナイメージ作成、コンテナの起動）

# 動作環境
推奨の下記の環境で作ることにした。（Webに記載あり）

* nginx 1.4.6
* php 5.6.x
    * with php-fpm, mbstring, gd2 with freetype, imap, ldap, zip, zlib and databse drivers
* mysql 5.5.50

実際に作った時の環境は以下

* **Docker** : 17.12.0-ce
* **Base Image** : CentOS 7.4.1708
* **Ansible** : 2.4.2.0
* **Ansible-Container** : 0.9.2

| Package | Version 
|:---|---:|
|nginx| 1.13.9|
|php|5.6.33|
|Mariadb|5.5.56

## Install Ansible and Ansible-Container

```shell-session
# yum install epel-release
# yum update

# yum install ansible
# yum install python-pip
# pip install --upgrade setuptools
# pip install ansible-container[docker,k8s]
```

# 1. Dockerfile

[ソースはここ](https://github.com/tksarah/dockerfile/blob/master/limesurvey/Dockerfile)

下記のようなDockerfileを作成。

```dockerfile
# Docker file for Lime Survey
FROM centos:7
MAINTAINER Tk

ARG URL=http://www.d-ip.jp/download/images/limesurvey2.73.0+171219_ja_dip.tar.gz

# Install Nginx
COPY nginx.repo /etc/yum.repos.d/
RUN yum -y update && yum -y install nginx

# Config Nginx for php
COPY default.conf /etc/nginx/conf.d/
 
# Install PHP 5.6
RUN yum -y install epel-release && yum -y update
RUN rpm -ivh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
RUN yum -y install --enablerepo=remi,remi-php56 php-fpm php-mbstring php-gd php-imap php-ldap php-zip php-zlib php-mysqlnd php-xml

# Config php-fpm 
RUN sed -i -e "s/^user\s=\sapache$/user = nginx/g" /etc/php-fpm.d/www.conf
RUN sed -i -e "s/^group\s=\sapache$/group = nginx/g" /etc/php-fpm.d/www.conf
RUN chgrp -R nginx /var/lib/php/{session,wsdlcache}

# Install Mariadb
RUN yum -y install mariadb-server

# Config Mariadb
RUN mysql_install_db --user=mysql --ldata=/var/lib/mysql/

# Download Limesurvey
RUN mkdir -p /var/www
RUN curl -L -o /tmp/limesurvey.tar.gz  ${URL}
RUN tar zxf /tmp/limesurvey.tar.gz -C /var/www &&  \
    mv /var/www/* /var/www/survey && \
    chown -R nginx.nginx /var/www/survey

# Set startup
COPY start.sh /root/
CMD ["/root/start.sh"]
```

補足

* phpのパッケージはドキュメントに記載のなかったものを追加
* nginx.repo 用意
* default.conf 用意
* mariadb,ngnix,php-fpmを順に起動するシェル準備（start.sh）
* LimeSurveyインストーラで事前チェックで「セッション書き込み」にアラートが出たため、``/var/lib/php/{session,wsdlcache}``の属性変更


## Build & Run

```shell-session
# docker build -t tksarah/limesurvey
Sending build context to Docker daemon  8.192kB
Step 1/19 : FROM centos:7
 ---> ff426288ea90

（略）
Successfully built af65374fe68f
Successfully tagged tksarah/limesurvey:latest

# docker run -id --name lime -p 80:80 tksarah/limesurvey
```

# 2. Ansible-Container Role 

[ソースはここ](https://github.com/tksarah/ansible_container/tree/master/limesurvey)

```shell-session
# mkdir limesurvey
# cd limesurvey
# ansible-container init
Ansible Container initialized.

# ls
ansible-requirements.txt  ansible.cfg  container.yml  meta.yml  requirements.yml

# for x in limesurvey nginx mariadb php start; do ansible-galaxy init $x; done
- limesurvey was created successfully
- nginx was created successfully
- mariadb was created successfully
- php was created successfully
- start was created successfully
```

## ロール作成
それぞれのロールを作る
### limesurvey role

あまり美しくないが。。。Renameのところ。

```yaml:defaults/main.yml
---
ROOT_DIR: /var/www
SERVICE_DIR: /var/www/survey
URL: "http://www.d-ip.jp/download/images/limesurvey2.73.0+171219_ja_dip.tar.gz"
```

```yaml:tasks/main.yml
---
- name: Create Service Directory
  file: path={{ SERVICE_DIR }} owner=nginx group=nginx recurse=yes state=directory

- name: Download Limesurvey
  get_url: url={{ URL }} dest=/tmp/limesurvey.tar.gz

- name: Unarchive
  unarchive: src=/tmp/limesurvey.tar.gz dest={{ ROOT_DIR }} remote_src=yes

- name: Rename
  shell: mv {{ ROOT_DIR }}/*/* {{ SERVICE_DIR }}
  args:
    creates: "{{ SERVICE_DIR }}/index.php"

- name: Change attribute
  file: path={{ ROOT_DIR }} owner=nginx group=nginx recurse=yes state=directory

- name: Cleanup
  file: path=/tmp/limesurvey.tar.gz state=absent
```

```yaml:vars/main.yml
---
URL: "https://github.com/LimeSurvey/LimeSurvey/archive/3.4.1+180221.tar.gz"
```

### nginx role

* templatesにdefault.conf.j2配置

```yaml:defaults/main.yml
---
ROOT_DIR: "/var/www/survey"
SERVER_NAME: "localhost"
```

```yaml:tasks/main.yml
- name: Add repository
  yum_repository:
    name: nginx
    description: Nginx Repo
    baseurl: http://nginx.org/packages/mainline/centos/7/$basearch/

- name: Install nginx
  yum: name=nginx state=latest disable_gpg_check=yes

- name: Config defalt.conf
  template: src=default.conf.j2 dest=/etc/nginx/conf.d/default.conf
```

### mariadb role

```yaml:tasks/main.yml
- name: Install Mariadb
  yum: name=mariadb-server state=latest disable_gpg_check=yes

- name: Initialize DB
  shell: mysql_install_db --user=mysql --ldata=/var/lib/mysql/ creates=/var/lib/mysql/mysql
```

### php role

```yaml:tasks/main.yml
- name: Install remi-release
  yum: name=http://rpms.famillecollet.com/enterprise/remi-release-7.rpm state=present disable_gpg_check=yes
- name: Install epel-release
  yum: name=epel-release state=latest disable_gpg_check=yes

- name: Install PHP 5.6
  yum: name={{ item }} state=latest enablerepo=remi,remi-php56 disable_gpg_check=yes
  with_items:
    - php-fpm
    - php-mbstring
    - php-gd 
    - php-imap
    - php-ldap
    - php-zip
    - php-zlib
    - php-mysqlnd
    - php-xml

- name: Config php-fpm for user
  lineinfile: path=/etc/php-fpm.d/www.conf regexp='^user\s=\sapache$' line='user = nginx'

- name: Config php-fpm for group
  lineinfile: path=/etc/php-fpm.d/www.conf regexp='^group\s=\sapache$' line='group = nginx'

- name: Change attribute
  file: path='/var/lib/php/{{ item }}' group=nginx
  with_items:
    - session
    - wsdlcache
```

### start role

* files/start.sh 配置

```yaml:tasks:main.yml
- name: Copy Start script
  copy: src=start.sh dest=/root/start.sh mode=755 owner=root group=root
```

## container.yml の作成

```yaml
version: "2"
settings:
  conductor:
    base: centos:7

  project_name: lime

services:
  limesurvey:
    from: "centos:7"
    roles:
      - nginx
      - php
      - mariadb
      - limesurvey
      - start
    ports:
      - "80:80"
    command: ["/root/start.sh"]
    dev_overrides:
      environment:
        - "DEBUG=2"

registries: {}
```

## Build & Run

```shell-session
# ansible-container build
Building Docker Engine context...
Starting Docker build of Ansible Container Conductor image (please be patient)...
Parsing conductor CLI args.
Docker? daemon integration engine loaded. Build starting.      project=lime
Building service...     project=lime service=limesurvey

PLAY [limesurvey] **************************************************************

（略）

PLAY RECAP *********************************************************************
limesurvey                 : ok=2    changed=1    unreachable=0    failed=0

Applied role to service role=start service=limesurvey
Committed layer as image        image=sha256:3bc525a274f0f200f3d6deec0d2c952c1b18b423347b6f3062629ab3c17f595d service=limesurvey
Build complete. service=limesurvey
All images successfully built.
Conductor terminated. Cleaning up.      command_rc=0 conductor_id=7e6027e26a72a147c15901f32c68ca9cf94b554ac900c547a367b250fc290461 save_container=False


# # ansible-container run
Parsing conductor CLI args.
Engine integration loaded. Preparing run.       engine=Docker? daemon
Verifying service image service=limesurvey

PLAY [Deploy lime] *************************************************************

TASK [docker_service] **********************************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0

All services running.   playbook_rc=0
Conductor terminated. Cleaning up.      command_rc=0 conductor_id=a47da5e993a652782418978a5538588838501accdaab88690e27bb9b8ccd75a5 save_container=False


# docker ps
WARNING: Error loading config file: /root/.docker/config.json - read /root/.docker/config.json: is a directory
CONTAINER ID        IMAGE                            COMMAND             CREATED              STATUS              PORTS                NAMES
87342d1aa3c7        lime-limesurvey:20180227140838   "/root/start.sh"    About a minute ago   Up About a minute   0.0.0.0:80->80/tcp   lime_limesurvey_1
```

run した後にもう一回 run しても冪等性が保たれる。

```shell-session
# ansible-container run
Parsing conductor CLI args.
Engine integration loaded. Preparing run.       engine=Docker? daemon
Verifying service image service=limesurvey

PLAY [Deploy lime] *************************************************************

TASK [docker_service] **********************************************************
ok: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0

All services running.   playbook_rc=0
Conductor terminated. Cleaning up.      command_rc=0 conductor_id=78904d7a5422cc6e51f5fe9e21bd5120d461178274b97c05032776acada59009 save_container=False
[root@kujira limesurvey]# docker ps -a
WARNING: Error loading config file: /root/.docker/config.json - read /root/.docker/config.json: is a directory
CONTAINER ID        IMAGE                            COMMAND             CREATED              STATUS              PORTS                NAMES
87342d1aa3c7        lime-limesurvey:20180227140838   "/root/start.sh"    About a minute ago   Up About a minute   0.0.0.0:80->80/tcp   lime_limesurvey_1
```

ansible-container の他オプション、**stop** で停止、**restart** で再起動、**destroy**でコンテナの削除

# トラブル
## ansible-cintainer build 実行時のエラー

ansible-container build fails with AttributeError: 'tuple' object has no attribute 'id' 

### 原因 
pip バージョン依存によるエラー
https://github.com/ansible/ansible-container/issues/884

### ワークアラウンド
python docker モジュールのダウングレード

```shell-session
# pip show docker
---
Metadata-Version: 2.0
Name: docker
Version: 3.1.0

# pip install -U docker==2.7.0
```

## ansible-container 実行後のワーニング

docker コマンド実行時に毎回出て来る。

```shell_session
# docker ps
WARNING: Error loading config file: /root/.docker/config.json - read /root/.docker/config.json: is a directory
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```

### 原因
https://github.com/ansible/ansible-container/issues/717

解決済み（0.9.3 Fixバージョン）
今回使ってるのは0.9.2なので、アップグレードをまつことにする。

### ワークアラウンド

* 無視
* ansible-container build 後に削除 .docker/config.json を削除

# 3.（おまけ）Migrating from a Dockerfile

ansible-container には Dockerfile からのマイグレーション機能が付いている。

http://docs.ansible.com/ansible-container/migrating.html

これを使って見たところ下記のような出力と共に YAML ファイルが生成された。

```shell-session
# mkdir migrate_dir
# cd migrate_dir/
# ansible-container import ../dockerfile/limesurvey　# ・・・この配下にDockerfileが存在
Project successfully imported. You can find the results in:
/root/migrate_dir
A brief description of what you will find...


container.yml
-------------

The container.yml file is your orchestration file that expresses what services you have and how to build/run them.

settings:
  conductor_base: centos:7
services:
  limesurvey:
    roles:
    - limesurvey


I added a single service named limesurvey for your imported Dockerfile.
As you can see, I made an Ansible role for your service, which you can find in:
/root/migrate_dir/roles/limesurvey

migrate_dir/roles/limesurvey/tasks/main.yml
-------------------------------------------

The tasks/main.yml file has your RUN/ADD/COPY instructions.

- name: Ensure /etc/yum.repos.d/ exists
  file:
    path: /etc/yum.repos.d/
    state: directory
- name: Install Nginx (nginx.repo)
  copy:
    src: nginx.repo
    dest: /etc/yum.repos.d/
- shell: yum -y update && yum -y install nginx
- name: Ensure /etc/nginx/conf.d/ exists
  file:
    path: /etc/nginx/conf.d/
    state: directory
- name: Config Nginx for php (default.conf)
  copy:
    src: default.conf
    dest: /etc/nginx/conf.d/
- name: Install PHP 5.6
  shell: yum -y install epel-release && yum -y update
- shell: rpm -ivh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
- shell: yum -y install --enablerepo=remi,remi-php56 php-fpm php-mbstring php-gd php-imap
    php-ldap php-zip php-zlib php-mysqlnd php-xml
- name: Config php-fpm
  shell: sed -i -e "s/^user\s=\sapache$/user = nginx/g" /etc/php-fpm.d/www.conf
- shell: sed -i -e "s/^group\s=\sapache$/group = nginx/g" /etc/php-fpm.d/www.conf
- shell: chgrp -R nginx /var/lib/php/{session,wsdlcache}
- name: Install Mariadb
  shell: yum -y install mariadb-server
- name: Config Mariadb
  shell: mysql_install_db --user=mysql --ldata=/var/lib/mysql/
- name: Download Limesurvey
  shell: mkdir -p /var/www
- shell: curl -L -o /tmp/limesurvey.tar.gz  ${URL}
- shell: tar zxf /tmp/limesurvey.tar.gz -C /var/www
- shell: mv /var/www/* /var/www/survey
- shell: chown -R nginx.nginx /var/www/survey
- name: Ensure /root/ exists
  file:
    path: /root/
    state: directory
- name: Set startup (start.sh)
  copy:
    src: start.sh
    dest: /root/


I tried to preserve comments as task names, but you probably want to make
sure each task has a human readable name.

migrate_dir/roles/limesurvey/meta/container.yml
-----------------------------------------------

Metadata from your Dockerfile went into meta/container.yml in your role.
These will be used as build/run defaults for your role.

# Docker file for Lime Survey
from: centos:7
maintainer: Tk
command:
- /root/start.sh


I also stored ARG directives in the role's defaults/main.yml which will used as
variables by Ansible in your build and run operations.

Good luck!
Project imported.
```

* コメント「#」は **-name** に置き換わっている
* shell , copy などのモジュール中心に置き換わっている

でもって、できたもので試しにビルドしてみる

```shell-session
# ansible-container build
```

結果はエラー。
ただ、よく見たら変数指定がおかしくなっているところがあり、そこを直して再実行したら通った。

```
- shell: curl -L -o /tmp/limesurvey.tar.gz  ${URL}
＝＞
- shell: curl -L -o /tmp/limesurvey.tar.gz  {{ URL }}
```

さっそく、run してみる。

```shell-session
# ansible-container run
Parsing conductor CLI args.
Engine integration loaded. Preparing run.       engine=Docker? daemon
Verifying service image service=limesurvey

PLAY [Deploy migrate_dir] ******************************************************

TASK [docker_service] **********************************************************
fatal: [localhost]: FAILED! => {"changed": false, "errors": ["ERROR: for migratedir_limesurvey_1  Cannot start service limesurvey: OCI runtime create failed: container_linux.go:296: starting container process caused \"exec: \\\"/root/start.sh\\\": permission denied\": unknown", "ERROR: for limesurvey  Cannot start service limesurvey: OCI runtime create failed: container_linux.go:296: starting container process caused \"exec: \\\"/root/start.sh\\\": permission denied\": unknown"], "failed": true, "module_stderr": "Creating migratedir_limesurvey_1 ... \r\n\r\u001b[1B\nERROR: for migratedir_limesurvey_1  Cannot start service limesurvey: OCI runtime create failed: container_linux.go:296: starting container process caused \"exec: \\\"/root/start.sh\\\": permission denied\": unknown\n\nERROR: for limesurvey  Cannot start service limesurvey: OCI runtime create failed: container_linux.go:296: starting container process caused \"exec: \\\"/root/start.sh\\\": permission denied\": unknown\n", "module_stdout": "", "msg": "Error starting project ERROR: for limesurvey  Cannot start service limesurvey: OCI runtime create failed: container_linux.go:296: starting container process caused \"exec: \\\"/root/start.sh\\\": permission denied\": unknown"}
        to retry, use: --limit @/root/migrate_dir/ansible-deployment/playbook.retry

PLAY RECAP *********************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1

Traceback (most recent call last):
  File "/usr/bin/conductor", line 11, in <module>
    load_entry_point('ansible-container', 'console_scripts', 'conductor')()
  File "/_ansible/container/__init__.py", line 19, in __wrapped__
    return fn(*args, **kwargs)
  File "/_ansible/container/cli.py", line 399, in conductor_commandline
    **params)
  File "/_ansible/container/__init__.py", line 19, in __wrapped__
    return fn(*args, **kwargs)
  File "/_ansible/container/core.py", line 854, in conductorcmd_run
    'Error executing the run command. Not all containers may be running.'
container.exceptions.AnsibleContainerException: Error executing the run command. Not all containers may be running.
Conductor terminated. Cleaning up.      command_rc=1 conductor_id=467076af06bebe4167568c5fbe21967158517991128d09b4aefce1a0cfdf9902 save_container=False
ERROR   Conductor exited with status 1
```

/root/start.sh の実行属性がとれてしまってるので、下記のようにつけたら run も成功。

```migrate_dir/roles/limesurvey/tasks/main.yml
# 最後の copy task
- name: Set startup (start.sh)
  copy:
    src: start.sh
    dest: /root/
    mode: 0755
```

ただし、ansible-container run は成功したものの、 Port 部分がアクティブにならなかった。
理由は、Dockerfile に「EXPOSE 80:80」を記載していないため。これをDockerfileに追記し、import することで run も問題なく成功。
なお、この port の設定は下記のファイルに格納されていた。（ansible-container import 実行時の出力にも表示されている）

```/migrate_dir/roles/limesurvey/meta/container.yml
# Docker file for Lime Survey
from: centos:7
maintainer: Tk
ports:
- 80:80
command:
- /root/start.sh
```

## おまけの、まとめ

ansible-container import のマイグレーションの留意点（version 0.9.2 時点）。

* 変数の確認
* 配布された実行権限付与ファイルの確認
* Dockerfile のパラメータ確認（EXPOSE等）

# ansible-container の所感

* まだ1.xになっていないこともあり、若干のBugや留意点が存在
* Dockerfile からのマイグレーションはかなり雑だと思いきや、結構使える（上記、留意点込）
* 他のオーケストレーションへのデプロイも対応しているため、期待できる（今回はそこまでは未確認）