こんにちは。tacckです。
色々とコンテンツをデプロイする時に、「同じ手順を人力で繰り返す」ということは多々あると思います。
これは、我々エンジニア・プログラマの美徳の一つ「怠惰」に反する行為ですよね。
こういったものは、自動化してしまいましょう!
今回から、私の好きな構成管理ツール Ansible を使って、Webサイト・Webアプリケーションのデプロイを段階的に自動化していく手順を説明していきたいと思います。
今回は、Webサーバの簡単な設定と、HTMLファイルのアップロードの流れを見てみたいと思います。
インストール方法は公式の情報にあります。
macOSの場合は pip
を使う方法が書かれていますが、 Hombrew
を使っている方は brew install ansible
でもインストールできます。
Windowsの方は、公式には書かれていないですね。。。Docker
を使うか、 VirtualBox
へ Linuxディストリビューションをインストールして使うか、とすれば良いと思います。
私は macOS 10.14 + Homebrew
で環境を作っているので、この環境ベースで進めていきます。
ファイルをアップロードする先 (Webサーバ) をどこかに用意しましょう。
Webサーバは「SSHで接続できること」と「Python 2 or 3が使えること」が条件です。
このシリーズでは、手軽で安価にサーバを用意できる Amazon Lightsail の CentOS
イメージで作成したサーバを利用する想定で進めます。
ターミナルアプリで、コマンドライン操作が可能であることを前提とします。
まず、 Apache HTTP Server を用意しつつ、シンプルにHTMLファイルのみをアップロードすることをやってみましょう。
必要なファイルは、こちらのようになります。それぞれ見ていきましょう。
.
├── ansible.cfg
├── inventory
├── main.yml
├── roles
│ └── upload
│ ├── files
│ │ └── index.html
│ └── tasks
│ └── main.yml
└── ssh
└── web_testing.pem
inventory
ファイルは、サーバへの接続情報となります。
Ansible では INI形式と YAML形式 のどちらかで指定できますが、今回は歴史的に長く使われている INI形式にしています。
[web]
xx.xx.xx.xx
[web:vars]
ansible_port=22
ansible_user=centos
ansible_ssh_private_key_file=ssh/web_testing.pem
xx.xx.xx.xx
はサーバのIPアドレスを指定します。(FQDNでも大丈夫です)
また1行目の [web]
のようにサーバをグルーピングすることが可能です。
4行目~7行目は、[web]
グループのサーバ全体への接続オプションとなります。
ここでは、ansible_port
(ssh接続時のポート番号) 、 ansible_user
(ssh接続時のユーザ名) 、 ansible_ssh_private_key_file
(ssh接続時の秘密鍵) を指定しています。
この辺りの内容は、実際に接続するサーバに合わせて変更してください。
今回必要なのは main.yml
と roles
配下のファイル群です。
まずは、main.yml
から。
---
- hosts: web
become: yes
roles:
- upload
hosts
は、先の inventory
で指定したサーバ(のグループ)を指定します。
全てのサーバを対象にする場合には all
とします。
become
は、いわゆる sudo
などの権限変更しての作業を行なうか、という項目です。
ここではWebサーバの設定や、HTMLファイルの配置場所に色々権限が必要なので、 yes
としています。
特にユーザ名を指定しなければ root
ユーザとして実行することになります。
roles
は、具体的な作業が書かれたロール名を指定します。
お気づきの通り、この upload
と roles/upload
が対応することになります。
この書き方 (Roles) は、Ansible自身の推奨する書き方で、こうすることでタスク単位で実行したい内容、必要なファイル、といったものを集中管理できるようになります。
うまく作っていれば他への流用もやりやすくなるので、この形で進めることをお勧めします。
今回は、実際にやりたいことを roles/upload/tasks/main.yml
に、アップロードしたいファイルを roles/upload/files/index.html
に格納しています。
まずは roles/upload/tasks/main.yml
を見てみましょう。
---
- name: set timezon Asia/Tokyo
shell: timedatectl set-timezone Asia/Tokyo
- name: install httpd
yum:
name: httpd
state: latest
- name: enable httpd
systemd:
name: httpd
enabled: yes
- name: restart httpd
systemd:
name: httpd
state: restarted
- name: upload
copy:
src: index.html
dest: /var/www/html
name
は実行するタスクの内容説明、になります。無くても実行はできますが、実行結果を見た時に何をやっているのかわからなくなることもあるので、きちんと具体的な内容で書いておきましょう。
ここでは、上から順番に "サーバのタイムゾーン変更" 、 "Apache HTTP Server のインストール(すでにあれば最新化)" 、 "Apache HTTP Server の有効化(マシン再起動時に起動されるようにする)" 、 "Apache HTTP Server の起動(すでに起動済みなら再起動)" 、 "HTMLファイルのアップロード" を行なっています。
タスク実行に使うモジュールは多くの種類があるので、自分が必要そうなものがあるか適宜検索してみてください。
今回アップロードしたい roles/upload/files/index.html
を用意します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Ansible1</title>
</head>
<body>
This is a sample HTML.
</body>
</html>
今回は特に凝ったことをしないので、中身はなんでも良いです。
また、 Ansible が ssh接続するために必要な秘密鍵も ssh/web_testing.pem
に配置します。
このファイルは "所有者のみの読み取り専用" でないといけません。(macOS / Linux などの場合)
$ chmod 600 ssh/web_testing.pem
などを実行しておきましょう。
では、実行してみます。
$ ansible-playbook -i inventory main.yml
PLAY [web] *********************************************************************************************************************************
TASK [Gathering Facts] *********************************************************************************************************************
ok: [xx.xx.xx.xx]
TASK [upload : set timezon Asia/Tokyo] *****************************************************************************************************
changed: [xx.xx.xx.xx]
TASK [upload : install httpd] **************************************************************************************************************
changed: [xx.xx.xx.xx]
TASK [upload : enable httpd] ***************************************************************************************************************
changed: [xx.xx.xx.xx]
TASK [upload : restart httpd] **************************************************************************************************************
changed: [xx.xx.xx.xx]
TASK [upload : upload] *********************************************************************************************************************
changed: [xx.xx.xx.xx]
PLAY RECAP *********************************************************************************************************************************
xx.xx.xx.xx : ok=6 changed=5 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
無事に実行できました!
ブラウザでサーバを開くと、先ほどのHTMLファイルが見えていると思います。
突然ですが、我々はエンジニアリングというサバンナにいます。
ここでは、テストを書かないと生き残ることができません。
サバンナで生き残るために、「Webサーバがきちんと動いているか」をテストコードを使って確認してみましょう。
やり方は色々とあると思いますが、今回は Node.js のテスティングフレームワークである Jest を使って書いてみます。
Node.js と Yarn が利用可能な状態、という前提で進めます。
パッケージは Jest の他に Axios を使います。
下記のように実行して、パッケージをインストールしていきます。
$ mkdir tests
$ cd tests
$ yarn init -y
$ yarn add -D jest axios
実行できたら、 package.json
というファイルが作成されているはずです。
こちらを少し書き換えて、下記のように "jest" と "scripts" を追加しましょう。
{
"name": "tests",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"axios": "^0.19.2",
"jest": "^25.1.0"
},
"jest": {
"testEnvironment": "node"
},
"scripts": {
"test": "jest index.test.js"
}
}
では、テストコードを書いてみましょう。
今回はあまり難しく考えずに、下記2点のテストにしてみたいと思います。
この辺りを工夫すれば、Webページをブラウザで開かなくてもテストできるようになっていきます。
const TARGET_URL = 'http://xx.xx.xx.xx'
const CONTENTS = 'This is a sample HTML.'
const axiosBase = require('axios')
const axios = axiosBase.create({
baseURL: TARGET_URL,
})
describe('check top page', () => {
it('get top page', async () => {
const response = await axios.get('/')
expect(response.status).toEqual(200)
})
it('check contents', async () => {
const response = await axios.get('/')
const reg = new RegExp(CONTENTS, 'i')
expect(reg.test(response.data)).toEqual(true)
})
})
作成できたら、下記のようなファイル構成になっているはずです。
tests
├── index.test.js
├── node_modules
├── package.json
└── yarn.lock
では、 tests
ディレクトリの中で yarn test
と実行してみましょう。
$ yarn test
yarn run v1.21.1
$ jest index.test.js
PASS ./index.test.js
check top page
✓ get top page (66ms)
✓ check contents (51ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 1.325s, estimated 2s
Ran all test suites matching /index.test.js/i.
✨ Done in 3.39s.
$
無事に実行できました!
テストも全てパスできているので、 Ansible を使って Webサーバを起動し、ファイルのアップロードもできたことが確認できました。
これで、サバンナでも生き残れますね。🦁