Ansibleに入門してみた
同時適用したいホスト一覧を設定ファイルに書く
↓はリモートのホスト一覧を記述した設定ファイル.$HOME/.ssh/configを利用しないやり方は
ssh user@host
と通常接続するときに ansible_userでuser
、ansible_hostでansible_host
がを定義することができる.
all:
children:
homelab:
hosts:
raspi1: # $HOME/.ssh/configの設定を暗黙に適用する
raspi2:
raspi3:
ubuntu1:
# ansible_become_passは予約変数
ansible_become_pass: "{{ ub1_password }}" # ub1_passwordは独自定義の変数.
ubuntu2:
ansible_become_pass: "{{ ub2_password }}"
vultr:
hosts:
vubuntu1: # $HOME/.ssh/configを利用しないやり方.
ansible_host: "{{ vub1_ipv4 }}"
ansible_user: "{{ vub1_user }}"
このそれぞれall
とかhomelab
とかがあとでPlaybookと呼ばれるレシピファイルで利用するグループ単位になる. なお hosts
下のraspi1
などの名前は何も書かなければ 暗黙に$HOME/.ssh/config
ファイルにかかれている内容のHost
変数を参照する.
例↓
Host raspi1 # ここの名前が下のinventory.yamlのhostsの名前で参照される
User raspi1
HostName exmple.com
Port 22
IdentityFile ~/.ssh/id_ed25519
AddKeysToAgent yes
UseKeychain yes
ProxyCommand cloudflared access ssh --hostname %h # Cloudflaredなどを利用したProxyコマンドでも問題なく動く
ansible-vault
を利用
公開したくない変数などの保存どうするの問題 -> やり方
ansible
の レシピに相当する Playbook
や リモートホストの設定一覧を記述するInventory
ファイルなどで秘密鍵やAPI tokenやCloudインスタンスのIPアドレスなど外部に公開すると、セキュリティ上問題のある変数をJinjaのtemplate変数の方法で記述できる.
- パスワードをローカルのPrivateなディレクトリに置いておき $HOME/.local/share/ansible_pwd.txt
などにランダムな文字列をbcrypt-tool
などで生成して保存するたとえば
$2a$10$0k./H/G16ejeoT612nSWR.j9djAsQIcPuU9MAmCXD2fQSCOxevbuC
- 上記ファイルをansibleファイルの暗号化のための鍵🔑として秘密設定ファイルを作る.
ansible-vault create --vault-password $HOME/.local/share/ansible_pwd.txt vault.yaml
とやるとファイルが作成されモーダルエディターが開く. 自分のMacBook ProではデフォルトでVimと同じコマンドで編集できた.
# この変数を他のレシピやホスト設定に {{ub1_password}} などの形で利用できる.
- ub1_password: xxxx
ub2_password: yyyy
vub1_user: john
vub1_ipv4: 43.xxx.xxx.xxx
:wq
で閉じる.
中身はテキストファイルになっていて
$ANSIBLE_VAULT;1.1;AES256
343231613831313161346165376530353264373037376...
のように暗号化されている.
ここまでの感想
Ansibleはシンプルと言われるがどちらかというとイージーという意味合いのほうがあっているのではないだろうか。暗黙に設定される内容が多すぎて、迷いそう。とくに編集するときに、プログラミング言語と違って、参照する変数がどこのファイルから来ているのか明示的にわからないので、絵で図示などをしながらなどしておかないとあとでわけが分からなくなりそう.
今のところ新たに設定したファイルと利用したファイルは
├── inventory.yaml
└── vault.yaml # 暗号化されたファイル
$HOME/.ssh
└── config # inventory.yamlで参照される sshの設定
$HOME/.local/share
└──ansible_pwd.txt # Ansibleにおける鍵, 外部に公開✘
Mermaidで図示しておくと安心かも
apt upgrade
をすべてのホストに適用する.
例: ---
- name: Update and upgrade apt packages
hosts: all # inventory.yamlのblockの名前で指定できる.
become: true
vars_files: # ここで変数を適用するファイルを指定している.今回はリモートサーバーのパスワードやIPなどを参照してくれている.
- vault.yaml
tasks:
- name: Update and upgrade apt packages
apt: # このブロックはAnsibleがDebian系ディストリビューション用にビルドインで用意してくれてる.
upgrade: yes
update_cache: yes
cache_valid_time: 86400
重要な点
inventory.yamlに設定したansible_become_pass: "{{ ub1_password }}"
がvault.yaml
ファイルから読み込んで自動で設定してくれるので、 Rootユーザーとして sudoコマンドなども実行できるようになっている. become: true
で明示的にルートユーザーになることを指定している.
# -vvは冗長な出力をしてくれる. vの数で出力内容がより冗長になる 例: -vvvもっと冗長に
ansible-playbook --vault-password-file $HOME/.local/share/ansible_pwd.txt -i inventory.yaml apt_upgrade.yaml -vv
上の図を見ていて思ったこと
- あれ ?
apt_upgrade.yaml
->vault.yaml
という依存の向きは実際にはapt_upgrade.yaml
でvault.yaml
の変数利用してないからいらなくね? - シンプルに
inventory.yaml
->vault.yaml
の記述をinventory.yaml
に書いたらいいやん?
さっきのファイルに
vars_files:
- vault.yaml
追加したらいけるんじゃね?
結果 => だめ
どうしてだめな仕様になっているのかはわからん..
とりあえず変数が記述されたファイルを読み込むには playbook.yaml
のほうで明示しないとエラーになるらしい.
Go をInstallもしくはUpgradeするスクリプトをすべてのホストに適用する.
公式に従って /usr/local/go
に実行バイナリ /usr/local/bin/go
をinstallするという想定. Ansibleでパスを指定するときにシェルスクリプト内では絶対パスで指定しないとうまくいかなかった.
なにかうまい方法で$HOME/.bashrc
などの環境変数の設定を読みに行くこともできるのかもしれないが、今のところやり方はわからない.
GoをInstallするシェルスクリプトをリモートにコピーして実行するという作戦.
リモートの環境変数の読み込みが環境によってまちまちなので、Debianだけでやるなら、絶対パス決め打ちのほうが確実.
#!/usr/bin/bash
GO_JSON=$(curl -s "https://go.dev/dl/?mode=json")
LATEST_VERSION=$(echo "$GO_JSON" | jq -r '.[0].version')
echo Latest version go: "$LATEST_VERSION"
if ! /usr/local/go/bin/go version; then
echo "Go is not installed"
CURRENT_VERSION=""
else
CURRENT_VERSION=$(/usr/local/go/bin/go version | awk '{print $3}')
echo Current version go: "$CURRENT_VERSION"
fi
ARCH_NAME=$(uname -m)
echo "Arch name" "$ARCH_NAME"
if [ "$ARCH_NAME" = "x86_64" ]; then
ARCH_NAME="amd64"
fi
if [ "$ARCH_NAME" = "aarch64" ]; then
ARCH_NAME="arm64"
fi
if [ "$LATEST_VERSION" = "$CURRENT_VERSION" ]; then
echo "The go version is latest" "$CURRENT_VERSION"
exit 0
fi
FILE_NAME="$LATEST_VERSION".linux-"$ARCH_NAME".tar.gz
DOWNLOAD_URL="https://go.dev/dl/$FILE_NAME"
echo "$DOWNLOAD_URL"
mkdir -p /tmp/go_install
wget -P /tmp/go_install "$DOWNLOAD_URL"
tar -xzf /tmp/go_install/"$FILE_NAME" -C /tmp/go_install
INSTALLED=/tmp/go_install/go
if [ -z "$CURRENT_VERSION" ]; then
sudo mv "$INSTALLED" /usr/local
# Intentionally not expand the $PATH variable because of
echo 'export PATH="$PATH":/usr/local/go/bin:"$HOME"/go/bin' >> "$HOME"/.bashrc
source "$HOME"/.bashrc && \
echo "Installed go version:" "$(go version)" "installed"
else
# Remove the current version
sudo rm -fr /usr/local/go
sudo mv "$INSTALLED" /usr/local
echo "New go version:" "$(/usr/local/go/bin/go version)" "installed"
fi
rm -fr /tmp/go_install
以下のようなPlaybookから実行するようにする.
---
- name: Copy Go install code
hosts: all
vars_files:
- vault.yaml
tasks:
- name: Create ~/script directory
file:
path: "{{ ansible_env.HOME }}/scripts"
state: directory
mode: '0755'
- name: Copy install_go.sh file
copy:
src: "src/install_go.sh"
dest: "{{ ansible_env.HOME }}/scripts/install_go.sh"
mode: '0644'
- name: Execute install_go.sh file
shell: /usr/bin/bash "{{ ansible_env.HOME }}/scripts/install_go.sh"
become: true
# become_method: sudoとするとsudo がついたコマンド以外はログインユーザーとして実行する
become_method: sudo
ansible-playbook --vault-password-file $HOME/.local/share/ansible_pwd.txt -i inventory.yaml go_install.yaml -vv
ここまでのDirectory構成
├── install_go.yaml # playbook
├──apt_upgrade.yaml # playbook
├── inventory.yaml # sshの接続設定とか
├── src
│ └── install_go.sh # リモートに渡したいやつら
└── vault.yaml # 秘密のやーつ
Go install upgradeの別のPlaybookの書き方
ChatGPT-4に手伝ってもらったやり方. シェルスクリプトの実行内容をすべてAnsibleの機能だけでやっている.
こちらのほうが Ansibleの機能をフルに利用してるので、実行時の出力が何が起こっているか見やすい.
set_fact
やregister
などを用いて変数を指定して他のブロックでも利用できるということらしい.
- hosts: all
become: true
vars_files:
- vault.yaml
tasks:
- name: Goの最新バージョン情報を取得
uri:
url: https://go.dev/dl/?mode=json
return_content: yes
register: go_latest
- name: Goの最新バージョンを変数に設定
set_fact:
# Jinja2のjson arrayから0番目のindex `first`を指定するやり方
latest_go_version: "{{ (go_latest.json | first).version }}"
- name: 現在のGoバージョンを確認
shell: /usr/local/go/bin/go version | awk '{print $3}'
register: current_go_version
ignore_errors: yes
- name: 現在と最新のGoバージョンを表示
debug:
msg: "現在のGoバージョンは: {{ current_go_version.stdout }}, 最新のGoバージョンは: {{ latest_go_version }}です。"
- name: アーキテクチャを確認
set_fact:
arch_name: "{{ 'amd64' if ansible_architecture == 'x86_64' else ('arm64' if ansible_architecture == 'aarch64' else ansible_architecture) }}"
- name: 最新バージョンをダウンロードして展開(アップグレードが必要な場合)
block:
- name: ダウンロードURLを設定
set_fact:
go_download_url: "https://go.dev/dl/{{ latest_go_version }}.linux-{{ arch_name }}.tar.gz"
- name: /tmp/go_installディレクトリを作成
file:
path: /tmp/go_install
state: directory
- name: Goのダウンロードと展開
unarchive:
src: "{{ go_download_url }}"
dest: /tmp/go_install
remote_src: yes
- name: Goのインストール
block:
- name: 既存のGoを削除
file:
path: /usr/local/go
state: absent
- name: 新しいGoを移動
command: mv /tmp/go_install/go /usr/local
- name: 環境変数の更新
lineinfile:
path: "{{ ansible_env.HOME }}/.bashrc"
line: 'export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin'
create: yes
- name: 一時ファイルの削除
file:
path: /tmp/go_install
state: absent
when: current_go_version.stdout != latest_go_version