Ansible を使って FTPユーザの作成と Apache の仮想サイトの設定をする

Ansible を使って設定を行う例として、 Linuxユーザを作成してFTPユーザとし、 Apache の仮想サイトを作成する部分をやってみたいと思います。

前提

対象サーバの LAMP はCentOS7.5 + Apache + PHP + MySQL サーバを構築し、WordPressサイトを引っ越す(2018/11/20)のうち

  • 1.初期設定 ~ 4.リポジトリ追加
  • 5.vsftpd のうち
    • 5.1.インストール, 5.2.設定
  • 6.Apache ~ 11.Webmin
  • 12.Apache仮想サイトのうち
    • 12.1.ダミーサイトの作成
  • 16.SSH

が既に設定済みの状態のサーバとします(13.Let’s Encrypt ~ 15.WP-CLI は対象外)。

また、今回 Ansible で設定するのは

  • 5.vsftpd のうち
    • 5.3.ユーザ設定
    • 5.4.ユーザ作成・設定
  • 12.Apache仮想サイトのうち
    • 12.2. 仮想サイト作成

の部分を想定しています。

設定

ディレクトリ階層

PROJECT_ROOT/
  ├ workspace/                              // データ永続化領域
  │    ├ entrypoint.sh                      // Dockerコンテナ起動時に実行するシェルスクリプト
  │    └ ansible/                           // Ansible 用ディレクトリ
  │         ├ targets/                      // リモートホストの情報を収めるディレクトリ
  │         │    └ hosts                    // リモートサーバの一覧 (今回は1つのみ)
  │         │
  │         ├ tasks/                        // Ansible のタスクを収めるディレクトリ
  │         │    ├ add_user.yml             // Linuxユーザの作成
  │         │    ├ vsftpd_user_settings.yml // vsftpd の設定
  │         │    └ apache_settings.yml      // Apache 仮想サイトの設定
  │         │
  │         ├ templates/                    // Apache 仮想サイト設定のテンプレートを収めるディレクトリ
  │         │    └ vhost.conf.j2            // Apache 仮想サイト設定のテンプレート
  │         │
  │         ├ vars/                         // 各種パラメータ変数の設定を収めるディレクトリ
  │         │    └ param_vars.yml           // 各種パラメータ変数
  │         │
  │         └ main.yml                      // Ansible の playbook
  │
  ├ docker-compose.yml                      // Docker Compose 設定ファイル
  └ Dockerfile                              // Dockerfile

Ansibleコントロールノード を Dockerコンテナ でビルドし、リモートサーバに公開鍵認証でSSH接続してインストール済みのパッケージの一覧を取得するまでAnsible を使って yum update を実行するの記事の構造をベースにカスタマイズ。

一揃いを Github に置きました。

Dockerfile, docker-compose.yml, entrypoint.sh. ansible/targets/hosts

この辺りはそのまま。 ansible/targets/hosts はグループ名を変更していますが、対となる ansible/main.ymlhosts の指定を変えれば良いだけなので些事。

ansible/vars/param_vars.yml

username: USERNAME
password: Password1234
rootdirectory: sample_site
domain: www.sample.jp
ipaddress: 192.0.2.1
portnum: 80

まずは今回の新顔の変数から。当初は末尾の「備考1: コマンドライン引数を変数に代入する」のように引数でコマンドラインから直接渡すことを考えていましたが、上記の通り6つもあるとそれだけでコマンドが長くなってしまい可読性が下がるので最終的に止めました。

代わりに変数を1つのファイルにまとめて、コマンド自体の長さは変わらないように調整しました。

この場合、以下の ansible/main.yml で触れますが vars_files: ./vars/param_vars.yml という一行で変数ファイルの読み込みができ、かつそれ以降は vars キーで変数としてそのまま使用できるので導入はわりとすんなり行きました。

ansible/main.yml

- name: Settings vsftpd and httpd

  become: yes
  become_user: ADMIN_USER
  become_method: su

  hosts:
    - add_vhost_servers

  vars_files: ./vars/param_vars.yml

  tasks:
    - name: Add Linux User
      include_tasks: ./tasks/add_user.yml
      vars:
        user: "{{ username }}"
        passwd: "{{ password }}"
    - name: Settings vsftpd user_list and user_conf
      include_tasks: ./tasks/vsftpd_user_settings.yml
      vars:
        user: "{{ username }}"
        directory: "{{ rootdirectory }}"
    - name: Setup Apache conffile
      include_tasks: ./tasks/apache_settings.yml
      vars:
        directory: "{{ rootdirectory }}"
        url: "{{ domain }}"
        ip: "{{ ipaddress }}"
        portnumber: "{{ portnum }}"

タスクのメイン部分。基本は前回と同様で、 tasks で指定する各種タスクが置き換わった形です。変数については上記の通り。

ansible/tasks/add_user.yml

- name: Add user, setting password and groups
  user:
    name: "{{ username }}"
    password: "{{ password | password_hash('sha512') }}"
    groups: apache
    append: yes

ansible/main.yml から渡ってきた各変数が使用される(ここでは username, password)、という形です。

一点注意なのはパスワードはそのままだと平文で保存されてしまいますが、実際に認証する際はハッシュ値に変換されるので不一致となってしまいます。そこで、保存時にハッシュ値に変換するように password_hash('sha512') を噛ませています。

ansible/tasks/vsftpd_user_settings.yml

- name: Settings vsftpd user_list
  blockinfile:
    path: /etc/vsftpd/user_list
    create: yes
    insertafter: EOF
    marker: "# {mark} ANSIBLE basic setup: {{ username }}"
    block: "{{ username }}"
- name: Settings vsftpd user_conf
  blockinfile:
    path: "/etc/vsftpd/user_conf/{{ username }}"
    create: yes
    insertafter: EOF
    marker: "# {mark} ANSIBLE basic setup: {{ username }}"
    block: "local_root=/var/www/{{ rootdirectory }}"
- name: Restart vsftpd
  systemd:
    name: vsftpd.service
    state: restarted
    daemon_reload: yes

次は vsftpd の設定。 user_list にユーザとして登録し、 user_conf ディレクトリの下にユーザ名のファイルを作成、そこに local_root の指定を記述する、という内容です。

ここでも一点注意。

設定ファイルへの書き込みとして blockinfile を使用しているのですが、 marker の値がユニークでないと、 loop で繰り返し処理をしたり、今回のケースだと異なる仮想サイトの設定を行ったり(Ansible を2回実行する)した場合に後勝ちで上書きされてしまうこと。

今回のケースでは user_list に2回目の変数のユーザしか残らなくなってしまいます。そのため、 marker の最後にユーザ名を混ぜることでコメント文が実行の度にユニークとなるようにしました。

ansible/tasks/apache_settings.yml

- name: mkdir root
  file:
    path: "/var/www/{{ rootdirectory }}"
    state: directory
    owner: ADMIN_USER
    group: ADMIN_USER
    mode: 0755
    recurse: no
- name: mkdir web
  file:
    path: "/var/www/{{ rootdirectory }}/web"
    state: directory
    owner: apache
    group: apache
    mode: 0775
    recurse: no
- name: Setup Apache conffile
  template:
    src: ../templates/vhost.conf.j2
    dest: "/etc/httpd/conf.d/{{ rootdirectory }}.conf"
    mode: '0644'
- name: Restart httpd
  systemd:
    name: httpd.service
    state: reloaded
    daemon_reload: yes

続いて Apache 仮想サイトの設定。ディレクトリを作ったり権限・所有者設定したり。

最後の Setup Apache conffile の処理では jinja2 のテンプレートから仮想サイト用の conf ファイルを作成しています。

ansible/templates/vhost.conf.j2

<VirtualHost {{ ipaddress }}:{{ portnum }}>
DocumentRoot "/var/www/{{ rootdirectory }}/web"
ServerName {{ domain }}
{% if domain | regex_search("^www\.", ignorecase=True) %}
ServerAlias {{ domain | regex_replace("^www\.(.*)$", '\\1') }}
{% endif %}
ScriptAlias /cgi-bin/ /var/www/{{ rootdirectory }}/web/cgi-bin/
{% if domain | regex_search("^www\.", ignorecase=True) %}
RewriteEngine on
RewriteCond %{HTTP_HOST} {{ domain | regex_replace("^www\.(.*)$", '^\\1$') | regex_replace("\.", '\.') }}
RewriteRule ^(.*)$       http://{{ domain }}$1 [R=301,L]
{% endif %}
<Directory "/var/www/{{ rootdirectory }}/web">
allow from all
AllowOverride All
Options FollowSymLinks
Require all granted
</Directory>
</VirtualHost>

大体設定ファイルの内容ですが、各種変数を使用しています。

また、ドメイン(変数 domain )が www. 始まりの場合はサーバ名を www.example.jp のように www. 付きとし、同時に ServerAliasexample.jpwww. なしもフォローするような条件分岐(if, regex_search)を挟みました。

併せて、 www. なしのURLにアクセスした場合に www. ありにリダイレクトする設定も、変数 domain を正規表現置換(regex_replace)で一括で入れるようにしました。

例えば、 domain: www.example.jp とした場合は以下のような設定ファイルが作成されます。

<VirtualHost 192.0.2.1:80>
DocumentRoot "/var/www/example/web"
ServerName www.example.jp
ServerAlias example.jp
ScriptAlias /cgi-bin/ /var/www/example/web/cgi-bin/
RewriteEngine on
RewriteCond %{HTTP_HOST} ^example\.jp$
RewriteRule ^(.*)$       http://www.example.jp$1 [R=301,L]
<Directory "/var/www/example/web">
allow from all
AllowOverride All
Options FollowSymLinks
Require all granted
</Directory>
</VirtualHost>

一方、 domain: form.example.jp のようなケースだと以下。

<VirtualHost 192.0.2.1:80>
DocumentRoot "/var/www/form_example/web"
ServerName form.example.jp
ScriptAlias /cgi-bin/ /var/www/form_example/web/cgi-bin/
<Directory "/var/www/form_example/web">
allow from all
AllowOverride All
Options FollowSymLinks
Require all granted
</Directory>
</VirtualHost>

上記の通り、 ServerAliasRewrite 系がありません。

動作確認

以上のような構成で動作検証。

# ansible-playbook -i /workspace/ansible/targets/hosts /workspace/ansible/main.yml -u SSH_REMOTEUSER --private-key="/root/.ssh/PRIVATE_KEY" -K
BECOME password: 

PLAY [Settings vsftpd and httpd] **********************************************************************************
TASK [Gathering Facts] ********************************************************************************************ok: [192.0.2.1]

TASK [Add Linux User] *********************************************************************************************included: /workspace/ansible/tasks/add_user.yml for 192.0.2.1

TASK [Add user, setting password and groups] **********************************************************************changed: [192.0.2.1]

TASK [Settings vsftpd user_list and user_conf] ********************************************************************included: /workspace/ansible/tasks/vsftpd_user_settings.yml for 192.0.2.1

TASK [Settings vsftpd user_list] **********************************************************************************changed: [192.0.2.1]

TASK [Settings vsftpd user_conf] **********************************************************************************changed: [192.0.2.1]

TASK [Restart vsftpd] *********************************************************************************************changed: [192.0.2.1]

TASK [Setup Apache conffile] **************************************************************************************included: /workspace/ansible/tasks/apache_settings.yml for 192.0.2.1

TASK [mkdir root] *************************************************************************************************changed: [192.0.2.1]

TASK [mkdir web] **************************************************************************************************changed: [192.0.2.1]

TASK [Setup Apache conffile] **************************************************************************************changed: [192.0.2.1]

TASK [Restart httpd] **********************************************************************************************changed: [192.0.2.1]

PLAY RECAP ********************************************************************************************************192.0.2.1                 : ok=12   changed=8    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

OKです。

仮想サイトにアクセスして Apache のデフォルトページが表示される、FTPでログインできる、ファイルアップロードなどの操作ができる、といった動作を一通りできることを確認できました。

備考1: コマンドライン引数を変数に代入する

例えば、以下のような main.yml を用意します。

- name: Get packages from hosts and do yum update

  become: yes
  become_user: ADMIN_USER
  become_method: su

  hosts:
    - update_servers

  tasks:
    - name: Get packages from hosts before update
      include_tasks: ./tasks/get_packages.yml
    - name: Output packages from hosts before update
      include_tasks: ./tasks/output_packages.yml
      vars:
        flag: "{{ before }}"
    - name: Do yum update
      include_tasks: ./tasks/yum_update.yml
    - name: Get packages from hosts after update
      include_tasks: ./tasks/get_packages.yml
    - name: Output packages from hosts after update
      include_tasks: ./tasks/output_packages.yml
      vars:
        flag: "{{ after }}"

Ansible を使って yum update を実行するmain.yml のうち、 vars の部分を少し編集したものです。

これで以下のように playbook を実行。

# ansible-playbook -i /workspace/ansible/targets/hosts /workspace/ansible/main.yml -u SSH_REMOTEUSER --private-key="/root/.ssh/PRIVATE_KEY" -K -e "before=before2 after=after22"

すると、 workspace/192.0.2.1_packages_before2workspace/192.0.2.1_packages_after22 が生成され、コマンドラインの引数が変数として使用されたことが分かりました。

当初はこれの延長線を考えていましたが、冒頭の通り6つも引数があると長くなってしまうので最終的には不採用としました。

ただし、これはこれで引数の使い方として参考になりそうなのでメモとして残しておきます。

参考

Ansible

引数、変数

変数

vars_files を採用。

ユーザ作成

パスワード

Facts

設定に追記

blockfile

複数のテキストブロックを追加する場合、マーカーラインをユニークにしないとテキストブロックが上書きされる

marker の文字列はユニークになるようにすること。

systemd

ディレクトリ作成

httpd

jinja2, if

jinja2, regrex

usermod

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。