はじめに
本記事では、Docker Composeを用いてAlma Linux9.4で動くAnsibleの検証環境を構築する手順を記載します。
Ansibleを触りたいときは、KVMやVagrantを用いた仮想環境を使っていたのですが、自分の環境だと起動に時間がかかってしまうため、より軽量に動かすためにDockerで作ってみました。
環境
- Ubuntu 22.04.3
- Docker 27.0.3
- Docker Compose version v2.28.1
構成
以下にDocker Composeの構成を記載していきます。
諸々のファイルは以下のリポジトリにもアップロードしています。
https://github.com/mozochan/ansible_dockerlab
簡単な使い方はREADMEにも記載しているので、併せて確認いただけると幸いです。
本記事では、どのような点を工夫したのか、迷ったのかということを中心に記載していきます。
ディレクトリ構成
ansible_dockerlab
├── Makefile
├── compose.yml
├── control-node
│ ├── .ansible.cfg
│ ├── Dockerfile
│ └── hosts
└── target-node
└── Dockerfile
Docker Composeをupさせると、以下のコンテナが起動します。
コンテナ名/ホスト名 | 用途 |
---|---|
control-node | ansible-coreをインストールしたAnsibleコントロールノード |
target-node1 | Ansibleターゲットノード1 |
target-node2 | Ansibleターゲットノード2 |
Ansibleコントロールノード関連ファイル
FROM almalinux:9.4
# Install necessary packages
RUN dnf update -y && \
dnf install -y epel-release && \
dnf install -y ansible-core openssh-clients vim sudo && \
dnf clean all
# Create ansibleuser and grant sudo privileges
RUN useradd -m -s /bin/bash ansibleuser && \
echo "ansibleuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
# Setup ssh_key for target-node
RUN mkdir -p /home/ansibleuser/.ssh && \
chmod 700 /home/ansibleuser/.ssh && \
ssh-keygen -t rsa -f /home/ansibleuser/.ssh/id_rsa -q -C "" -N "" && \
chown -R ansibleuser:ansibleuser /home/ansibleuser/.ssh
# Copy ansible.cfg and hosts to container
COPY --chown=ansibleuser:ansibleuser .ansible.cfg /home/ansibleuser/
COPY --chown=ansibleuser:ansibleuser hosts /home/ansibleuser/
# Set default user to ansibleuser
USER ansibleuser
# Set default directory
WORKDIR /home/ansibleuser
# Keep Docker Container running
CMD ["tail", "-F", "/dev/null"]
- DockerイメージとしてAlma Linux9.4を使用し、ansible-coreをインストールしています。
- AnsibleコントロールノードとSSH公開鍵認証を行うため、openssh-clientsをインストールし、鍵の作成をコントロールノードで行っています。
- ansible_become_method sudoを試したかったので、コントロールノード - ターゲットノード間のSSH接続はansibleuserというユーザを作成し、接続する方式としています。
- 後述する.ansible.cfgとhosts(インベントリ)を/home/ansibleuserに配置しています。
[defaults]
inventory=/home/ansibleuser/hosts
host_key_checking=False
become=True
become_method=sudo
become_ask_pass=False
[target]
target-node1
target-node2
[target:vars]
ansible_user=ansibleuser
ansible_ssh_private_key_file=/home/ansibleuser/.ssh/id_rsa
- コントロールノード - ターゲットノード間で通信を行うための設定を記載したansible.cfgとインベントリを配置しています。
Ansibleターゲットノード関連ファイル
FROM almalinux:9.4
# Install SSH Server and sudo
RUN dnf update -y && \
dnf install -y openssh-server sudo && \
dnf clean all
# Create ansibleuser and grant sudo privileges
RUN useradd -m -s /bin/bash ansibleuser && \
echo "ansibleuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
# Create .ssh directory and SSH Host Key
RUN mkdir -p /home/ansibleuser/.ssh && \
chmod 700 /home/ansibleuser/.ssh && \
chown ansibleuser:ansibleuser /home/ansibleuser/.ssh && \
ssh-keygen -A
# Permit PubkeyAuthentication
RUN sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config
# Copy Public Key from control-node image
COPY --from=ansible_dockerlab-control-node --chown=ansibleuser:ansibleuser /home/ansibleuser/.ssh/id_rsa.pub /home/ansibleuser/.ssh/authorized_keys
# Start SSH Server
CMD ["/usr/sbin/sshd", "-D"]
- コントロールノードとSSH接続するためにopenssh-serverをインストールしています。
- ansible_become_method sudoを試すために、ansibleuserを作成し、ノンパスのsudo権限を付与しています。
- Dockerイメージを作成する過程でsshを起動した際に「sshd: no hostkeys available – exiting.」というエラーが発生したため、ssh-keygen -Aでホストキーを作成しています。
- コントロールノードとのSSH公開鍵認証のため、Dockerfileの COPY –fromを利用して、コントロールノードのDockerイメージから公開鍵をターゲットノードのauthorized_keysとしてコピーしています。
Makefileとcompose.yml
.PHONY: build_control
build_control:
@docker compose build control-node
.PHONY: build_target
build_target: build_control
@docker compose build target-node1
.PHONY: up
up: build_target
@docker compose up -d
.PHONY: ps
ps:
@docker compose ps -a
.PHONY: stop
stop:
@docker compose stop
.PHONY: down
down:
@docker compose down
.PHONY: con
con:
@docker compose exec control-node bash
.PHONY: tar1
tar1:
@docker compose exec target-node1 bash
.PHONY: tar2
tar2:
@docker compose exec target-node2 bash
.PHONY: clean
clean:
@docker compose down --rmi all --volumes --remove-orphans
x-target: &target-params
build: ./target-node
depends_on:
- control-node
command: /sbin/init
privileged: true
tty: true
services:
control-node:
build: ./control-node
container_name: control-node
hostname: control-node
init: true
tty: true
target-node1:
<<: *target-params
container_name: target-node1
hostname: target-node1
target-node2:
<<: *target-params
container_name: target-node2
hostname: target-node2
- ターゲットノードのDockerfileでCOPY –fromを使ってコントロールノードの公開鍵をコピーするために、必ずコントロールノード→ターゲットノードの順番でDockerイメージの作成が必要と考えました。
- Docker Composeにはdepends_onという項目でサービスの依存関係を記載することができますが、buildの依存関係まで記載できないようだったので、Makefileでコントロールノード→ターゲットノードの順番でbuild + upまで実行できるコマンド(make up)を定義しました。
- ターゲットノードでsystemctlコマンドを実行したい場合があると考え、「command: /sbin/init」「privileged: true」を設定し、無理やりコンテナ内で/sbin/initをPID1で起動しています。
- compose.ymlで2つのターゲットノードで共通となる項目はアンカー(&target-params)を設定し、「«: *target-params」の部分で項目を継承する設定としています。
Ansibleのテスト
以下のコマンドでコンテナを起動できます。
$ make up
以下のコマンドでコントロールノードにログイン、テストを行い、“ping”: “pong"と表示されたらAnsibleでの接続はOKです。
$ make con
$ ansible all -m ping
target-node2 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
target-node1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
試しにhttpdのインストール、起動を行うplaybookも実行します。
$ vim test.yml
---
- name: Install and start httpd
hosts: all
become: true
tasks:
- name: Install httpd
ansible.builtin.dnf:
name: httpd
state: present
- name: Start httpd
ansible.builtin.systemd_service:
name: httpd.service
enabled: true
state: started
$ ansible-playbook test.yml
PLAY [Install and start httpd] ************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************
ok: [target-node1]
ok: [target-node2]
TASK [Install httpd] **********************************************************************************************************************
changed: [target-node2]
changed: [target-node1]
TASK [Start httpd] ************************************************************************************************************************
changed: [target-node2]
changed: [target-node1]
PLAY RECAP ********************************************************************************************************************************
target-node1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
target-node2 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ターゲットノード1にログインしhttpdが起動しており、かつ自動起動がオンになっていればOKです。
$ make tar1
# systemctl status httpd
● httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; preset: disabled)
Active: active (running) since Mon 2024-11-11 15:30:34 UTC; 1min 32s ago
Docs: man:httpd.service(8)
Main PID: 451 (httpd)
Status: "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes served/sec: 0 B/sec"
CGroup: /docker/b8885a897f36edda0629985116b607fd9e93c6e0681ea43c1b1ac6e6d6474a33/system.slice/httpd.service
├─451 /usr/sbin/httpd -DFOREGROUND
├─452 /usr/sbin/httpd -DFOREGROUND
├─453 /usr/sbin/httpd -DFOREGROUND
├─454 /usr/sbin/httpd -DFOREGROUND
└─455 /usr/sbin/httpd -DFOREGROUND
Nov 11 15:30:34 target-node1 systemd[1]: Starting The Apache HTTP Server...
Nov 11 15:30:34 target-node1 httpd[451]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.>
Nov 11 15:30:34 target-node1 httpd[451]: Server configured, listening on: port 80
Nov 11 15:30:34 target-node1 systemd[1]: Started The Apache HTTP Server.
ホストOSに戻り、以下のコマンドでDockerコンテナ、ネットワーク、イメージを丸ごと削除できます。
$ make clean
反省点や今後の展望
今回、Dockerコンテナでsystemctlを利用したいがために特権モードを与えていますが、コンテナが攻撃者に悪用された際にホストのリソースへの高度なアクセス権限を持たせてしまうため、セキュリティリスクがあります。
また、ディスクやネットワーク周りのAnsible検証環境としては、仮想マシンに分があるので、腰を据えてAnsibleの検証をしたいときは仮想マシン、サクッと確認して環境もすぐに壊していい場合はDockerを利用するというような住み分けで利用していこうかと考えています。
後は、今回は特に意識しなかったのですが、Dockerイメージのサイズが重くなってしまったので、暇があればもっと軽量なコンテナを作ってみようかと考えています。
$ docker compose images
CONTAINER REPOSITORY TAG IMAGE ID SIZE
control-node ansible_dockerlab-control-node latest d736727bff71 330MB
target-node1 ansible_dockerlab-target-node1 latest e0c36db1a778 259MB
target-node2 ansible_dockerlab-target-node2 latest fb2cc3fa04f9 259MB
終わりに
Ansibleを勉強したいと思ったのに、途中からDockerの環境構築が楽しくなってしまい、目的を見失っておりました…