Ansibleってなんじゃ?
Ansible(アンシブル)は、レッドハットが開発するオープンソースの構成管理ツールである。サーバを立ち上げる際、>あらかじめ用意した設定ファイルに従って、ソフトウェアのインストールや設定を自動的に実行する事が出来る
出典: フリー百科事典『ウィキペディア(Wikipedia)』
らしいです。今までポチポチクリックして設定していた部分を自動化できるなんてすごいですね。
実行環境
- macOS
mojave 10.14.6 - ansible
2.8.3
下準備
1.ansibleインストール
macの場合は以下のコマンドでインストールする事ができます。
brew install ansible
ansible --version
ansible 2.8.3
2.ansible実行先サーバ構築
今回はAWSを使用します。AmazonLinux2でサーバを一つ立ち上げ、pemキーとpublicIPを控えておいてください。
またletsencrptでssl化する対象ドメインも用意してください
# 今回作成するディレクトリ構成
├── ansible.cfg
├── hosts
├── playbook.yml
├── roles
│ ├── common #サーバで共通で実行するタスク
│ │ └── tasks
│ │ └── main.yml
│ ├── composer #composerのインストールやらなにやら
│ │ └── tasks
│ │ └── main.yml
│ ├── git_clone #リポジトリをクローンしてくる処理
│ │ └── tasks
│ │ ├── main.yml
│ │ └── templates
│ │ └── env.j2
│ ├── letsencrypt #指定されたドメインをssl化
│ │ └── tasks
│ │ ├── main.yml
│ │ └── templates
│ │ ├── laravel-ssl.conf.j2
│ │ └── letsencrypt.j2
│ ├── mysql
│ │ └── tasks
│ │ ├── main.yml
│ │ └── templates
│ │ ├── init_my.cnf.j2
│ │ └── my.cnf.j2
│ ├── nginx
│ │ └── tasks
│ │ ├── main.yml
│ │ └── templates
│ │ ├── laravel.conf.j2
│ │ └── nginx.conf.j2
│ ├── php
│ │ └── tasks
│ │ ├── main.yml
│ │ └── templates
│ │ └── php-fpm.conf.j2
│ └── restart_nginx
│ └── handlers
│ └── main.yml
└── vars #変数ファイル
└── variables.yml
今回のディレクトリ構成は4つに分類する事ができます。
####hosts
サーバの接続情報を定義します。sshキーの配置場所の指定もここで行います。
####vars
variables.ymlにrolesで使う変数の情報を記述します。例えばnginxのドメイン名の指定など、gitのclone先のリポジトリ情報などはここに記載します。
####roles
このディレクトリにインストールするパッケージや処理を項目ごとに分けて配置します。
####playbook.yml
rolesや、変数ファイルを読み込んだり、ssh接続するユーザーの指定を記述します。
ここで書かれたものを元に構築されるというイメージですね。
ansibleではrolesというディレクトリ配下に処理を記述することにより、構築するリソースを分ける事ができます。
分けられたリソースはplaybook.yml内で実行したい処理のみ記述する事ができます。
簡単に言えばrolesはモジュールのようなもので、使いたい処理のみplaybook.ymlにかけるのでかなり見通しが良くなります。
早速作る
##1.hostsファイルを作成
サーバに接続するための情報を記述します。
[localhost]
127.0.0.1 ansible_connection=local
[aws] #ここに記載されたIPに対して接続します。複数指定することも可能です。
111.111.111.111
111.111.111.111
[aws:vars] #[aws]で使う変数を定義しています。
ansible_user=ec2-user #サーバ接続するユーザー
ansible_ssh_private_key_file=/path/to/example.pem #サーバに接続するためのpemキーを指定
##2.variablesファイル作成
varsディレクトリの下にvariables.ymlを作成します。
ここにしっかり情報を指定しないとansible実行時にエラーが起きます。
---
# nginx
virtual_server_name: example.com #nginx指定するServername
virtual_root_path: "{{git_repo_dest}}/public" #nginxで指定するrootディレクトリ
# db
db_name: ansible_test #作成するDBの名前
db_user: root #作成するdbのユーザー名
db_password: root #作成するdbパスワード
#git
git_repo_url: https://github.com/username/repository.git #clone元repositoryのURL
git_repo_dest: /www/test #どこにgit cloneしたフォルダを配置するか指定
git_branch_name: master # cloneしてくるブランチ指定
#Laravel .env
MAIL_DRIVER: sendmail
MAIL_HOST: locahost
MAIL_PORT: 25
MAIL_USERNAME: null
MAIL_PASSWORD: null
MAIL_FROM_ADDRESS: test@test.com
MAIL_FROM_NAME: test@test.com
#SSL(let's encrypt) #let's encryptで有効期限を知らせるメール通知先
ssl_email: test@test.com
##3.rolesファイル作成
rolesファイルは決められたディレクトリ名で作成しなければ正常に動作しないので注意が必要です。
※例
├── nginx #role名 playbookでこのディレクトリ名を指定します。
│ └── tasks
│ ├── main.yml #この名前じゃなきゃだめ
│ └── templates #テンプレート置き場
│ ├── laravel.conf.j2
│ └── nginx.conf.j2
タイトルの構成になるように作っていきます。
また、templateファイルを作ってますが、
###1.common
サーバで共通で実行するタスクを定義します。
├── common
│ └── tasks
│ └── main.yml
#ホスト名設定
- name: set hostname
hostname:
name: "{{virtual_server_name}}"
- name: amzn2-core.repo priority down
replace:
path: /etc/yum.repos.d/amzn2-core.repo
regexp: 'priority=10'
replace: 'priority=99'
when: ansible_facts['distribution'] == "Amazon"
- name: amzn2-extras.repo priority down
replace:
path: /etc/yum.repos.d/amzn2-extras.repo
regexp: 'priority = 10'
replace: 'priority=99'
when: ansible_facts['distribution'] == "Amazon"
- name: check exists EPEL
shell: amazon-linux-extras list | grep epel
register: epel_check
failed_when: epel_check.rc not in [0,1]
when: ansible_facts['distribution'] == "Amazon"
- name: install EPEL when amazon linux
shell: amazon-linux-extras install -y epel
when: ansible_facts['distribution'] == "Amazon" and epel_check.rc == 0
- name: install EPEL when other linux
yum:
name: ['epel-release','https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm']
when:
- ansible_facts['distribution'] == "CentOS"
- ansible_facts['distribution_major_version'] == "7"
#必須パッケージインストール
- name: yum basic install
yum:
name: ['zip', 'unzip', 'git', 'mysql', 'nginx', 'nodejs','git']
state: latest
#グループ作成
- name: create group
group:
name: laravel
state: present
# nginxを作成したグループに参加させる
- name: add nginx to laravel group
user:
name: "{{ item.name }}"
append: yes
groups: "{{ item.group }}"
with_items:
- {name: 'nginx', group: 'laravel'}
- {name: 'ec2-user', group: 'laravel'}
###4.php
php関連のタスクを定義します。templatesにはnginxとphpをつなぐために必要なphp-fpmファイルのテンプレートを配置します。
|── php
│ └── tasks
│ ├── main.yml
│ └── templates
│ └── php-fpm.conf.j2
# PHP-FPM FastCGI server
# network or unix domain socket configuration
upstream php-fpm {
server unix:/run/php-fpm/www.sock;
}
---
- name: remi repository install
yum:
name: [http://rpms.famillecollet.com/enterprise/remi-release-7.rpm]
- name: yum php install
yum:
name: ['php72', 'php-cli' ,'php-gd', 'php-mysqlnd', 'php-mbstring', 'php-mcrypt', 'php-pdo', 'php-xml', 'php-fpm','php-json','php-xmlrpc','php-pecl-zip']
state: latest
enablerepo: remi,remi-php72
- name: modify php.ini
replace:
dest: /etc/php.ini
regexp: "{{ item.regexp }}"
replace: "{{ item.replace }}"
with_items:
- { regexp: "^;date.timezone =", replace: "date.timezone = Asia/Tokyo" }
- { regexp: "^expose_php = On", replace: "expose_php = Off" }
- { regexp: "^upload_max_filesize = .*$", replace: "upload_max_filesize = 256M" }
- { regexp: "^memory_limit = .*$", replace: "memory_limit = 1024M" }
- name: modify php-fpm config file
replace:
dest: /etc/php-fpm.d/www.conf
regexp: "{{ item.regexp }}"
replace: "{{ item.replace }}"
with_items:
- { regexp: "^user = apache", replace: "user = nginx" }
- { regexp: "^group = apache", replace: "group = nginx" }
- { regexp: "^listen = 127.0.0.1:9000", replace: "listen = /var/run/php-fpm/php-fpm.sock" }
- { regexp: "^;listen.owner = nobody", replace: "listen.owner = nginx" }
- { regexp: "^;listen.group = nobody", replace: "listen.group = nginx" }
- name: make php-fpm.conf
template:
src: php-fpm.conf.j2
dest: /etc/nginx/conf.d/php-fpm.conf
- name: start php-fpm
systemd:
name: php-fpm.service
state: restarted
daemon_reload: yes
enabled: yes
###5.nginx
ngix関連のタスクを定義します。
├── nginx
│ └── tasks
│ ├── main.yml
│ └── templates
│ ├── laravel.conf.j2
│ └── nginx.conf.j2
server {
root {{virtual_root_path}};
listen 80;
server_name {{virtual_server_name}};
location / {
client_max_body_size 10m;
index index.php index.html;
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location /.well-known/ {
default_type "text/plain";
}
}
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# サーバ情報の非表示
server_tokens off;
# クリックジャッキング
add_header X-Frame-Options SAMEORIGIN;
# XSS対策
add_header X-XSS-Protection "1; mode=block";
# IEで発生するコンテンツタイプSniffing対策
add_header X-Content-Type-Options nosniff;
sendfile off;
etag off;
if_modified_since off;
keepalive_timeout 65;
gzip on;
include /etc/nginx/conf.d/*.conf;
}
- name: make nginx config file for laravel
template:
src: laravel.conf.j2
dest: /etc/nginx/conf.d/laravel.conf
- name: change nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify:
- restart nginx
- name: start nginx
systemd:
name: nginx.service
state: restarted
daemon_reload: yes
enabled: yes
###6.mysql
mysql関連のタスクを定義します。
├── mysql
│ └── tasks
│ ├── main.yml
│ └── templates
│ ├── init_my.cnf.j2
│ └── my.cnf.j2
[client]
user = root
password = {{ mysql_default_password.stdout }}
connect-expired-password
[client]
user = {{db_user}}
password = {{db_password}}
default-character-set = utf8mb4
[mysqld]
character-set-server = utf8mb4
skip-character-set-client-handshake
default-storage-engine = INNODB
collation-server = utf8mb4_general_ci
init-connect = SET NAMES utf8mb4
explicit_defaults_for_timestamp
datadir = /var/lib/mysql
socket = /var/lib/mysql/mysql.sock
symbolic-links = 0
log-error = /var/log/mysqld.log
pid-file = /var/run/mysqld/mysqld.pid
[mysqldump]
default-character-set=utf8mb4
[mysql]
default-character-set=utf8mb4
---
- block:
- name: remove mariadb-libs
yum:
state: absent
name: mariadb-libs
- name: install mysql repository
yum:
name: https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm
validate_certs: yes
- name: disable mysql80-community-server
shell: yum-config-manager --disable mysql80-community
- name: enable mysql57-community-server
shell: yum-config-manager --enable mysql57-community
- name: install mysql-community-server
yum:
state: present
name: ['mysql-community-server','MySQL-python']
- name: running and enabled mysqld
service:
name: mysqld
state: started
enabled: yes
- name: check .my.cnf exists
stat:
path: /root/.my.cnf
register: mycnf_file
- name: get temporary password
shell: cat /var/log/mysqld.log | grep "temporary password" | awk '{print $11}'
register: mysql_default_password
when: not mycnf_file.stat.exists
- name: deploy init .my.cnf
template:
src: init_my.cnf.j2
dest: /root/.my.cnf
owner: root
group: root
mode: 0644
when: not mycnf_file.stat.exists
- name: change password validation to the easy way
shell: |
mysql -u root -p'{{ mysql_default_password.stdout }}' --connect-expired-password -e "SET GLOBAL validate_password_length=4;"
mysql -u root -p'{{ mysql_default_password.stdout }}' --connect-expired-password -e "SET GLOBAL validate_password_policy=LOW;"
when: not mycnf_file.stat.exists
- name: change root user password
shell: |
mysql -u root -p'{{ mysql_default_password.stdout }}' --connect-expired-password -e "ALTER USER 'root'@'localhost' IDENTIFIED BY 'root';"
when: not mycnf_file.stat.exists
- name: deploy changed .my.cnf
template:
src: my.cnf.j2
dest: /root/.my.cnf
owner: root
group: root
mode: 0644
- name: deploy changed .my.cnf
template:
src: templates/my.cnf.j2
dest: /etc/my.cnf
- name: remove all anonymous user
mysql_user:
name: ''
state: absent
host: localhost
- name: create database
mysql_db:
name: "{{db_name}}"
state: present
encoding: utf8mb4
- name: create user
mysql_user:
name: vagrant
password: vagrant
priv: '*.*:ALL,GRANT'
state: present
- name: restart mysqld
service:
name: mysqld
state: restarted
when: ansible_facts['distribution'] == "Amazon"
###5.git_clone
リポジトリからgitcloneします。
├── git_clone
│ └── tasks
│ ├── main.yml
│ └── templates
│ └── env.j2
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://{{virtual_server_name}}
AUTH_URL=https://{{virtual_server_name}}
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE={{db_name}}
DB_USERNAME={{db_user}}
DB_PASSWORD={{db_password}}
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_DRIVER={{MAIL_DRIVER}}
MAIL_HOST={{MAIL_HOST}}
MAIL_PORT={{MAIL_PORT}}
MAIL_USERNAME={{MAIL_USERNAME}}
MAIL_PASSWORD={{MAIL_PASSWORD}}
MAIL_FROM_ADDRESS={{MAIL_FROM_ADDRESS}}
MAIL_FROM_NAME={{MAIL_FROM_NAME}}
MAIL_ENCRYPTION=null
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
---
- name: check_repos
stat:
path: "{{ git_repo_dest }}"
register: check_repos
- name: git clone
git:
repo: "{{ git_repo_url }}"
dest: "{{ git_repo_dest }}"
version: "{{ git_branch_name }}"
- block:
- name: disable git check permissions
shell: "cd {{ git_repo_dest }}; git config core.filemode false"
- name: change project permissions
file:
path: "{{ git_repo_dest }}"
recurse: yes
state: directory
owner: nginx
group: laravel
mode: '777'
when: not check_repos.stat.exists
- name: composer install
become: no
shell: "cd {{ git_repo_dest }}; composer install "
- name: check_env
stat:
path: "{{ git_repo_dest }}/.env"
register: check_env
- name: create .env
template:
src: env.j2
dest: "{{ git_repo_dest }}/.env"
owner: nginx
group: laravel
mode: 0777
when: not check_env.stat.exists
- name: generate APP_KEY
become: no
shell: "cd {{ git_repo_dest }};php artisan key:generate"
when: not check_env.stat.exists
- name: migration DB
shell: "cd {{ git_repo_dest }}; php artisan migrate"
register: migrate_result
changed_when: "migrate_result.stdout != 'Nothing to migrate.'"
when: ansible_facts['distribution'] == "Amazon"
###7.composer
- name: check composer
stat: path=/usr/local/bin/composer
register: composer_bin
tags: composer
- block:
- name: download composer
get_url:
url: https://getcomposer.org/installer
dest: /tmp/installer
- name: install composer
shell: cat /tmp/installer | php -- --install-dir=/usr/local/bin
- name: rename composer.phar to composer
shell: mv /usr/local/bin/composer.phar /usr/local/bin/composer
- name: make composer executable
file:
path: /usr/local/bin/composer
mode: a+x
state: file
when: not composer_bin.stat.exists
tags: composer
###6.lets'encrypt
lets'encryptでssl証明書を取得する処理を定義します。
├── letsencrypt
│ └── tasks
│ ├── main.yml
│ └── templates
| |── letsencrypt.j2
│ └── laravel-ssl.conf.j2
server {
listen 80;
server_name {{virtual_server_name}};
return 301 https://$host$request_uri;
}
server {
listen 443;
server_name {{virtual_server_name}};
root {{virtual_root_path}};
client_max_body_size 10m;
ssl on;
ssl_certificate /etc/letsencrypt/live/{{virtual_server_name}}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{virtual_server_name}}/privkey.pem;
ssl_prefer_server_ciphers on;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
location / {
index index.php index.html;
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location /.well-known/ {
default_type "text/plain";
}
}
* * 1 * * root letsencrypt renew --post-hook "service nginx restart"
---
- name: install python-simplejson
yum:
name: ['python-simplejson','openssl']
state: present
- name: install letsencrypt
yum:
name: ['letsencrypt']
state: present
- name: check letsencrypt folder
stat:
path: "/etc/letsencrypt/live/{{virtual_server_name}}"
register: letsencrypt
- name: Create SSL/TLS certificates
shell: "letsencrypt certonly --webroot -w {{ virtual_root_path }} -d {{ virtual_server_name }} --email {{ ssl_email }} --agree-tos --keep-until-expiring --non-interactive --debug"
when: not letsencrypt.stat.exists
- name: add virtualhost.conf for certificates settings
template:
src: laravel-ssl.conf.j2
dest: /etc/nginx/conf.d/laravel.conf
when: not letsencrypt.stat.exists
notify:
- restart nginx
- name: add cron for automatically cert update
template:
src: letsencrypt.j2
dest: /etc/cron.d/letsencrypt
owner: root
group: root
mode: '0600'
when: not letsencrypt.stat.exists
##7.playbookを作成する。
適当なディレクトリにplaybook.ymlを作成し編集します。
rolesには上で作ったリソースを定義します。作りたくないタスクがあればコメントアウトで実行しないように設定する事もできます。
ansible
├── playbook.yml
---
- hosts: aws #hostsファイルに記述したホストを指定します。
user: ec2-user #接続ユーザ
become: yes # root権限でコマンド実行
vars_files: #変数ファイル指定。後ほど作成します。
- ./vars/variables.yml
roles: #rolesディレクトリに今まで作ったファイルを読み込んでいます。
- common
- php
- mysql
- composer
- git_clone
- nginx
- letsencrypt
これらのファイルを作成した後以下のコマンドでansibleを実行します。
###playbook.ymlの構文があってるかチェック※実際にはansibleは実行されません。
ansible-playbook -i hosts playbook.yml --syntax-check
#大丈夫だと以下が返ってくる。
playbook: playbook.yml
###playbook実行
ansible-playbook -i hosts playbook.yml
これで自動構築が始まると思います。
終わりに
まだ環境ごとに作りたいリソースを分ける事ができなかったり、状況に応じての処理が甘いですが、laravelの実行環境を構築する事ができます。
ansibleはアプリケーションよりの自動化ソフトなので、インフラ構築などはterraformなどを使うとよいと思います。