AnsibleでRaspberryPi4の初期設定を自動化
はじめに
前回インストールした2つのRaspberryPiOSに初期設定を行います。仕事でAnsibleを少し触ったので勉強もかねてAnsibleで自動化してみようと思います。
AnsibleのコントローラマシンはMacを使い、MacからRaspberryPiへプロビジョニング(初期設定)を行います。設定が完了したらBerryBootでイメージ化してバックアップしておくとクリーンな環境への巻き戻しが楽になりそうです。
今回のAnsible導入でメリットと感じた点は以下の通りです。
- 32bit, 64bit の両OSに対して全く同じ設定を漏れなく反映させることができる
- 初期設定としてどんな内容を行なったかを設定ファイル(後述のPlaybookファイル) に残しておける
- 部分的にでも再利用ができそうなので他のOS初期化時にも使えそう
- Ansible の勉強
自動化する内容
今回は以下の内容で構築しようと思います。
内容 | 詳細 |
---|---|
OS アップデート | aptパッケージを最新化しvimとufwを追加でインストールします。 vi だとコードのハイライトがデフォルトで効かないのでvimを導入します。 ufwはFirewallの設定ツールです。 |
OS ユーザの作成 | 作業用のユーザ作成とpi ユーザを無効化します。鍵ファイルも一緒に作成します。 ユーザのグループはpiと同じものを設定しておきます。 |
自動ログイン修正 | OSを起動するとpiユーザで自動ログインされますが、新ユーザでのログインに修正します。 |
ホスト名変更 | 複数のOSを判別できるように変更します。 ssh接続はIP管理にするとアドレスを忘れてしまいそうなのでホスト名で接続します。 |
sshサービス設定 | sshはポート変更、パスワードログイン無効化、rootログイン無効化の3点を設定します。 |
raspi-config の各設定 | I2Cの有効化やロケールの設定などを行います。 |
環境
環境は以下の構成でRaspberryPi はOS インストール直後でネットにつながっている状態から始めます。OSのネットワーク設定はBerryBoot で設定したネットワーク接続情報が自動的に反映されるようです。MacとRaspberry Pi は同じネットワークにつなげます。
リソース | 内容 |
---|---|
本体 | Raspberry Pi4 Model B 4GB RAM |
OS | BerryBoot 上のRaspberry Pi OS 32bit, 64bitの両方 |
Mac | Ansible コントローラマシン |
RaspberryPi 側の初期設定
Ansible はssh とPython で対象マシンを操作するので、Raspberry Pi側はこの2点だけ設定しておきます。デスクトップで最初に表示される設定ウィザードは「cancel」で閉じておきます。
ssh はraspi-config で有効化します。
「5 Interfacing Options」 > 「P2 SSH」> 「Yes」
Python はすでにインストール済みだったのでパッケージのアップデートだけ実施します。
$ sudo apt updage && sudo apt upgrade
Mac 側 の初期設定
Mac からRaspberry Pi にssh 接続できるか確認します。接続するのはデフォルトユーザの「pi」でパスワードは「raspberry」です。 同一ネットワークで接続するため、IPの指定はせずホスト名で接続します。
$ ssh pi@raspberrypi.local
Ansible のインストールはhomebrewで行います。作業用フォルダも一緒に作っておきます。
$ brew install ansible
$ mkdir ~/ansible
Ansible の設定
インストールできたらAnsible の設定ファイルを作成します。設定ファイルは主に3種類ありますが、1つはデフォルトの設定であれば不要なので2ファイルだけ作成します。ファイルは上記で作成した「ansible」フォルダ直下に作ります。
種類 | ファイル名 | 内容 |
---|---|---|
Ansible設定ファイル | ansible.cfg | Ansible 本体の設定です(今回は作らない) |
Inventoryファイル | inventory.yml (任意の名前) | 構成管理対象の接続先などを記述します |
Playbookファイル | raspi-init.yml (任意の名前) | 反映したい内容を記述します |
Inventory ファイル
接続先情報は以下の内容で作成しました。先に64bitOSから試してみます。
# 変数定義 all: vars: user_name: matucon host_name: pi464 ssh_key_name: id_rsa_pi4_64 modify_ssh_port: 33322 # デフォルトのラズパイ接続先(パスワードでログイン) pi_default: hosts: raspberrypi.local: vars: ansible_ssh_user: pi ansible_ssh_pass: raspberry
各対象マシンの環境依存になる値などは変数化が可能です。今回変数化した内容は以下の通りです。(64bitOS版の設定です)
変数 | 内容 | 値 |
---|---|---|
user_name | 新しいLinuxユーザ名 | matucon |
host_name | 新しいホスト名 | pi464 |
ssh_key_name | 新ユーザのssh鍵ファイル名 | id_rsa_pi4_64 |
modify_ssh_port | 変更するsshポート | 33322 |
ファイルができたらAnsible から疎通確認します。
$ ansible pi_default -m ping
以下のようなレスポンスになればOKです。
raspberrypi.local | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" }
Playbook ファイル
上記の設定内容をPlaybookファイルに落としたものが以下になります。(長くなってしまったので折りたたみました) タスクごとに日本語コメント入れましたのでご参考ください。
注意点としてネットワークの設定とpiユーザの無効化は一番最後に実施します。Ansibleはpiユーザでsshしてるので、この設定を途中変えるとAnsibleが実行できなくなってしまいます。
Playbookでは新ユーザのパスワードはとりあえずユーザ名と同じにしてるので、設定完了後に変更した方が良さそうです。
ここをクリックしてPlaybook表示
- hosts: pi_default
become: yes
become_user: root
tasks:
- name: パッケージアップデート
apt:
upgrade: dist
- name: パッケージインストール
apt:
pkg:
- vim
- ufw
- name: パッケージクレンジング
apt:
autoclean: yes
autoremove: yes
- name: 新ユーザ作成
user:
name: "{{ user_name }}"
generate_ssh_key: yes
ssh_key_file: /home/{{ user_name }}/.ssh/{{ ssh_key_name }}
password: "{{ user_name | password_hash('sha512') }}"
update_password: on_create
- name: 公開鍵のファイル名変更
copy:
remote_src: true
src: /home/{{ user_name }}/.ssh/{{ ssh_key_name }}.pub
dest: /home/{{ user_name }}/.ssh/authorized_keys
- name: 秘密鍵をローカル(Mac)にコピー
fetch:
src: /home/{{ user_name }}/.ssh/{{ ssh_key_name }}
dest: ~/.ssh/{{ ssh_key_name }}
flat: yes
- name: 秘密鍵の権限変更
file:
path: /home/{{ user_name }}/.ssh/authorized_keys
owner: "{{ user_name }}"
group: "{{ user_name }}"
mode: '0600'
- name: .ssh ディレクトリの権限変更
file:
path: /home/{{ user_name }}/.ssh/
state: directory
owner: "{{ user_name }}"
group: "{{ user_name }}"
mode: '0700'
- name: piユーザと同じグループを新ユーザに設定
shell: |
usermod -G `groups pi | sed -r "s/^.*: //g" | sed "s/ /,/g"` {{ user_name }}
- name: piユーザのホームディレクトリを新ユーザのホームディレクトリにコピー
copy:
remote_src: true
src: /home/pi/
dest: /home/matu/
- name: sudoers 既存ファイル削除
file:
path: /etc/sudoers.d/010_{{ user_name }}-nopasswd
state: absent
- name: sudoers 作成
file:
path: /etc/sudoers.d/010_{{ user_name }}-nopasswd
state: touch
owner: root
group: root
mode: 0440
- name: sudoers 追記
lineinfile:
dest: /etc/sudoers.d/010_{{ user_name }}-nopasswd
state: present
regexp: '^.*'
line: "{{ user_name }} ALL=(ALL) NOPASSWD: ALL"
- name: ssh 設定(パスワードでのログイン無効化)
lineinfile:
dest: /etc/ssh/sshd_config
state: present
backup: true
regexp: '^#PasswordAuthentication.*'
line: 'PasswordAuthentication no'
- name: ssh 設定(ポート変更)
lineinfile:
dest: /etc/ssh/sshd_config
state: present
backup: true
regexp: '^#Port.*'
line: 'Port {{ modify_ssh_port }}'
- name: ssh 設定(rootでログイン無効化)
lineinfile:
dest: /etc/ssh/sshd_config
state: present
backup: true
regexp: '^#PermitRootLogin.*'
line: 'PermitRootLogin forced-commands-only'
- name: 自動ログイン設定
replace:
path: /etc/lightdm/lightdm.conf
regexp: '^autologin-user=pi$'
replace: 'autologin-user={{ user_name }}'
backup: true
- name: 自動ログインサービス設定
replace:
path: /etc/systemd/system/autologin@.service
regexp: '--autologin pi'
replace: '--autologin {{ user_name }}'
backup: true
- name: raspi-config設定
shell: |
raspi-config nonint do_hostname {{ host_name }}
raspi-config nonint do_change_locale ja_JP.UTF-8
raspi-config nonint do_change_timezone Asia/Tokyo
raspi-config nonint do_wifi_country JP
raspi-config nonint do_i2c 0 # yes
raspi-config nonint do_spi 0 # yes
- name: ホスト名変更
hostname:
name: "{{ host_name }}"
- name: Firewallリセット
ufw:
state: reset
policy: deny
logging: 'on'
- name: Firewallルール設定
ufw:
rule: allow
proto: tcp
src: 192.168.0.0/24 # 接続してるネットワークアドレスを指定
port: '{{ item }}'
loop:
- '80'
- '443'
- '8080'
- '5900'
- '{{ modify_ssh_port }}'
- name: Firewall有効化
ufw:
state: enabled
- name: pi ユーザ無効化
user:
name: pi
expires: -1
- name: piユーザパスワード変更
user:
name: pi
password: "{{ user_name | password_hash('sha512') }}"
- name: piユーザからsudo 権限削除
shell: gpasswd -d pi sudo
- name: piユーザのsudo 設定(このplaybook はpiユーザで実行してるので一番最後に実施しないと以降の処理がsudo できずにエラーとなる)
replace:
path: /etc/sudoers.d/010_pi-nopasswd
regexp: '^(pi.*)'
replace: '# \1'
Playbook 実行
以下のコマンドでPlaybook を反映します。OSアップデートなど時間のかかる処理があるので気長に待ちます。
$ ansible-playbook raspi-init.yml -i inventory.yml
完了したら手動でOSを再起動します。Ansibleで再起動することもできますが、再起動後のホスト名や接続可能ユーザが変わってしまうので再起動タスクがエラーになってしまいます。
鍵ファイルはMacの以下に出力されてるので、sshログインしてみます。
~/.ssh/id_rsa_pi4_64
以下のコマンドでログインできれば成功です。ポートとssh鍵ファイル名はInventory の変数値です。
$ ssh matucon@pi464.local -p 33322 -i ~/.ssh/id_rsa_pi4_64
確認できたら32bitOSにも反映してみます。設定ファイルの変更箇所は1つだけで、Inventoryの「64」を「32」に変えて実行します。 私の環境ではどちらのOSも上記Playbookで設定することができました。
コマンドが長いので、Macの「~/.ssh/config」に以下を追記しておくと接続が楽になります。
Host pi432 User matucon Port 33322 HostName pi432.local identityFile ~/.ssh/id_rsa_pi4_32 TCPKeepAlive yes IdentitiesOnly yes Host pi464 User matucon Port 33322 HostName pi464.local identityFile ~/.ssh/id_rsa_pi4_64 TCPKeepAlive yes IdentitiesOnly yes
接続コマンド
$ ssh pi432
途中でエラーが出た場合
今回作ったPlaybookは冪等生が一部ないので同じPlaybookを何度も実行すると既存の設定が壊れてしまう可能性があります。なので問題を直して再実行する場合はエラー直前のタスクまでコメントアウトして実行するかBerryBootでOSResetしてからPlaybookをかけ直しました。
Playbook先頭でやっているOSアップデートは時間がかかるので、アップデートまで完了したものをBerryBootでイメージ化しておくとやり直ししやすくなります。
設定ファイルを編集するタスクであればバックアップが取られているので(ファイル名に日付を入れてバックアップされる)ここから巻き戻すことも可能です。
最後に
AnsibleによるRaspberryPiのプロビジョニングを行いました。Playbook は便宜上1ファイルにまとめてしまいましたが、長くなり過ぎてしまってるので分割したほうがデバッグも管理しやすそうです。