年末は自宅で稼働する5台のラズパイ達のパッケージアップデートやスクリプト整理などをやっておりました😎
OSのメジャーアップデートもそろそろやらなきゃなということで、ついにbullseyeへアプデすることにしました。 なお既存環境からメジャーアップデートさせるのは非推奨ということらしいので、新たにSDカードにイメージを焼き直して環境構築からやり直そうとしたのですが、これが手順が多くてつらい…。
ということで以前から気になっていたAnsibleで初期設定するようにしてみました。
↓手動で初期設定する記事は以前こちらで書きました。
事前準備
SDカードにOSイメージを焼く
Raspberry Pi Imagerという便利ツールがありますので、これを使ってSDカードにRaspberry Pi OSのイメージを焼きましょう。
↓Raspberry Pi ImagerのDLはこちらからどうぞ。
なお、Raspberry Pi Imagerの画面上の歯車アイコンを押すとホスト名、Wifi、SSHの初期設定ができるようになっていますので、事前に入力しておきます。
ラズパイでホスト名を設定しておくと、IPではなく{ホスト名}.local
で接続できるので便利です。
初期設定をしておくと、いちいちモニタやキーボードをラズパイに繋いでWifi, SSHの初期設定をする必要もなくなります。(知らないうちにソフトがかなり便利になっていてうれしいですね)
ちょっとハマったこと
Raspberry Pi Zero WはWifiの5GHz帯未対応なので、2.4GHz帯のWifiに接続するようにしましょう…。これに気付かず数時間溶かしました🤤
公開鍵認証でSSH接続できるようにする
AnsibleはSSHでコマンド実行するので予め公開鍵認証でSSH接続できるように設定します。
以下はSSH接続するホストでのコマンド例です。
# SSH用の公開鍵・秘密鍵を生成する $ ssh-keygen # デフォルトだとラズパイに.sshディレクトリがないので作成する $ ssh {接続先のホスト名} mkdir -p /home/pi/.ssh # 公開鍵をラズパイのauthorized_keysに渡す $ cat "${HOME}/.ssh/id_rsa.pub" | ssh {接続先のホスト名} "cat >> /home/pi/.ssh/authorized_keys" # sshd_configのパスワード認証をOFF、authorized_keysのパスを指定する $ echo 'PasswordAuthentication no' | ssh {接続先のホスト名} "sudo tee -a /etc/ssh/sshd_config" $ echo 'AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2' | ssh {接続先のホスト名} "sudo tee -a /etc/ssh/sshd_config"
これで公開鍵認証でSSH接続できるようになりました。
Ansibleを使う
Ansibleをインストールする
aptでインストールすると2.10.x以上のAnsibleがインストールできなかったので、pipでインストールしています。
# Ansibleのインストール $ python3 -m pip install --user ansible # インストールされたか確認する $ ansible --version ansible [core 2.13.7] config file = /home/tm/ansible/ansible-raspberry-pi/ansible.cfg configured module search path = ['/home/tm/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /home/tm/.local/lib/python3.8/site-packages/ansible ansible collection location = /home/tm/.ansible/collections:/usr/share/ansible/collections executable location = /home/tm/.local/bin/ansible python version = 3.8.10 (default, Nov 14 2022, 12:59:47) [GCC 9.4.0] jinja version = 3.1.2 libyaml = True # community.generalはufwを扱う際に必要になるのでインストールします $ ansible-galaxy collection install community.general
ディレクトリ構造
今回、下記のようなディレクトリ構造にしました。
$ tree . ├── ansible.cfg ├── hosts.ini ├── playbook.yml ├── roles │ ├── apt │ │ ├── tasks │ │ │ └── main.yml │ ├── git │ │ ├── tasks │ │ │ └── main.yml │ ├── nvm │ │ ├── tasks │ │ │ └── main.yml │ ├── osinfo │ │ ├── tasks │ │ │ └── main.yml │ ├── ssh │ │ ├── tasks │ │ │ └── main.yml │ ├── ufw │ │ ├── tasks │ │ │ └── main.yml │ └── vim │ │ ├── tasks │ │ │ └── main.yml
Ansibleにはインベントリ、Playbook、Rolesといった固有の概念があります。
- ansible.cfg: 設定ファイル。ログ出力パス等を定義する。
- hosts.ini: インベントリファイル。対象ホストを定義する。
- playbook.yml: Playbook。実行する命令や実行時の条件を定義する。
- roles/*: Rolesの定義。Playbookから呼び出せる処理の固まり。
インベントリー(hosts.ini)
ファイル名は任意です。
[raspiservers] mustang.local ansible_user=pi jaguar.local ansible_user=pi stratocaster.local ansible_user=pi lespaul.local ansible_user=pi telecaster.local ansible_user=pi
今回操作対象となるホスト名と接続時に使うユーザー名をansible_user
に指定します。
ちなみに我が家のラズパイにはギター由来の名前を付いていてそれがホスト名になっています🎸
Playbook(playbook.yml)
ファイル名は任意です。
--- - hosts: raspiservers become: yes become_user: root roles: - apt - git - vim - nvm - ssh - ufw
hosts
にはhosts.iniで定義しておいたセクションを指定できます。
apt installなどでroot権限が必要になってくるので、予めbecome: yes
, become_user: root
でrootユーザーで実行するようにしています。
roles
には後述しますがRolesで定義した具体的な処理の固まりを呼び出しています。
playbook.yml自体に具体的な処理(apt update, apt installにあたる処理など)を書くことも可能ですが、予めRolesに処理を切り出しておくことで他のPlaybookから再利用できたり、長期的にコードが煩雑になり辛くなるので分けておく方が良いようです。
設定ファイル(ansible.cfg)
[defaults] log_path=/tmp/ansible.log
log_path
でログ出力先を指定しています。
Roles(roles/*)
roles配下にRole別にディレクトリを作成し、roles/*/tasks/main.ymlに具体的な処理を記述します。
新規にRoleを追加したい時はansible-galaxy init roles/{Role名}
を叩くと必要なファイル群を自動生成してくれるので便利です。
roles/apt/tasks/main.yml
--- - name: apt update apt: update_cache: yes - name: apt upgrade apt: upgrade: yes - name: install apt packages apt: name: - vim - git - tig - ufw - chromium - jq
roles/git/tasks/main.yml
--- - name: check gitconfig(global) already exist become: no stat: path: /home/pi/.gitconfig register: gitconfig_global changed_when: not gitconfig_global.stat.exists - name: configure git global become: no shell: | git config --global user.name "hogefuga" git config --global user.email "hogefuga@gmail.com" when: not gitconfig_global.stat.exists
git config --global
がいつも面倒なのでここでセットしています。
roles/vim/tasks/main.yml
--- - name: check vimrc already exist stat: path: /home/pi/.vimrc register: vimrc changed_when: not vimrc.stat.exists - name: configure vim shell: | echo >> 'syntax on' /home/pi/.vimrc echo >> 'set tabstop=4' /home/pi/.vimrc echo >> 'set number' /home/pi/.vimrc echo >> 'set hlsearch' /home/pi/.vimrc update-alternatives --set editor /usr/bin/vim.basic when: not vimrc.stat.exists
.vimrcによく指定するパラメータをセットしています。
roles/nvm/tasks/main.yml
--- - name: check nvm already installed stat: path: /home/pi/.nvm register: result changed_when: not result.stat.exists - name: install nvm become: no shell: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash when: not result.stat.exists - name: nvm install nodejs become: no shell: source /etc/profile && nvm install --lts # nvm install is too late for Raspberry Pi Zero... timeout: 300 when: not result.stat.exists
Node.jsはバージョン管理したいケースが多いのでnvmをインストールさせました。
timeout
を指定すると処理が指定した秒数でタイムアウトするようになります。
Raspberry Pi Zeroだと恐らくスペックが低いが故にフリーズ状態になってしまう時があったので、タイムアウトさせるようにしました。
roles/ssh/tasks/main.yml
--- - name: check id_rsa exist stat: path: /home/pi/.ssh/id_rsa register: idrsa changed_when: not idrsa.stat.exists - name: ssh-keygen become: no shell: | ssh-keygen -b 2048 -t rsa -f /home/pi/.ssh/id_rsa -q -N "" when: not idrsa.stat.exists
GitHubに公開鍵を登録することも多いので予めSSHの公開鍵・秘密鍵を生成させています。
roles/ufw/tasks/main.yml
--- - name: ufw already set shell: ufw status register: ufw_status changed_when: ufw_status.rc >= 1 - name: reboot fro ufw ansible.builtin.reboot: reboot_timeout=300 when: ufw_status.rc >= 1 - name: enable ufw community.general.ufw: state: enabled policy: deny when: ufw_status.rc >= 1 - name: enable ufw logging community.general.ufw: logging: 'on' when: ufw_status.rc >= 1 - name: ufw allow tcp port 22 community.general.ufw: rule: allow port: '22' proto: tcp when: ufw_status.rc >= 1 - name: ufw allow tcp port 5900 community.general.ufw: rule: allow port: '5900' proto: tcp when: ufw_status.rc >= 1
ansible.builtin.reboot
でホストが再起動します。
再起動をあえて挟んでいる理由は、そのままufw enable
をしようとするとERROR: Couldn't determine iptables version
が発生する為です。
↓参考
pi 4 - ufw and iptables on buster - Raspberry Pi Stack Exchange
実行する
インベントリ、Playbook、Rolesが用意できたらあとは実行するのみです。
ansible-playbook -i hosts.ini playbook.yml
でPlaybookの定義を元に処理が実行されます。
あとは気長に処理完了を待つだけです。
あとがき
Ansibleで予め処理セットを準備したおかげでラズパイ初期化への心理的負担がかなり減ったのがメリットだったと思いました!