はじめに
Ansibleを使って1台のEC2をyumリポジトリサーバに構成して、これを使って2台目以降のEC2にRPMパッケージをインストールしました。
yumリポジトリを自作した目的
- 重要なパッケージを検証済みのバージョンで揃えたい
- インターネットに接続しないサーバにもパッケージをインストールしたい
- 各サーバにパッケージをインストールする際のバージョン指定は全部「latest」とし、リポジトリに配置するパッケージでバージョンをコントロールしたい
- 各サーバにリポジトリ設定を追加したくない
環境
使用した環境は以下のとおりですが、物理マシンにCentOSの環境でも特に違いはありません。
- コントロールノード: AWS Cloud9
Ansibleを実行するマシンです - ターゲットノード: AWS EC2 / Amazon Linux 2
Ansibleで自動構成される対象ホストです
コントロールノード
Ansibleをインストールする必要があるノードはコントロールノードだけです。EPELからyumでインストールします。
sudo yum -y install epel-release # AWS Cloud9はインストール済み
sudo yum -y install ansible
ターゲットノード
自動構成の対象となるターゲットノードに必要な追加ソフトウェアはありません。
必要な環境はPython 2.6以上か3.5以上だけなので Amazon Linux、CentOS 6 以上などがそのまま使用できます。
インベントリとプレイブックの構成
今回の規模ならもっと単純でもいいのですが、規模によって構成をあまり変えたくないので、公式のベストプラクティスになるべく準拠して以下の構成にしました。
参考: Best Practices — Ansible Documentation
.
├── inventories # インベントリ用ディレクトリ
│ ├── development # 開発環境用インベントリ(今回の対象)
│ ├── production # 本番環境用インベントリ
│ └── staging # ステージング環境用インベントリ
│
├── roles # ロール用ディレクトリ
│ ├── common # 共通ロール
│ ├── httpd # HTTP Serverをインストールするロール
│ ├── openvpn # OpenVPNをインストールするロール
│ └── repo # リポジトリサーバを構成するロール
│
├── repo-servers.yml # リポジトリサーバのグループ用プレイブック
├── site.yml # マスタープレイブック
└── vpn-servers.yml # VPNサーバのグループ用プレイブック
インベントリ
inventories
ディレクトリの直下は環境ごとのディレクトリで、開発環境、ステージング環境、本番環境用のディレクトリを用意しました。今回は開発環境用のdevelopment
だけを使用します。
├── inventories
│ ├── development # 開発環境用(今回の対象)
│ ├── production # 本番環境用
│ └── staging # ステージング環境用
development
の下は以下のような構成になっています。
├── inventories
│ ├── development
│ │ ├── group_vars # グループごとの変数定義を格納するディレクトリ
│ │ │ └── all.yml # グループ「all」(全体)用の変数定義ファイル
│ │ ├── host_vars # ホストごとの変数定義を格納するディレクトリ
│ │ │ ├── repo-1.yml # ホスト「repo-1」用の変数定義ファイル
│ │ │ └── vpn-1.yml # ホスト「vpn-1」用の変数定義ファイル
│ │ └── hosts.yml # 開発環境用のインベントリ定義ファイル
インベントリ定義ファイル
hosts.yml
でホストとグループの全体を定義します。
書籍などではiniファイル形式で解説されていることが多いようですが、ここではYAML形式を使用します。
all: # グループ「all」(全体)
children: # allの子グループ
repo-servers: # グループ「repo-servers」
hosts: # repo-serversに属するホスト
repo-1 # yumリポジトリを構成するホスト
vpn-servers: # グループ「vpn-servers」
hosts: # vpn-serversに属するホスト
vpn-1 # openvpnをインストールするホスト
all
は全体を表す定義済みのグループ名です。今回はユーザー定義の子グループとしてrepo-servers
とvpn-servers
の2つを定義し、それぞれにターゲットノードのrepo-1
とvpn-1
を含めました。
グループ変数定義ファイル
group_vars
ディレクトリにはグループごとの変数定義ファイルを配置します。
今回は全ターゲットノードへのSSH接続に同じ秘密鍵を使用するので、全体用の変数としてall.yml
に定義しました。
ansible_ssh_private_key_file: ~/my-keypair.pem
ansible_ssh_private_key_file
は、SSH秘密鍵のパスを格納するための定義済み変数です。
ホスト変数定義ファイル
host_vars
ディレクトリにはホストごとの変数定義ファイルを配置します。
例えば以下のように定義済み変数ansible_host
に各ホストのIPアドレスを設定することで、「repo-1」「vpn-1」という名前で接続できるようになります。
ansible_host: 172.16.1.4
ansible_host: 172.16.1.5
ロール
プレイブック内の処理を再利用しやすくするために、分割してロールとして定義します。例えば Apache HTTP Server をインストールするロールは、リポジトリサーバの他に Webサーバでもそのまま利用できます。
roles
ディレクトリの下にユーザー定義のロール名でディレクトリを作ります。今回は以下の4つのロールを定義しました。
├── roles
│ ├── common # 共通ロール
│ ├── httpd # HTTP Serverをインストールするロール
│ ├── openvpn # OpenVPNをインストールするロール
│ └── repo # リポジトリサーバを構成するロール
参考: Roles — Ansible Documentation
共通ロール
共通ロールcommon
は、全ターゲットノードに共通の構成のために作りました。
ロール名のディレクトリの下に、転送するファイルを格納するfiles
とタスク定義ファイルを格納するtasks
ディレクトリを配置します。
├── roles
│ ├── common
│ │ ├── files # ターゲットノードに転送するファイルを格納するディレクトリ
│ │ │ └── etc
│ │ │ ├── yum.repos.d
│ │ │ │ └── myrepo.repo
│ │ │ └── hosts
│ │ └── tasks # タスク定義ファイルを格納するディレクトリ
│ │ └── main.yml
共通ロールのファイル
files
ディレクトリの下はディレクトリ構成もファイル名も自由です。今回はターゲットノードに配置するパスのとおりに、/etc/yum.repos.d/myrepo.repo
(リポジトリ設定ファイル)と/etc/hosts
(ホスト名とIPアドレスの設定ファイル)を配置しました。
自作のリポジトリサーバを他のホストから利用できるようにするために、以下のリポジトリ設定ファイルを用意します。
yum-plugin-priorities
がインストールされていれば、priority
に10より小さい値を設定することで標準設定のリポジトリよりも優先されるようになります。
[myrepo]
name=myrepo
baseurl=http://repo-1.local/repo/
priority=2
gpgcheck=0
enabled=0
report_instanceid=yes
他のホストがrepo-1.local
という名前でリポジトリサーバに接続できるようにするために、以下の/etc/hosts
ファイルも用意します。DNSを使用する場合は不要です。
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost6 localhost6.localdomain6
172.16.1.4 repo-1.local
共通ロールのタスク
-
copy
モジュールを使用して、files
で用意した/etc/hosts
ファイルを転送して配置します -
copy
モジュールを使用して、files
で用意したリポジトリ設定ファイルを転送して配置します -
yum
モジュールを使用して、インターネットのリポジトリから指定のパッケージをインストールします
- name: Put hosts file
copy:
dest=/etc/hosts
src=roles/common/files/etc/hosts
- name: Put repo file
copy:
dest=/etc/yum.repos.d/myrepo.repo
src=roles/common/files/etc/yum.repos.d/myrepo.repo
- name: Install common packages
yum:
name="{{item}}"
state=latest
with_items:
- yum-plugin-priorities
- bash-completion
with_items:
以下に定義したパッケージが順にインストールされます。
yum-plugin-priorities
は自作リポジトリを全ホストに優先利用させるために必須のパッケージです(Amazon Linux 2にはデフォルトでインストールされていますが、必須であることをここで明示します)。
その他、全ホストにインストールしたいパッケージをここにいくつでも追加定義することができます。私はbash-completion
が入っていないと耐えられないので追加しました。
各モジュールの公式ドキュメント:
copy – Copy files to remote locations
yum – Manages packages with the yum package manager
HTTP Serverロール
ロールhttpd
はApache HTTP Serverをインストールするために定義しました。
ターゲットノードに転送するファイルはないので、tasks
ディレクトリだけを配置します。
├── roles
│ ├── httpd
│ │ └── tasks
│ │ └── main.yml
HTTP Serverロールのタスク
-
yum
モジュールを使用して、パッケージhttpd
をインストールします -
service
モジュールを使用して、サービスhttpd
を起動状態にして自動実行にします
- name: Install HTTPD packages
yum:
name="{{item}}"
state=latest
with_items:
- httpd
- name: Start and enable the service
service:
name=httpd
state=started
enabled=yes
各モジュールの公式ドキュメント:
yum – Manages packages with the yum package manager
service – Manage services
OpenVPNロール
ロールopenvpn
はOpenVPNをインストールするために定義しました。
├── roles
│ ├── openvpn
│ │ └── tasks
│ │ └── main.yml
OpenVPNロールのタスク
自作リポジトリmyrepo
を有効化してopenvpn
パッケージをインストールするだけです。
- name: Install VPN packages
yum:
enablerepo=myrepo
name="{{item}}"
state=latest
with_items:
- openvpn
リポジトリロール
ロールrepo
は共通ロールおよびHTTP Serverロールとの組み合わせで、リポジトリサーバを構成するためのロールです。
files
とtasks
に加えて、タスクからの通知で実行されるハンドラを格納するためのhandlers
ディレクトリを持ちます。
├── roles
│ └── repo
│ ├── files
│ │ └── var
│ │ └── www
│ │ └── html
│ │ └── repo
│ │ └── Packages
│ │ └── openvpn
│ │ ├── lzo-2.06-8.amzn2.0.3.x86_64.rpm
│ │ ├── openvpn-2.4.7-1.el7.x86_64.rpm
│ │ └── pkcs11-helper-1.11-3.el7.x86_64.rpm
│ ├── handlers
│ │ └── main.yml
│ └── tasks
│ └── main.yml
リポジトリロールのファイル
yumリポジトリで公開するRPMパッケージファイルです。今回はOpenVPNと依存パッケージのみを配置します。HTTP Serverで公開するので、配置場所は/var/www/html/
の下にします。
リポジトリロールのタスク
-
yum
モジュールを使用して、リポジトリデータベースを更新するためのコマンドcreaterepo
をインストールします -
synchronize
モジュール(rsync)を使用して、RPMパッケージファイルをディレクトリごと/var/www/html/repo/
に配置します - ファイルが追加または変更されたら、通知
packages changed
を発行してハンドラを起動します。
- name: Install commands for repository server
yum:
name="{{item}}"
state=latest
with_items:
- createrepo
- name: Put repository packages
synchronize:
delete=yes
dest=/var/www/html/repo/
src=roles/repo/files/var/www/html/repo/Packages
notify: "packages changed"
各モジュールの公式ドキュメント:
yum – Manages packages with the yum package manager
synchronize – A wrapper around rsync to make common tasks in your playbooks quick and easy
リポジトリロールのハンドラ
タスクからpackages changed
が通知されたら、listen:
でこの通知をリスニングしている以下のハンドラが実行されます。ここではcreaterepo
コマンドを実行してリポジトリデータベースを更新します。
- name: Run createrepo
command: /usr/bin/createrepo /var/www/html/repo
listen: "packages changed"
コマンドcreaterepo /var/www/html/repo
を実行することで、指定のパスの直下にレポジトリデータベースが作成または更新されます。
var
└── www
└── html
└── repo
└── Packages
└── openvpn
├── lzo-2.06-8.amzn2.0.3.x86_64.rpm
├── openvpn-2.4.7-1.el7.x86_64.rpm
└── pkcs11-helper-1.11-3.el7.x86_64.rpm
/var/www/html/repo/repodata/
にデータベースが作成されます。
var
└── www
└── html
└── repo
├── Packages
│ └── openvpn
│ ├── lzo-2.06-8.amzn2.0.3.x86_64.rpm
│ ├── openvpn-2.4.7-1.el7.x86_64.rpm
│ └── pkcs11-helper-1.11-3.el7.x86_64.rpm
└── repodata
├── 4b2f....7463.xml.gz
├── 637d....275f.xml.gz
├── 6484....cacf-filelists.sqlite.bz2
├── e80e....9d8f-primary.sqlite.bz2
├── e9d8....df04-other.xml.gz
├── ffc4....ee24-other.sqlite.bz2
└── repomd.xml
これで/var/www/html/repo
がyumリポジトリになります。
/var/www/html
はHTTP Serverのドキュメントルートなので、他のホストに対してhttp://ホスト名/repo/
のURLでyumリポジトリを公開できるようになります。
プレイブック
リポジトリサーバのグループ用、VPNサーバのグループ用、およびそれら2つを順に実行するマスタープレイブックの、計3つのプレイブックを作成しました。
.
├── repo-servers.yml # リポジトリサーバのグループ用
├── site.yml # マスタープレイブック
└── vpn-servers.yml # VPNサーバのグループ用
2つのホストグループはインベントリ定義ファイルで以下のように定義したので、repo-servers
を対象としてansible-playbook
を実行するとホストrepo-1
が、vpn-servers
対象として実行するとホストvpn-1
が自動的に構成されます。
repo-servers: # グループ「repo-servers」
hosts: # repo-serversに属するホスト
repo-1 # yumリポジトリを構成するホスト
vpn-servers: # グループ「vpn-servers」
hosts: # vpn-serversに属するホスト
vpn-1 # openvpnをインストールするホスト
repo-serversグループ用プレイブック
ターゲットノードでsudo を使用して、ロールをhttpd
、repo
、common
の順に実行します。
このプレイブックが正常に終了すると自作のyumリポジトリが利用可能になります。
- name: Deploy repo servers
hosts: repo-servers
become: yes
roles:
- httpd
- repo
- common
vpn-serversグループ用プレイブック
ターゲットノードでsudo を使用して、ロールをcommon
、openvpn
の順に実行します。
openvpn
は自作リポジトリからパッケージを取得してインストールします。
- name: Deploy VPN servers
hosts: vpn-servers
become: yes
roles:
- common
- openvpn
マスタープレイブック
上記2つのプレイブックを順に実行します。
- import_playbook: repo-servers.yml
- import_playbook: backgate-servers.yml
プレイブックの実行
ansible-playbook
コマンドの-i
オプションでインベントリ定義ファイルを指定し、引数でマスタープレイブックを指定して実行します。
エラーが発生する場合は-vvv
などのオプションを追加して詳細を確認してください。
ansible-playbook -i inventories/development/hosts.yml site.yml
あとがき
Ansibleの進化が速いので、書籍などの情報がだんだん古くなっているようです。がんばって公式ドキュメントをもっと読もうと思います。
- 公式ドキュメント: Ansible Documentation