Ansibleの使い方:Ubuntu 24.04でVPSを自動設定する
Ubuntu 24.04にpipxでAnsibleを導入し、VPSの要塞化を行うPlaybookを作成します。SSH設定やfirewall構築の手順に加え、Permission deniedやsudo関連のエラー解決策も解説します。手動設定を自動化して、サーバー管理のミスを防ぎましょう。
作成するもの
Ansibleをインストールしたコントロールマシン1台と、標準イメージのみがインストールされた新規のUbuntu 24.04 VPSを1台以上用意します。最終的に、サーバーを定義したinventory file、認証が正常であることを確認するad-hoc ping、そして新規VPS向けのチェックリストをコード化したplaybookを構築します。このplaybookは、SSH keyを持つdeploy userの作成、sshdの要塞化、fail2ban、unattended upgrades、およびOpenSSHを許可した後に他の通信を拒否するfirewallの設定を行います。対象は1台でも20台でも可能です。2回実行しても2回目は何も変更されません。これがこの手法の目的です。
15年間にわたりVPSのプロビジョニングを行ってきた経験から、ある共通のパターンを指摘できます。多くの人は最初の5台を手動で設定しますが、6台目の設定時に、最初の5台で行った作業を忘れてしまい、週末を無駄にします。このガイドは multiple Linux serversの管理 に関する調査を深めるものです。3つのターミナルで同じ apt install を入力していることに気づいたら、ぜひ読んでください。
Ansibleの本質的な定義
Ansibleはagentlessです。管理対象のサーバーにdaemonをインストールする必要はありません。control machineは通常のSSH経由で接続し、小さなPythonモジュールをtargetへコピーして実行します。実行後、出力されたJSONを読み取り、モジュールを削除します。targetに必要なのはpython3だけであり、これは標準のUbuntuイメージに既に含まれています。重要な概念はidempotent(冪等性)です。これは「タスクがアクションではなく、state(状態)を記述する」ことを意味します。パッケージに対するstate: presentは、「インストーラーを実行する」のではなく「インストールされている状態にする」ことを意味します。既にそのstateにある場合、Ansibleは何もしません。その際、changedではなくokと報告します。この特性こそが製品の本質です。この特性により、playbookの再実行が安全になり、安全な再実行によってshell scriptがinfrastructureへと進化します。
前提条件と注意点
- コントロールマシン:ノートPCまたは小型のVPS。Ubuntu 24.04を想定しています。macOSの場合は、Homebrewでpipxをインストールすれば同様に動作します。
- ターゲットVPS:KVM上で動作するUbuntu 24.04が1台以上必要です。rootユーザーでアクセス可能である必要があります。ターゲット側にソフトウェアのインストールは行いません。
- SSH key 認証:すべてのターゲットに対して設定してください。Ansibleの認証方式は
sshコマンドに依存します。ssh root@hostでパスワードを求められる場合、Ansibleは失敗します。 - Ubuntu 24.04の仕様:Ubuntu 24.04では、
pip install ansibleはerror: externally-managed-environmentによって動作しません。これは不具合ではなく、ディストリビューションの意図的な仕様です。pipxを使用してください。 - YAMLの空白:インデントは構文の一部です。インデントが正しくないと
mapping values are not allowed in this contextが発生します。タブ文字が混入するとエラーになります。 - SSHセッションの維持:Playbookでsshdを要塞化(hardening)している間は、各ターゲットでSSHセッションを維持したままにしてください。顧客の復旧作業において、テストのためにセッションを閉じてしまい、ロックアウトが発生するケースが多発しています。
Step 1: pipではなくpipxを使用して、control machineにAnsibleをインストールする
一般的な手法はpip3 install ansibleです。しかし、Command 'pip3' not found, but can be installed with: sudo apt install python3-pipにより、完全に新規の24.04イメージでは早い段階で失敗します。pipをインストールしても、以下の問題に直面するだけです:
pip3 install ansibleerror: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
python3-xyz, where xyz is the package you are trying to
install.Ubuntu 24.04では、Pythonがexternally managed(PEP 668)としてマークされています。そのため、pipがaptと同じファイルに対して競合することはできません。--break-system-packagesは使用しないでください。そのflag名は正当な意味を持っています。推奨される方法はpipxです。pipxはAnsible専用のisolated virtualenvを作成し、バイナリをPATHに追加します:
sudo apt update && sudo apt install -y pipx
pipx ensurepath
pipx install --include-deps ansiblePATHの変更を反映させるため、pipx ensurepathの後に新しいshellを開いてください。--include-depsは単なる装飾ではありません。ansibleパッケージ自体にはconsole scriptsは含まれていません。ansibleやansible-playbookなどは、ansible-core依存関係のエントリポイントです。そのため、flagを指定しない場合、pipxはNo apps associated with package ansible or its dependenciesエラーでインストールを拒否します。また、ansible-coreではなくansibleパッケージをインストールしてください。フルパッケージにはcommunity collectionsが含まれています。このplaybookは、そのうちの2つ(ansible.posixとcommunity.general)のモジュールを使用します。
ansible --version正しい結果はansible [core 2.19.x]のような行で始まり、実行中のPythonの名前が表示されます。現在のコアリリースであれば、すべて問題なく動作します。ansible: command not foundと表示される場合は、~/.local/binがまだPATHに通っていないことを意味します。新しいshellを開くか、source ~/.bashrcを実行してください。
以上でインストールは完了です。targetには何もインストールされません。
Step 2: すべてのターゲットへの SSH key access
ssh-keygen -t ed25519 -C "ansible control"
ssh-copy-id root@10.0.0.10
ssh-copy-id root@10.0.0.20次に、各ホストに対して以下のコマンドを実行し、確認を行います。
ssh root@10.0.0.10 true && echo okこの1行には2つの役割があります。パスワードなしで key auth が動作することの確認と、known_hosts への host key の記録です。すぐに実行してください。Ansible は未登録の host key がある場合、実行中にインタラクティブなプロンプトを表示します。これは、処理が停止(hang)しているように見えるためです。
Step 3: インベントリ — 最初は INI、規模が大きくなったら YAML
インベントリは、Ansibleが操作する対象のリストを記述したテキストファイルです。新しいプロジェクトディレクトリに inventory.ini を作成してください。
[vps]
web1 ansible_host=10.0.0.10
web2 ansible_host=10.0.0.20
[vps:vars]
ansible_user=rootweb1 は任意のエイリアスです。これは出力に表示され、--limit web1 で指定する対象となります。ansible_host は実際の接続先アドレスです。[vps] はグループであり、[vps:vars] はそのグループ内のすべてのホストに対する変数を設定します。ansible_user は Ansible がログインするユーザーです。その隣にある ansible.cfg を設定しておけば、-i を入力する必要がなくなります。
[defaults]
inventory = inventory.iniAnsible はカレントディレクトリから ansible.cfg を読み込みます。YAML形式のインベントリも同様です。inventory.yml という名前で保存し、ansible.cfg でそのファイル名を指定してください。ホストごとに多くの変数を管理する場合は、YAML形式が適しています。
vps:
hosts:
web1:
ansible_host: 10.0.0.10
web2:
ansible_host: 10.0.0.20
vars:
ansible_user: rootこれらは同等です。サーバーが2台程度なら INI の方が視認性に優れています。サーバーが20台程度になるなら YAML の方が拡張性に優れています。どちらか一方を選択し、それ以降は形式を固定してください。
Step 4: ad-hoc commands — the green pong that proves everything
ansible all -m pingこれは ICMP ではありません。ping モジュールは、SSH ログイン、モジュールのコピー、ターゲット上での Python 実行、クリーンアップを含む、完全なリハーサルです。正しい結果は、ホストごとに 1 ブロックの緑色になります。
web1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}緑色の SUCCESS は、認証、Python インタープリタ、およびトランスポートがすべて正常であることを意味します。この場合、playbook も正常に動作します。赤色の UNREACHABLE! は、モジュールが実行される前にトランスポートが失敗したことを意味します。詳細なエラー内容と修正方法は、以下の failure modes セクションを確認してください。知っておくべき ad-hoc コマンドがもう 2 つあります。
ansible all -a "uptime"
ansible all -m apt -a "update_cache=true upgrade=dist" --becomead-hoc は、単発の操作や確認用です。2 回以上実行する内容は、playbook に記述してください。
Step 5: 最初の playbook — new-VPS checklist as code
これは、新しいサーバーのセットアップ時に手動で行う作業をすべてまとめたものです。site.yml として保存してください。
---
- name: Baseline a fresh Ubuntu VPS
hosts: vps
become: true
vars:
deploy_user: deploy
deploy_pubkey: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
baseline_packages:
- fail2ban
- unattended-upgrades
- ufw
baseline_services:
- fail2ban
- unattended-upgrades
tasks:
- name: Create the deploy user
ansible.builtin.user:
name: "{{ deploy_user }}"
groups: sudo
append: true
shell: /bin/bash
- name: Install the deploy user's SSH key
ansible.posix.authorized_key:
user: "{{ deploy_user }}"
key: "{{ deploy_pubkey }}"
- name: Passwordless sudo for the deploy user
ansible.builtin.copy:
dest: /etc/sudoers.d/deploy
content: "{{ deploy_user }} ALL=(ALL) NOPASSWD:ALL\n"
mode: "0440"
validate: /usr/sbin/visudo -cf %s
- name: Install baseline packages
ansible.builtin.apt:
name: "{{ baseline_packages }}"
state: present
update_cache: true
- name: Enable and start baseline services
ansible.builtin.service:
name: "{{ item }}"
state: started
enabled: true
loop: "{{ baseline_services }}"
- name: Harden sshd with a drop-in
ansible.builtin.copy:
dest: /etc/ssh/sshd_config.d/00-hardening.conf
content: |
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
X11Forwarding no
mode: "0644"
validate: /usr/sbin/sshd -t -f %s
notify: Restart ssh
- name: Allow OpenSSH through ufw
community.general.ufw:
rule: allow
name: OpenSSH
- name: Enable ufw with default deny
community.general.ufw:
state: enabled
policy: deny
handlers:
- name: Restart ssh
ansible.builtin.service:
name: ssh
state: restartedコピーするだけでなく、理解しておくべき箇所:
Variables は vars: の下にあり、"{{ deploy_user }}" で参照されます。値が波括弧 { で始まる場合は、YAML parser の誤読を防ぐために式全体を引用符で囲んでください。lookup('file', ...) は実行時に control マシンから公開鍵を読み取るため、playbook 自体に鍵情報は含まれません。
The loop. loop: "{{ baseline_services }}" は各アイテムに対してサービスタスクを1回実行し、出力には各アイテムが1行ずつ表示されます。一方、apt タスクはパッケージリスト全体を一度に処理します。1回の apt トランザクションの方が高速であり、パッケージ管理における推奨パターンです。ループは、一度に1つの対象に対して動作するモジュールに使用します。
The handler の概念を理解してください。notify: Restart ssh は「今すぐ sshd を再起動する」という意味ではありません。handler をキューに入れ、play の最後に、かつ notifying タスクが実際に changed を報告した場合にのみ実行されます。明日、playbook を再実行してください。drop-in ファイルは既に正しく、copy タスクは ok を報告し、sshd は再起動されません。validate: の行は安全装置です。sshd は古いファイルを置き換える前にファイルをチェックするため、タイポがあってもデーモンが壊れるのではなく、タスクが失敗するだけで済みます。
PermitRootLogin prohibit-password ではなく no を意図的に使用。 この playbook は鍵を使用して root としてログインします。prohibit-password は、自身のアクセスを維持したまま、root へのパスワードログインを無効にします。デプロイユーザーの動作が確認できたら(ssh deploy@10.0.0.10 sudo true — web1 は Ansible だけが知るエイリアスであるため、プレーンなアドレスを使用)、inventory 内の ansible_user=deploy を切り替え、後の実行で no に制限を強めます。自分が締め出されない順序で hardening を行ってください。
00- プレフィックスが重要です。 ほとんどのキーワードにおいて、sshd は最初に解析したものを優先します。Ubuntu の sshd_config は、自身の本体の前に辞書順で sshd_config.d/*.conf を含んでいます。Ubuntu 24.04 の cloud images には、既にそのディレクトリに 60-cloudimg-settings.conf が含まれています。また、cloud-init を通じてパスワードログインを有効にするプロバイダーは、PasswordAuthentication yes を持つ 50-cloud-init.conf を追加します。00-hardening.conf という名前を付けることで、それが最初にソートされ、両方よりも優先されます。
Task order はファイアウォールの安全策です。 Allow OpenSSH は、deny ポリシーを持つ Enable ufw の前に実行されます。Ansible はリストされた順序通りにタスクを実行するため、壁が構築される前に穴が存在することになります。fail2ban は、ここで役立てるために特別な設定を必要としません。Ubuntu のデフォルト設定で sshd を監視します。jail の実際の動作と調整方法については、fail2ban on Ubuntu 24.04 guide で説明しています。
Step 6: --check による dry run の実行、その後実際に実行
ansible-playbook site.yml --checkCheck mode は接続を行い、実行予定の内容を計算しますが、変更は一切行いません。末尾にある PLAY RECAP 内の changed= を確認してください。これが、各ホストに対して実行される予定のタスク数です。注意点があります。後のタスクが前のタスクによる変更に依存している場合、check mode では構造上の制限により失敗します。Ubuntu の標準サーバーイメージには ufw がプリインストールされているため、この playbook の dry run は正常に終了します。しかし、ufw が入っていない最小構成のイメージでは、check mode で ufw タスクが失敗します。これは、check mode ではパッケージが実際にインストールされないため、モジュールが呼び出す対象が存在しないことが原因です。これは dry run の仕様であり、playbook のバグではありません。計画に問題がなければ、以下を実行してください。
ansible-playbook site.yml各タスクはホストごとに 1 行ずつ結果を表示します(黄色は changed、緑は ok)。要約は以下のようになるはずです。
PLAY RECAP *********************************************************************
web1 : ok=10 changed=9 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=10 changed=9 unreachable=0 failed=0 skipped=0 rescued=0 ignored=010 個の ok、8 つのタスク、および handler の合計です。changed は私の結果と 1、2 個異なる場合があります。Ubuntu の標準イメージには ufw と unattended-upgrades がプリインストールされているためです。また、fail2ban は apt でインストールされた直後に起動するため、最初の実行時に「すでにその状態である」と報告し、ok となることがあります。unreachable と failed は必ず 0 でなければなりません。become: true について:これは root として接続している間の形式的なものですが、ansible_user を deploy に変更した瞬間、sudo が適用されます。この playbook がインストールする NOPASSWD 設定の sudoers ファイルにより、-K がコマンドラインに表示されなくなります。これがない場合、以下で説明する Missing sudo password が発生します。
Step 7: 2回実行する — 冪等性(idempotence)の動作
すぐに同じコマンドを再度実行してください:
web1 : ok=9 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0changed=0 と ok は、通知(notify)されたハンドラが実行されなかったため、値が1減少しました。再インストール、sshd の再起動、ufw への変更は一切行われていません。この性質により、Playbook はプロビジョナーであると同時に「監査(audit)」の役割も果たします。来月、inventory に web3 を追加して再実行してください。新しいサーバーは構築され、既存のサーバーは検証されます。未操作のサーバーで changed がゼロでない場合、それはドリフト(drift)を意味します。これは、Playbook で管理すべき設定が手動で変更されたことを示しています。
このパターンは積み重ねていくことができます。次に作成すべき Playbook は、同じ VPS 上に WireGuard VPN を構築し、SSH がトンネル経由でのみ応答するように ufw ルールを強化するものです。その次は、すべてのアプリサーバーに Docker と Compose をインストールするものです。site.yml の出力が3画面分を超えたら、role に分割してください。ただし、それまでは分割しないでください。
失敗パターンと表示される文字列
UNREACHABLE with Permission denied.
web1 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: root@10.0.0.10: Permission denied (publickey).",
"unreachable": true
}モジュールが実行される前にSSH接続に失敗しました。原因は ansible_user の設定ミス、鍵がホストにコピーされていない、または誤った鍵を提示していることです。まず plain ssh root@10.0.0.10 を実行し、次に ssh -v を実行して提示された鍵を確認してください。SSHでのパスワード認証は成功するが Ansible が失敗する場合は、ssh-copy-id をスキップしています。
Missing sudo password.
web1 | FAILED! => {
"msg": "Missing sudo password"
}become: true を設定し、非rootユーザーとして接続しています。そのユーザーに sudo のパスワードが必要です。コマンドラインに -K (--ask-become-pass) を追加するか、ユーザーに NOPASSWD の sudoers 設定を付与してください。Playbook が deploy に切り替える前に設定をインストールするのは、まさにこのためです。
error: externally-managed-environment. Ubuntu 24.04 のシステム Python に対して pip を実行しました。ステップ 1 の内容を参照してください。pipx を使用し、pip や --break-system-packages は使用しないでください。
mapping values are not allowed in this context.
ERROR! Syntax Error while loading YAML.
mapping values are not allowed in this contextほとんどの場合、インデントの問題です。キーの階層が正しくないか、コロンの後にスペースがありません。エラーに表示される行番号は、間違いの「付近」を指しています。上の行も確認してください。また、found character '\t' that cannot start any token はタブ文字が混入していることを意味します。YAML ではタブは禁止されています。実行前に必ず ansible-playbook site.yml --syntax-check を行い、エディタの YAML 設定をスペース2つ分のインデントにしてください。
/usr/bin/python3: not found. 標準的な Ubuntu 24.04 イメージでは稀ですが、minimal または netboot イメージでは一般的です。ターゲット側に Python がないため、モジュールの実行に失敗します。raw モジュールを使用して Python をインストールしてください。これはリモート側に何も必要としない唯一のモジュールです。ansible all -m raw -a "apt-get update && apt-get install -y python3" --become を実行した後、Playbook を再実行してください。
FAQ
管理対象のサーバーに Ansible をインストールする必要がありますか?
不要です。Ansible は agentless です。コントロールマシンが SSH 経由で小さな Python モジュールを転送・実行し、終了後に削除します。ターゲットには python3 と SSH アクセス権限のみが必要です。これらは Ubuntu の標準イメージに既に含まれています。このガイドでインストールが必要なのは、コントロールマシンのみです。
なぜ Ansible は "Permission denied (publickey)" と表示するのですか?
UNREACHABLE! ブロックに Permission denied (publickey) が含まれている場合、Ansible が実行される前に SSH 認証に失敗しています。inventory 内の ansible_user が設定済みのユーザーと一致しているか、そのホストに対して ssh-copy-id が実行できるか、そして通常の ssh user@host でパスワードなしでログインできるかを確認してください。通常の ssh コマンドが解決されれば、Ansible も解決されます。両者は同じ転送手段を使用しているためです。
Ansible における idempotent(冪等性)とは何を意味しますか?
タスクは「実行すべきアクション」ではなく、「あるべき状態(例:このパッケージが存在する、この行がファイルにある)」を宣言します。既にその状態である場合、Ansible は何もせず、changed の代わりに ok を報告します。そのため、プレイブックを2回実行すると、2回目は changed=0 と表示されます。再実行はリスクのある再インストールではなく、安全な監査として機能します。
Ubuntu 24.04 に Ansible をインストールする場合、pip と pipx のどちらを使うべきですか?
pipx を使用してください。Ubuntu 24.04 ではシステム Python が外部管理対象としてマークされているため、設計上 pip install ansible は error: externally-managed-environment で失敗します。pipx install --include-deps ansible を使用すると、Ansible は隔離された virtualenv に配置され、ansible、ansible-playbook、およびその他のコマンドが PATH に適切に登録されます。
ansible と ansible-core パッケージの違いは何ですか?
ansible-core はエンジンと ansible.builtin モジュールのみを含むパッケージです。ansible パッケージは、コア機能に厳選された community collections をまとめたものです。これには、本ガイドで使用する ansible.posix (authorized_key モジュール) と community.general (ufw モジュール) が含まれます。まずはフルパッケージから開始してください。コアと厳選したコレクションのみに絞り込むのは、明確な理由がある場合のみにしてください。