はじめに
前回はアプリレイヤーまでの自動化を実現しました。今回は、これに加えテスト用サーバの作成まで含めて自動化してみたので、やったことをメモとして残しておきます。
具体的には、今回は
- Ansibleでブータブルディスクを使いテスト用のVMをデプロイ
- Serverspecでインフラテスト
- Ansibleで使い終わったVMの削除
を実行するジョブを作成しました。
Ansibleは仕事で少し使ってて覚えがあったから&設定項目をymlでシンプルに書けるから、Serverspecはテスト項目を直感的に書けるから、という理由で採用しました。
VMデプロイに関してはテンプレートからコピーすればいいのでは……?と思う人もいるかもしれません。が、今回のケースでは無償版のvSphereを使っているため、テンプレート・vMotionを始めとする便利な機能をほぼ使えず、ブータブルメディアを使ってOSをインストールするしかなかったのです。つらい。
システム概要
赤字が今回新しく作る項目です。
ちなみに前まで使ってたWin機は買い替えJenkinsもCentOSに移植しました。何もしなくてもシェルスクリプトを使える喜びが胸いっぱいに広がります。
やったこと
AnsibleやServerspecのインストールについては公式ドキュメント見たほうが早いので省略します。
isoファイルにks.cfgを仕込む
Kickstart による自動インストール用 .iso ファイルを作成するにはを参考に、kickstartを仕込んだブータブルディスクを作成しました。なお、この作業はJenkins入れたCentOS上で実行しました。
CentOSの公式サイトからCentOS 7.7(Minimal)のisoファイルをダウンロードし、中身を抜きます。
# 以下の作業は全てrootで実行
wget http://ftp.riken.jp/Linux/centos/7.7.1908/isos/x86_64/CentOS-7-x86_64-Minimal-1908.iso
# isoをマウント
mkdir /root/mountpoint
mount -t iso9660 CentOS-7-x86_64-Minimal-1908.iso /root/mountpoint
# isoの中身をコピー
mkdir /root/workspace
cp -r /root/mountpoint /root/workspace
その後、/root
直下にあるanaconda-ks.cfg
をコピーし、kickstart用のcfgを作成します。作成したks.cfg
は/root/workspace
直下に置きます。
#version=DEVEL
# System authorization information
auth --enableshadow --passalgo=sha512
# Use CDROM installation media
cdrom
# Use graphical install
text
# Run the Setup Agent on first boot
firstboot --enable
ignoredisk --only-use=sda
# Keyboard layouts
keyboard --vckeymap=jp --xlayouts='jp'
# System language
lang ja_JP.UTF-8
# Network information
network --bootproto=static --device=link --gateway=<デフォルトGWのip> --ip=<固定ip> --netmask=<サブネットマスク> --nameserver=8.8.8.8 --noipv6
network --hostname=<ホスト名>
# Root password
rootpw --iscrypted <ハッシュ化したパスワード(/etc/shadowから抜き取る等して作成)>
# System services
services --disabled="chronyd"
# System timezone
timezone Asia/Tokyo --isUtc --nontp
# System bootloader configuration
zerombr
bootloader --location=mbr --boot-drive=sda
# Partition clearing information
clearpart --none --initlabel
# Disk partitioning information
autopart --type="thinp" --fstype="xfs" --nohome
%packages
@^minimal
@core
%end
%addon com_redhat_kdump --disable --reserve-mb='auto'
%end
%anaconda
pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty
pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok
pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty
%end
# reboot when finish installing
reboot --eject
次に、isolinux/isolinux.cfg
を以下の様に書き換えます。
#default vesamenu.c32
default kickstart
timeout 600
display boot.msg
# Clear the screen when exiting the menu, instead of leaving the menu displayed.
# For vesamenu, this means the graphical background is still displayed without
# the menu itself for as long as the screen remains in graphics mode.
menu clear
menu background splash.png
menu title CentOS 7
menu vshift 8
menu rows 18
menu margin 8
#menu hidden
menu helpmsgrow 15
menu tabmsgrow 13
# Border Area
menu color border * #00000000 #00000000 none
# Selected item
menu color sel 0 #ffffffff #00000000 none
# Title bar
menu color title 0 #ff7ba3d0 #00000000 none
# Press [Tab] message
menu color tabmsg 0 #ff3a6496 #00000000 none
# Unselected menu item
menu color unsel 0 #84b8ffff #00000000 none
# Selected hotkey
menu color hotsel 0 #84b8ffff #00000000 none
# Unselected hotkey
menu color hotkey 0 #ffffffff #00000000 none
# Help text
menu color help 0 #ffffffff #00000000 none
# A scrollbar of some type? Not sure.
menu color scrollbar 0 #ffffffff #ff355594 none
# Timeout msg
menu color timeout 0 #ffffffff #00000000 none
menu color timeout_msg 0 #ffffffff #00000000 none
# Command prompt text
menu color cmdmark 0 #84b8ffff #00000000 none
menu color cmdline 0 #ffffffff #00000000 none
# Do not display the actual menu unless the user presses a key. All that is displayed is a timeout message.
menu tabmsg Press Tab for full configuration options on menu items.
menu separator # insert an empty line
menu separator # insert an empty line
label linux
menu label ^Install CentOS 7
kernel vmlinuz
append initrd=initrd.img inst.stage2=hd:LABEL=CentOS\x207\x20x86_64 quiet
label check
menu label Test this ^media & install CentOS 7
#menu default (comment out-ed)
kernel vmlinuz
append initrd=initrd.img inst.stage2=hd:LABEL=CentOS\x207\x20x86_64 rd.live.check quiet
# kickstart menu
label kickstart
menu label ^Kickstart Installation of CentOS 7
menu default
kernel vmlinuz
append initrd=initrd.img inst.stage2=hd:LABEL=CentOS\x207\x20x86_64 inst.ks=cdrom:/ks.cfg
menu separator # insert an empty line
# utilities submenu
menu begin ^Troubleshooting
menu title Troubleshooting
label vesa
menu indent count 5
menu label Install CentOS 7 in ^basic graphics mode
text help
Try this option out if you're having trouble installing
CentOS 7.
endtext
kernel vmlinuz
append initrd=initrd.img inst.stage2=hd:LABEL=CentOS\x207\x20x86_64 xdriver=vesa nomodeset quiet
label rescue
menu indent count 5
menu label ^Rescue a CentOS system
text help
If the system will not boot, this lets you access files
and edit config files to try to get it booting again.
endtext
kernel vmlinuz
append initrd=initrd.img inst.stage2=hd:LABEL=CentOS\x207\x20x86_64 rescue quiet
label memtest
menu label Run a ^memory test
text help
If your system is having issues, a problem with your
system's memory may be the cause. Use this utility to
see if the memory is working correctly.
endtext
kernel memtest
menu separator # insert an empty line
label local
menu label Boot from ^local drive
localboot 0xffff
menu separator # insert an empty line
menu separator # insert an empty line
label returntomain
menu label Return to ^main menu
menu exit
menu end
以上が終わったら下記コマンドでブータブルiso(centos7_ks.iso
)を作成し、Web Client経由でESXi側に置いておきます。
cd /root/workspace
mkisofs -J -T -o /root/centos7_ks.iso -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -R -m TRANS.TBL -graft-points -V "CentOS 7 x86_64" /root/workspace
Ansible Playbookを作成
今回は以下の3つのPlaybookを作成します。
- VM作成用
- サーバ基本設定用
- VM削除用
今回はAnsible公式ドキュメントのBest Practiceを元に以下のファイル構成を採用しました。VM作成・基本設定・削除ごとにディレクトリを分けています。
.
├── ansible.cfg # sshで設定流す際のオプション等を記述
├── roles # 設定用のymlを格納
│ ├── <role A>
│ │ └── tasks
│ │ ├── <設定実行用のyml>.yml
│ │ │ :
│ │ └── main.yml
│ └── <role B>
│ : └── tasks
│ ├── <設定実行用のyml>.yml
│ │ :
│ └── main.yml
├── site.yml # 読み込むplaybookを記述
├── hosts # inventoryを記述
└── testservers.yml # hostsで定義したグループと実行対象のroleの組み合わせを定義
ここで、tasks以下には以下のディレクトリを配置します(各ディレクトリに置くymlについては後述)
- VM作成
-
prepare
: VM作成準備用 -
esxi
: VM作成用
-
- VM設定
setting
- VM削除
tideup
共通設定として、ansible.cfg
とhosts
と、各roleのmain.yml
、そしてsite.yml
は以下の様に作成しました。
ansible.cfg
のdeprecation_warning
は、「将来このモジュールは使えなくなりますよ〜〜〜」という警告を出さなくするものなので、不要っちゃ不要かもしれません。
[defaults]
deprecation_warnings = False
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
[prepare]
<ESXiのIPアドレス>
[esxi]
<JenkinsサーバのIPアドレス>
[vm]
<テスト用サーバのIPアドレス>
[tideup]
<JenkinsサーバのIPアドレス>
- name: <roleの説明>
include_tasks: <実行するyml>.yml
- name: include required playbooks
import_playbook: testservers.yml
VM作成用
AnsibleにはESXi上にVMを作成するためのモジュールとして、vmware_guest
とvsphere_guest
があります。他の記事でも言われていますが、無償版vSphereではvsphere_guest
でないとVMの作成が出来ない&vsphere_guest
はAnsibleのバージョンが2.8以下でないと使えないので注意しましょう。
また、Ansibleの実行サーバでvsphere_guest
を使うために、
証明書エラーが出た場合はvalidate_certs
でno
を指定してエラーを回避しましょう。
先に.vmdkファイルなどを格納するディレクトリを作成するPlaybookも実行していますが、ぶっちゃけ不要かもしれません。。。
- hosts: prepare
roles:
- prepare
- hosts: esxi
roles:
- deploy
- name: Create directory for VM
file:
path: "/vmfs/volumes/datastore1/<テスト用VMのホスト名>"
state: directory
mode: 0755
- name: Create a virtual machine on given ESXi
vsphere_guest:
vcenter_hostname: <ESXiのIPアドレス>
username: root
password: "<ESXiのパスワード>"
guest: <VMのホスト名>
state: powered_on
vm_extra_config:
vcpu.hotadd: yes
mem.hotadd: yes
notes: This VM is used for app test
validate_certs: no
vm_disk:
disk1:
size_gb: 50
type: thin
datastore: datastore1
vm_hardware:
memory_mb: 4096
num_cpus: 2
osid: centos64Guest
scsi: paravirtual
vm_cdrom:
type: iso
iso_path: "datastore1/iso/centos7_ks.iso"
vm_nic:
nic1:
type: vmxnet3
network: VM Network
network_type: standard
esxi:
hostname: <ESXiのホスト名>
datacenter: ha-datacenter
基本設定用
VM作成後に以下の基本設定用のplaybookを作成します。
rpm_key
モジュールで使用するリポジトリのGPG KEYを追加しておかないとyum install
時にエラーとなるので注意が必要です。
- hosts: vm
roles:
- setting
# epelを追加
- name: enable epel repository
yum_repository:
name: epel
description: EPEL YUM repo
baseurl: http://ftp.riken.jp/Linux/fedora/epel//7/x86_64/
# base用のGPG KEYを追加
- name: exec rpm --install (base/extras/updates)
rpm_key:
state: present
key: http://ftp.riken.jp/Linux/centos/RPM-GPG-KEY-CentOS-7
# epel用のGPG KEYを追加
- name: exec rpm --install (epel)
rpm_key:
state: present
key: http://ftp.riken.jp/Linux/fedora/epel/RPM-GPG-KEY-EPEL-7
- name: install required packages
yum:
name: "{{ packages }}"
state: latest
vars:
packages:
- "http://ftp.riken.jp/Linux/remi/enterprise/remi-release-7.rpm"
- python3
- python2-pip
- name: install required packages with pip
pip:
name: "{{ modules }}"
executable: pip3.6
vars:
modules:
- requests
- bs4
- numpy
テスト用VM削除用
- hosts: tideup
roles:
- tideup
- name: Destroy a virtual machine
vsphere_guest:
vcenter_hostname: <ESXiのIPアドレス>
username: "<ユーザ名>"
password: "<パスワード>"
guest: <削除したいVMのホスト名>
state: absent
force: yes
Serverspecを使ったインフラテスト自動化
以下の項目について単体テストを書きます。
- IPアドレス・デフォルトGWを適切に設定できているか?
- 必要なyumレポジトリを有効にできているか?
- 必要なserviceを
yum install
できているか? - 必要なパッケージを
pip install
できているか?
serverspec-init
コマンドを叩いてServerspec実行ファイルの雛形を作成し、以下の様にテスト用のコードを書きます。
require 'spec_helper'
# ネットワーク設定の確認
describe host('<ホスト名 or IPアドレス>') do
its(:ipaddress) { should eq '<IPアドレス>' }
end
describe default_gateway do
its(:ipaddress) { should eq '<デフォルトGW>' }
end
# epelリポジトリがあるか確認
describe yumrepo('epel'), :if => os[:family] == 'redhat' do
it { should exist }
it { should be_enabled }
end
# Pythonまわりのyum installが実行されているか確認
describe package('python2-pip'), :if => os[:family] == 'redhat' do
it { should be_installed }
end
describe package('python3'), :if => os[:family] == 'redhat' do
it { should be_installed }
end
# 必要なパッケージがpip installされているか確認
describe package('requests') do
it { should be_installed.by('pip3') }
end
describe package('bs4') do
it { should be_installed.by('pip3') }
end
describe package('numpy') do
it { should be_installed.by('pip3') }
end
ここで、実行ユーザをrootで指定するため、spec_helper.rb
を以下の様に変更します。
require 'serverspec'
require 'net/ssh'
set :backend, :ssh
# sudoパスワード用の設定
if ENV['ASK_SUDO_PASSWORD']
begin
require 'highline/import'
rescue LoadError
fail "highline is not available. Try installing it."
end
set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false }
else
set :sudo_password, ENV['SUDO_PASSWORD']
end
host = ENV['TARGET_HOST']
options = Net::SSH::Config.for(host)
options[:user] = 'root'
options[:paranoid] = false
options[:user_known_hosts_file] = "/dev/null"
set :host, options[:host_name] || host
set :ssh_options, options
# ログインパスワード用の設定
if ENV['ASK_LOGIN_PASSWORD']
options[:password] = ask("\nEnter login password: ") { |q| q.echo = false }
else
options[:password] = ENV['LOGIN_PASSWORD']
end
set :ssh_options, options
Jenkinsジョブの作成・修正
PlaybookをGitLabからcloneし、Ansibleを実行するジョブを作成します。このジョブはパイプラインでの実行を前提とするので、GitLabへのpush/mergeをトリガーとする様な設定は特に行いません。
VM作成用ジョブ・基本設定用ジョブ・VM削除用ジョブ
以下のスクリプトを実行するジョブを作成します。
基本設定用ジョブではkickstartによるOS自動インストールが完了し、Ansibleによる設定を流せる状態になるまで待機する必要があります。OSインストールにはおおよそ8分かかるため、480秒待機する設定にしてあります。
正直頭の悪い処理だと思うので、より良い方法があったら教えていただきたいです………
sleep 480 # 基本設定用ジョブのみ
# ansibleによる基本設定を実行
ansible-playbook -i hosts site.yml --diff
インフラテスト用ジョブ
以下のスクリプトを実行するジョブを作成します。
LOGIN_PASSWORD=<テスト用サーバのログインパスワード> /usr/local/bin/rake spec
アプリテスト用ジョブ
アプリのテストはテストサーバ上で実行するので、前回作成したテスト用ジョブで実行するスクリプトを、以下の様に書き換えます。
echo "ファイルを転送"
sshpass -p "<パスワード>" scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null * root@<IPアドレス>:/root/
echo "テストスクリプトを実行"
sshpass -p "<パスワード>" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@<IPアドレス> "python3 tinder_auto.py 10"
パイプライン
前回作成したパイプラインを以下の様に書き換えます。
# VM作成、VM基本設定実行、VMテスト実行、テスト環境の削除、を追加
node {
stage("VM作成"){
build job: "deploy-testserver"
}
stage("VM基本設定実行"){
build job: "setup-testserver"
}
stage("VMテスト実行"){
build job: "check-testserver"
}
stage("Tinderコードのテスト"){
build job: "tinder_test"
}
stage("コード配布"){
build job: "tinder_pull"
}
stage("テスト環境の削除"){
build job: "tideup-testserver"
}
}
実行してみようのコーナー
上手く設定できていれば以下の様に実行されます。
所感
作っといてアレですが、コードpush→コード配布&テスト環境消滅まで11分弱かかるので、コードの修正を速やかに反映できているとは正直言い難いなぁという感じがあります……Docker使った方が簡単&高速にやりたいことを実現出来る気がします(あたりまえ)
何らかの事情でDockerを使えない現場(あるのか?)であれば、この方法を採用する意味もあるのかもしれません。
それから今回作成したPlaybookは余り綺麗でないので、構成などもう少し整理したいです。。。
やっぱりまだまだ初心者なので、知見ある方のコメントツッコミなどお待ちしております。