サーバの設定を機械化したいと考えました。
IaC (Infrastructure as Code) という分野は知っている。 Chef や Puppet 、 Ansible といったツールも存在は知っており、3年ほど前に非常に簡単な内容ではあるものの、 Ansible を軽く触ったこともありました。
今回はその再来となります。
ただし、「 Ansibleコントロールノード を実マシンではなく Dockerコンテナ として構築する」という部分が今回の新たなチャレンジです。
環境
今回の試験環境です。
- ホストコンピュータ: Windows 10 Pro
- IP:
YYY.YYY.YYY.YYY
- IP:
- Docker: Docker for Windows (version 19.03.13)
- リモートサーバ: CentOS 7.6
- IP:
XXX.XXX.XXX.XXX
- SH接続用ユーザ:
SSH_REMOTEUSER
- 管理者ユーザ:
ADMIN_USER
- IP:
設定
適当なディレクトリを掘って各種設定ファイルを用意します。
ディレクトリ階層
PROJECT_ROOT/
├ workspace/ // データ永続化領域
│ ├ entrypoint.sh // Dockerコンテナ起動時に実行するシェルスクリプト
│ ├ get_packages.yml // Ansible の playbook (リモートサーバのパッケージとバージョンの一覧を取得)
│ ├ hosts // リモートサーバの一覧 (今回は1つのみ)
│ └ package_list.j2 // パッケージとバージョンの一覧のリストのテンプレート
│
├ docker-compose.yml // Docker Compose 設定ファイル
└ Dockerfile // Dockerfile
一揃いをGithubにも置いてみました。
Docker
Docker に関する設定。
Dockerfile
FROM centos
RUN yum -y install epel-release
RUN yum install -y sudo
RUN yum -y install openssh-clients
RUN yum -y install ansible
RUN mkdir /workspace
Ansible 本体の他、 SSHクライアント として openssh-clients
、あとエントリポイントのシェルスクリプト実行のために sudo
も入れます。
docker-compose.yml
version: '3.1'
services:
ansible:
build: .
volumes:
- ./workspace:/workspace
tty: true
entrypoint: bash -c "bash /workspace/entrypoint.sh && /bin/bash"
データ永続化のための volumes
の指定と entrypoint
の指定をしています。
tty: true
にしているのですが entrypoint: bash -c "bash /workspace/entrypoint.sh"
だと Dockerコンテナがexitしてしまう現象に遭遇したので && /bin/bash
でもう一度 bash を起動しています。
entrypoint.sh
#!/bin/bash
FILENAME="PRIVATE_KEY"
PUBFILENAME="PRIVATE_KEY.pub"
ORIGINPATH="/workspace/"
COPYPATH="/root/.ssh/"
# ssh key generating
if [ ! -e $ORIGINPATH$FILENAME ]; then
sudo ssh-keygen -t rsa -b 4096 -f $ORIGINPATH$FILENAME -N ""
echo "success: generating ssh key"
else
echo "no operation: ssh key"
fi
# dir
if [ ! -d $COPYPATH ]; then
# mkdir
sudo mkdir $COPYPATH
# chmod
sudo chmod 600 $COPYPATH
echo "success: mkdir"
else
echo "no operation: mkdir"
fi
if [ ! -e $COPYPATH$FILENAME ]; then
# file copy
sudo cp $ORIGINPATH$FILENAME $COPYPATH$FILENAME
# chown
sudo chown root:root $COPYPATH$FILENAME
# chmod
sudo chmod 600 $COPYPATH$FILENAME
echo "success: copy ssh private key"
else
echo "no operation: copy ssh private key"
fi
if [ ! -e $COPYPATH$PUBFILENAME ]; then
# file copy
sudo cp $ORIGINPATH$PUBFILENAME $COPYPATH$PUBFILENAME
# chown
sudo chown root:root $COPYPATH$PUBFILENAME
# chmod
sudo chmod 600 $COPYPATH$PUBFILENAME
echo "success: copy ssh private key"
else
echo "no operation: copy ssh public key"
fi
エントリポイントは以下のことを実行しています。
- データ永続化領域 にSSH接続の公開鍵認証のための秘密鍵が存在しなければ生成
- ディレクトリが存在していなければディレクトリを作成します
- これは、データ永続化領域 にある鍵をそのまま公開鍵認証に使用するとパーミッションで怒られるので データ永続化領域 から Dokerコンテナのみの領域に鍵をコピーするためです
- 参考: Docker ComposeでSecretを使ってホストのssh秘密鍵を共有する – nullpo.io
- 今回はテストなので……
- 2.で作成したディレクトリへ公開鍵認証のための秘密鍵と公開鍵をコピーし、所有者・権限を設定
Ansible
Ansible に関する設定。
今回は Ansible の動作試験として「リモートサーバのパッケージとバージョンの一覧を取得する」ため、以下の記事の内容をお借りしました。
get_packages.yml
- name: Get packages from hosts
become: yes
become_user: ADMIN_USER
become_method: su
hosts:
- test_servers
tasks:
- name: Get packages
package_facts:
manager: auto
become: true
- name: Output packages
template:
src: ./package_list.j2
dest: "/workspace/{{ inventory_hostname }}_packages"
mode: '0644'
delegate_to: localhost
ほぼ上述記事の内容そのままですが、3ヶ所ほど変更を。
- SSHユーザは管理者権限を持たないため昇格させます。そのために
become
,become_user
,become_method
を追加 - 今回はホスト名ではなくIPアドレスでアクセスするため、
inventory_hostname_short
(接続対象の最初の.
までの文字列を取得するマジック変数)だと最初のオクテットしか拾えず何のことやらさっぱりなので、今回はフルネームのinventory_hostname
に変更 template
のdest
で存在しないディレクトリを指定するとDestination directory /PATH/TO/FILEBASE/ does not exist
エラーで怒られてしまうので、ホスト名のディレクトリの中にpackage
というファイルを作成するのではなく、ホスト名_package
というファイルを作成することにしました
hosts
[test_servers]
XXX.XXX.XXX.XXX
サーバのIPを指定。
package_list.j2
# {{ inventory_hostname }}
{% if ansible_facts.os_family == 'RedHat' %}
{% for package_name in ansible_facts.packages.keys()|sort %}
{% for package in ansible_facts.packages[package_name] %}
{{ package['name'] }}-{{package['version']}}-{{package['release']}}.{{package['arch']}}
{% endfor %}
{% endfor %}
{% elif ansible_facts.os_family == 'Debian' %}
{% for package_name in ansible_facts.packages.keys()|sort %}
{% for package in ansible_facts.packages[package_name] %}
{{ [package['name'], package['version']] | join('_') }}
{% endfor %}
{% endfor %}
{% endif %}
こちらもAnsible を利用したシステム構成情報の自動取得 – SIDfm 脆弱性の管理と対策に纏わる話の内容ほぼそのまま。
ただし、 get_packages.yml
と同様 inventory_hostname_short
だと識別不可なので inventory_hostname
に変更しました。
手順
Docker
設定を一通り揃えたところでまずは Dokerコンテナ をビルドします。
PowerShell を管理者実行で起動し、設定ファイルを置いたプロジェクトのディレクトリまで移動します。
> docker-compose up -d
Creating network "ANSIBLE_TEST_default" with the default driver
Building ansible
Step 1/6 : FROM centos
---> AAAAAAAAAAAA
Step 2/6 : RUN yum -y install epel-release
---> Running in BBBBBBBBBBBB
## 略
Complete!
Removing intermediate container BBBBBBBBBBBB
---> CCCCCCCCCCCC
Step 3/6 : RUN yum install -y sudo
---> Running in DDDDDDDDDDDD
## 略
Complete!
Removing intermediate container DDDDDDDDDDDD
---> EEEEEEEEEEEE
Step 4/6 : RUN yum -y install openssh-clients
---> Running in FFFFFFFFFFFF
## 略
Complete!
Removing intermediate container FFFFFFFFFFFF
---> GGGGGGGGGGGG
Step 5/6 : RUN yum -y install ansible
---> Running in HHHHHHHHHHHH
## 略
Complete!
Removing intermediate container HHHHHHHHHHHH
---> IIIIIIIIIIII
Step 6/6 : RUN mkdir /workspace
---> Running in JJJJJJJJJJJJ
Removing intermediate container JJJJJJJJJJJJ
---> KKKKKKKKKKKK
Successfully built KKKKKKKKKKKK
Successfully tagged ANSIBLE_TEST_default:latest
WARNING: Image for service ansible was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating ANSIBLE_TEST_default_1 ... done
OKです。
> docker-compose exec ansible /bin/bash
#
bash でログインもできました。
# ls -al /root/.ssh/
total 16
drw------- 2 root root 4096 Mon dd hh:ii .
dr-xr-x--- 1 root root 4096 Mon dd hh:ii ..
-rw------- 1 root root 3381 Mon dd hh:ii PRIVATE_KEY
-rw------- 1 root root 743 Mon dd hh:ii PRIVATE_KEY.pub
コピー・権限設定もOKですね。
リモートサーバ側の設定
さてここで閑話休題。公開鍵認証のためにリモートサーバに上述の公開鍵、つまり PRIVATE_KEY.pub
を送ります。
方法は何でも良いのですが、ディレクトリを作成しなければならなかったので手っ取り早く手作業で作りました。
$ su -
# vi /etc/ssh/sshd_config
#PermitRootLogin yes
PermitRootLogin no
#PermitEmptyPasswords no
PermitEmptyPasswords no
# systemctl reload sshd
# exit
SSHの設定変更。
$ mkdir .ssh
$ vi .ssh/authorized_keys
## PRIVATE_KEY.pub の内容を貼り付け
$ chmod 700 .ssh/
$ chmod 600 .ssh/authorized_keys
これで準備完了です。
SSH接続の試験
さっそく公開鍵認証で接続してみます。
# ssh -i ~/.ssh/PRIVATE_KEY SSH_REMOTEUSER@XXX.XXX.XXX.XXX
The authenticity of host 'XXX.XXX.XXX.XXX (XXX.XXX.XXX.XXX)' can't be established.
ECDSA key fingerprint is SHA256:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'XXX.XXX.XXX.XXX' (ECDSA) to the list of known hosts.
Last login: Wek Mon dd hh:ii:ss yyyy from YYY.YYY.YYY.YYY
$ exit
logout
Connection to XXX.XXX.XXX.XXX closed.
# ssh -i ~/.ssh/PRIVATE_KEY SSH_REMOTEUSER@XXX.XXX.XXX.XXX
Last login: Wek Mon dd hh:ii:ss yyyy from YYY.YYY.YYY.YYY
$ exit
logout
Connection to XXX.XXX.XXX.XXX closed.
2回試してみました。OKですね。
Ansible の試験
SSH接続ができることを確認したところで、 Ansible の試験をしたいと思います。
# ansible-playbook -i /workspace/hosts /workspace/get_packages.yml -u SSH_REMOTEUSER --private-key="/root/.ssh/PRIVATE_KEY" -K
BECOME password:
PLAY [Get packages from hosts] *****************************************************************************************
TASK [Gathering Facts] *************************************************************************************************
ok: [XXX.XXX.XXX.XXX]
TASK [Get packages] ****************************************************************************************************
ok: [XXX.XXX.XXX.XXX]
TASK [Output packages] *************************************************************************************************
changed: [XXX.XXX.XXX.XXX]
PLAY RECAP *************************************************************************************************************
XXX.XXX.XXX.XXX : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
OKです。
ちなみに今回は色々オプションを付けていますが……
-i
: サーバの一覧のファイルを指定-u
: SSH接続する際のユーザ名を指定--private-key
: 秘密鍵のパスを指定-K
:become
で昇格する際にパスワード認証をする
としています。
結果 (/workspace/XXX.XXX.XXX.XXX_packages)
# XXX.XXX.XXX.XXX
acl-2.2.51-14.el7.x86_64
## 略
zlib-devel-1.2.7-18.el7.x86_64
無事インストールされているパッケージの一覧が取得できました。
途中トライアンドエラーを何度も繰り返しましたが、ひとまずここまで到達できたので書き留めておきます。
参考
Docker
Dockerfile
Dockerfile の所在や扱い方。任意のディレクトリで良いのか、というところからスタートしました……(そこから?
データの永続化
- Docker の Volume バックアップ(Docker v19.03, macOS/Windows10) – Qiita
- Windows for Docker & docker composeにて、top-level volumes option で named volume を定義してPostgreSQLのデータを永続化する – メモ的な思考的な
docker-compose down
すると Dockerコンテナ 内の変更は消えることは分かっていたのですが、永続化とまともに向き合うことに。
Docker Compose
docker-compose up -d
の -d
オプションはバックグラウンド実行。 Ansible の Dockerコンテナ はここの情報をベースに。
Docker Compose 周りはたまたま手元にあったこの書籍も参考に。
Docker のネットワーク
docker network ls
の使い方など。
よくまとまっていて非常に参考になりました。
Docker for Windows のネットワーク
Docker compose のエントリポイント
- docker-compose.ymlの書き方について解説してみた – Qiita
- Compose ファイル・リファレンス — Docker-docs-ja 17.06 ドキュメント
- [docker] CMD とENTRYPOINT の違いを試してみた – Qiita
エントリポイント全般。
エントリポイントを実行するとコンテナがexitしてしまって bash で入れない現象の回避。
SSH
SSHクライアント
Docker の centos
イメージには初期インストールされていなかったので openssh-clients
をインストール。
SSH接続、公開鍵認証
- CentOS7.3でSSH接続(公開鍵認証)する方法 – Qiita
- linux(OpenSUSE,CentOS)からsshでログイン : Linux備忘録&ちょっと休憩しましょ
- [MAC][Bash]ssh-copy-idコマンドを使って公開鍵をサーバに転送する | Coffee Breakにプログラミング備忘録
鍵の強度
ssh-keygen
するときのオプションで。
ssh-keygen
エントリポイントでSSHの秘密鍵をシェルスクリプトから作成しましたが、その際に対話式ではなくコマンドワンライナーで完結させたかったのでオプションを漁ることに。
SSHの秘密鍵をデータ永続化しつつパーミッションの警告を回避する方法
エントリポイントを使用して公開鍵認証用の鍵のパーミッション警告を回避する手法の参考に。
Ansible
- 無理をしないansible – Qiita
- Ansible で始める Linux 管理 · DeNA Engineers’ Blog
- Ansible ドキュメント — Ansible Documentation
テスト用の playbook
SSH接続時のユーザ名指定
-u
オプションで。
昇格
become
周りについて。
マジック変数
Destination directory /PATH/TO/FILEBASE/ does not exist
結局解決策は分からなかったので回避しました。
その他
sudo
sudo
が必要な場面があったので。これも Docker の centos
イメージにはなかったので入れることに。
シェルスクリプト
- シェルスクリプトで文字列と変数を連結する | ハックノート
- 【シェルスクリプト】ファイルやディレクトリの有無を確認する方法色々 | server-memo.net
- 【シェルスクリプト】条件分岐させるifの使い方!
普段あまり書かないので文法をおさらい。