サマリー
Debian系のディストリビューションに搭載されている UnattendedUpgrades を使用して、Ubuntuのパッケージを定期的に自動更新してみます。
unattended-upgradesとは
UnattendedUpgrades は unattended-upgrades というパッケージで提供される、自動更新用のプログラムです。
大抵のDebianディストリビューションにはデフォルトでインストールされています。
デスクトップ環境を利用している人であれば、ソフトウェアセンターで無意識に設定しているかもしれません。
初期設定のままであればごく一部のパッケージを自動更新する程度なのですが、追加したリポジトリを対象にしたり必要に応じて再起動したり出来るので、全自動運転にはもってこいです。
また、この自動更新によって特定のバージョンでしか動かないアプリケーションやドライバーが急に動かなくなることもあるので、活用はしなくとも存在を認識しておく必要はあるでしょう。
定期的にパッケージ一覧を更新している apt-daily.timer
やunattended-upgradeコマンドを呼び出す apt-daily-upgrade.timer
といったスケジューラは apt
パッケージのものでunattended-upgradesパッケージには含まれていませんが、関連するため合わせて見ていく必要があります。
- https://packages.debian.org/search?suite=bullseye§ion=all&arch=any&searchon=contents&keywords=apt-daily.timer
- https://packages.debian.org/search?suite=bullseye§ion=all&arch=any&searchon=contents&keywords=apt-daily-upgrade.timer
以降は、Ubuntu 20.04を対象に設定を確認していきます。
unattended-upgradesの初期設定確認
注目すべきファイルは以下の3つです。
/etc/apt/apt.conf.d/10periodic
/etc/apt/apt.conf.d/20auto-upgrades
/etc/apt/apt.conf.d/50unattended-upgrades
おそらく、特に手を加えていなければ以下のような設定になっていると思います。
$ apt-config dump | grep Periodic
APT::Periodic "";
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "0";
APT::Periodic::AutocleanInterval "0";
APT::Periodic::Unattended-Upgrade "1";
重複した設定が含まれていますが、以下の設定ファイルに書かれている通りです。
$ cat /etc/apt/apt.conf.d/10periodic
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "0";
APT::Periodic::AutocleanInterval "0";
$ cat /etc/apt/apt.conf.d/20auto-upgrades
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
更に、unattended-upgradesの初期設定を見てみます。
最初から APT::Periodic::Unattended-Upgrade "1";
が設定されており、unattended-upgradesが有効になっていることが確認できます。
$ apt-config dump | grep Unatte
APT::Periodic::Unattended-Upgrade "1";
Unattended-Upgrade "";
Unattended-Upgrade::Allowed-Origins "";
Unattended-Upgrade::Allowed-Origins:: "${distro_id}:${distro_codename}";
Unattended-Upgrade::Allowed-Origins:: "${distro_id}:${distro_codename}-security";
Unattended-Upgrade::Allowed-Origins:: "${distro_id}ESMApps:${distro_codename}-apps-security";
Unattended-Upgrade::Allowed-Origins:: "${distro_id}ESM:${distro_codename}-infra-security";
Unattended-Upgrade::DevRelease "auto";
上記の設定は、以下のファイルに書かれている通りです。
$ grep -v -e "//" -e "^$" /etc/apt/apt.conf.d/50unattended-upgrades
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}";
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::Package-Blacklist {
};
Unattended-Upgrade::DevRelease "auto";
また、定期的な更新がいつ行われているのかは apt-daily
関係のtimerを見ることで確認できます。
$ systemctl list-timers apt-daily*
NEXT LEFT LAST PASSED UNIT ACTIVATES
Wed 2023-06-14 06:06:08 JST 11h left Tue 2023-06-13 18:08:51 JST 21min ago apt-daily-upgrade.timer apt-daily-upgrade.service
Wed 2023-06-14 06:44:10 JST 12h left Tue 2023-06-13 18:08:51 JST 21min ago apt-daily.timer apt-daily.service
2 timers listed.
Pass --all to see loaded but inactive timers, too.
apt-daily.timer
はパッケージ一覧を更新する apt-daily.service
の実行を、
apt-daily-upgrade.timer
はunattended-upgradeコマンドを実行してパッケージを更新する apt-daily-upgrade.service
の実行を、それぞれスケジュールしています。
(呼び出しているスクリプトはどちらも /usr/lib/apt/apt.systemd.daily
です)
apt-daily-upgrade.timer
の設定値を見ると、大体1日1回(6時~7時)実行するように設定されていました。
$ cat /lib/systemd/system/apt-daily-upgrade.timer
[Unit]
Description=Daily apt upgrade and clean activities
After=apt-daily.timer
[Timer]
OnCalendar=*-*-* 6:00
RandomizedDelaySec=60m
Persistent=true
[Install]
WantedBy=timers.target
ここまでが、Ubuntu 20.04の初期設定のようです。
unattended-upgradesの設定(標準パッケージ)
unattended-upgradesでディストリビューションに付属する標準パッケージを自動アップデートの対象とする場合は 50unattended-upgrades
のコメントアウトを外すだけで実現できます。
--- /tmp/50unattended-upgrades 2023-06-12 17:13:22.422196783 +0900
+++ 50unattended-upgrades 2023-06-12 17:40:37.276850113 +0900
@@ -12,9 +12,9 @@
// should also install from here by default.
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
-// "${distro_id}:${distro_codename}-updates";
-// "${distro_id}:${distro_codename}-proposed";
-// "${distro_id}:${distro_codename}-backports";
+ "${distro_id}:${distro_codename}-updates";
+ "${distro_id}:${distro_codename}-proposed";
+ "${distro_id}:${distro_codename}-backports";
};
// Python regular expressions, matching packages to exclude from upgrading
とはいえ、OSにデフォルトでついてくる設定ファイルを直接変更すると、アップデート時に何かと上書きされる可能性もありますので、別のファイルに出力しておきます。
$ cat <<'_EOL_' | sudo tee /etc/apt/apt.conf.d/51unattended-upgrades
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-updates";
"${distro_id}:${distro_codename}-proposed";
"${distro_id}:${distro_codename}-backports";
};
_EOL_
Ubuntu 20.04の場合は ${distro_id}
には Ubuntu
、 ${distro_codename}
には focal
が入ります。
sudo unattended-upgrade --dry-run
コマンドを実行することで、アップデート対象となるパッケージが確認できます。(以下出力例)
$ sudo unattended-upgrade --dry-run -v
Starting unattended upgrades script
Allowed origins are: o=Ubuntu,a=focal, o=Ubuntu,a=focal-security, o=UbuntuESMApps,a=focal-apps-security, o=UbuntuESM,a=focal-infra-security, o=Ubuntu,a=focal-updates, o=Ubuntu,a=focal-proposed, o=Ubuntu,a=focal-backports
Initial blacklist:
Initial whitelist (not strict):
Option --dry-run given, *not* performing real actions
Packages that will be upgraded: apport grub-common grub-pc grub-pc-bin grub2-common iptables libip4tc2 libip6tc2 libxtables12 mokutil python3-apport python3-problem-report tzdata
Writing dpkg log to /var/log/unattended-upgrades/unattended-upgrades-dpkg.log
/usr/bin/dpkg --status-fd 10 --no-triggers --unpack --auto-deconfigure /var/cache/apt/archives/iptables_1.8.4-3ubuntu2.1_amd64.deb /var/cache/apt/archives/libxtables12_1.8.4-3ubuntu2.1_amd64.deb /var/cache/apt/archives/libip6tc2_1.8.4-3ubuntu2.1_amd64.deb /var/cache/apt/archives/libip4tc2_1.8.4-3ubuntu2.1_amd64.deb
/usr/bin/dpkg --status-fd 10 --configure --pending
Preconfiguring packages ...
Preconfiguring packages ...
/usr/bin/dpkg --status-fd 10 --no-triggers --unpack --auto-deconfigure /var/cache/apt/archives/tzdata_2023c-0ubuntu0.20.04.2_all.deb
/usr/bin/dpkg --status-fd 10 --configure --pending
/usr/bin/dpkg --status-fd 10 --no-triggers --unpack --auto-deconfigure /var/cache/apt/archives/python3-problem-report_2.20.11-0ubuntu27.27_all.deb
/usr/bin/dpkg --status-fd 10 --configure --pending
/usr/bin/dpkg --status-fd 10 --no-triggers --unpack --auto-deconfigure /var/cache/apt/archives/python3-apport_2.20.11-0ubuntu27.27_all.deb /var/cache/apt/archives/apport_2.20.11-0ubuntu27.27_all.deb
/usr/bin/dpkg --status-fd 10 --configure --pending
/usr/bin/dpkg --status-fd 10 --no-triggers --unpack --auto-deconfigure /var/cache/apt/archives/mokutil_0.6.0-2~20.04.1_amd64.deb
/usr/bin/dpkg --status-fd 10 --configure --pending
Preconfiguring packages ...
Preconfiguring packages ...
/usr/bin/dpkg --status-fd 10 --no-triggers --unpack --auto-deconfigure /var/cache/apt/archives/grub-pc_2.04-1ubuntu26.17_amd64.deb /var/cache/apt/archives/grub2-common_2.04-1ubuntu26.17_amd64.deb /var/cache/apt/archives/grub-pc-bin_2.04-1ubuntu26.17_amd64.deb /var/cache/apt/archives/grub-common_2.04-1ubuntu26.17_amd64.deb
/usr/bin/dpkg --status-fd 10 --configure --pending
All upgrades installed
The list of kept packages can't be calculated in dry-run mode.
何も表示されない場合は Unattended-Upgrade::Allowed-Origins
の設定が間違っているか、更新対象のパッケージが無いかのどちらかでしょう。
更新対象パッケージが無いと動きが見えにくい機能でもあるので、設定の動作確認の際はパッケージ更新が遅れている環境を使うと分かりやすいと思います。
これで、寝て起きたら自動的にUbuntuの標準パッケージが更新されている準備が整いました。
タイマーが待てない人は sudo unattended-upgrade
を手動で実行してアップデートの動きを確認してみても良いでしょう。
unattended-upgradesの設定(追加リポジトリ)
追加したリポジトリも含めて自動更新したい場合は、先ほどと同様に Unattended-Upgrade::Allowed-Origins
にリポジトリに対応した設定を追加します。
対象となるリポジトリの名称を知る必要があるので /var/lib/apt/lists
から対象のリポジトリ登録を確認します。(以下はgitlab-eeの場合の例)
$ grep -e Origin -e Suite /var/lib/apt/lists/packages.gitlab.com_gitlab_gitlab-ee_ubuntu_dists_focal_InRelease
Origin: packages.gitlab.com/gitlab/gitlab-ee
Suite: focal
Unattended-Upgrade::Allowed-Origins
の記述形式は ${Origin}:${Suite}
なので、
"packages.gitlab.com/gitlab/gitlab-ee:${distro_codename}";
を Unattended-Upgrade::Allowed-Origins
に追加することで自動アップデートの対象となります。
例えば以下のように、gitlab-ee用のファイルを作成します。
$ cat <<'_EOL_' | sudo tee /etc/apt/apt.conf.d/51unattended-upgrades-gitlab-ee
Unattended-Upgrade::Allowed-Origins {
"packages.gitlab.com/gitlab/gitlab-ee:${distro_codename}";
};
_EOL_
sudo unattended-upgrade --dry-run
で確認してみると 16.0.4-ee.0
がアップデート対象として選出できていることが分かります。
$ sudo unattended-upgrade --dry-run -v
Starting unattended upgrades script
Allowed origins are: o=Ubuntu,a=focal, o=Ubuntu,a=focal-security, o=UbuntuESMApps,a=focal-apps-security, o=UbuntuESM,a=focal-infra-security, o=packages.gitlab.com/gitlab/gitlab-ee,a=focal
Initial blacklist:
Initial whitelist (not strict):
Option --dry-run given, *not* performing real actions
Packages that will be upgraded: gitlab-ee
Writing dpkg log to /var/log/unattended-upgrades/unattended-upgrades-dpkg.log
/usr/bin/dpkg --status-fd 10 --no-triggers --unpack --auto-deconfigure /var/cache/apt/archives/gitlab-ee_16.0.4-ee.0_amd64.deb
/usr/bin/dpkg --status-fd 10 --configure --pending
All upgrades installed
The list of kept packages can't be calculated in dry-run mode.
apt list --upgradable
でも同じパッケージが確認できます。
$ apt list --upgradable
Listing... Done
gitlab-ee/focal 16.0.4-ee.0 amd64 [upgradable from: 16.0.3-ee.0]
N: There are 279 additional versions. Please use the '-a' switch to see them.
ログの見やすさのため、先ほど作った 51unattended-upgrades
は /tmp
に移動してあります(手順外)
(Option) OSの自動再起動
通常のパッケージ更新の場合はサービスの再起動で済みますが、linux-image-.*-generic
をはじめとするカーネル関係のパッケージが更新された場合は再起動を要求されるケースもあります。
任意の時間に自動的にOSの再起動がしたい場合は Unattended-Upgrade::Automatic-Reboot
と Unattended-Upgrade::Automatic-Reboot-Time
を設定しましょう。
例えば、自動的に再起動するスケジューラを "02:00" にセットする場合は以下のようになります。
$ cat <<'_EOL_' | sudo tee /etc/apt/apt.conf.d/51unattended-upgrades-reboot-policy
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "02:00";
_EOL_
Unattended-Upgrade::Automatic-Reboot-Time
はデフォルトが "now"
なので、unattended-upgradeコマンド実行後に必要であれば即座に再起動されます。
(Option) 更新完了に伴う処理を追加したい場合
unattended-upgradesによって自動でパッケージが更新されるのは良いのですが、更新が終わった後に通知をしたり、何らかの処理を入れたい場合があります。
メールを送るだけで良ければ Unattended-Upgrade::Mail
の設定で済みそうですが、例えばSlackにメッセージを送ったり、カーネルの更新に合わせて独自のカーネルモジュールをコンパイルしたり、もう少し自由な処理を追加する方法があるとありがたいところです。
そこで、力技で実現する方法を考えてみます。
unattended-upgradesによって更新されるファイルは以下の2つがあります。
file | description |
---|---|
/var/lib/apt/periodic/unattended-upgrades-stamp |
unattended-upgradeが完了したら更新される |
/var/log/unattended-upgrades/unattended-upgrades-dpkg.log |
unattended-upgradeでパッケージをする時のログ出力先 |
/var/lib/apt/periodic/unattended-upgrades-stamp
はパッケージ更新の有無によらず、unattended-upgradesの完了後にタイムスタンプが更新されるので、このファイルを監視しておくことで追加の処理を作ることができます。
このファイルは --dry-run
実行時も更新されるので、動作を試すのも容易です。
今回はsystemdを利用してサンプルを実装してみます。
まず /etc/systemd/system/unattended-upgrades-notify.path
を作成し、/var/lib/apt/periodic/unattended-upgrades-stamp
の変更を監視します。
[Unit]
Description="Check unattended upgrade finished"
[Path]
PathChanged=/var/lib/apt/periodic/unattended-upgrades-stamp
[Install]
WantedBy=multi-user.target
次に /etc/systemd/system/unattended-upgrades-notify.service
を作成し、実行権限を付与した任意のスクリプトを呼び出せるようにします。
[Unit]
Description="Run unattended upgrade additional script"
[Service]
Type=oneshot
ExecStart=/opt/unattended-upgrades-script.sh
[Install]
WantedBy=multi-user.target
もちろん作成するファイル名は何でも良いのですが *.path
はデフォルトで同名の *.service
に対応するので、名前は揃えておくと良いでしょう。
By default, a service by the same name as the path (except for the suffix) is activated. Example: a path file foo.path activates a matching service foo.service.
https://man.archlinux.org/man/systemd.path.5.en
/opt/unattended-upgrades-script.sh
の中身は任意の処理を書きましょう。今回はechoするだけです。
#!/bin/bash
set -eu
echo "Post processing after unattended-upgrade."
systemdのサービスを有効にします。
sudo systemctl daemon-reload
sudo systemctl start unattended-upgrades-notify.path
sudo systemctl enable unattended-upgrades-notify.path
sudo systemctl enable unattended-upgrades-notify.service
あとは sudo unattended-upgrade --dry-run
を実行し、任意の処理のスクリプトが呼び出されるか確認してみましょう。
$ systemctl status unattended-upgrades-notify.service
● unattended-upgrades-notify.service - "Run unattended upgrade additional script"
Loaded: loaded (/etc/systemd/system/unattended-upgrades-notify.service; enabled; vendor preset: enabled)
Active: inactive (dead) since Tue 2023-06-13 23:55:37 JST; 552ms ago
TriggeredBy: ● unattended-upgrades-notify.path
Process: 6833 ExecStart=/opt/unattended-upgrades-script.sh (code=exited, status=0/SUCCESS)
Main PID: 6833 (code=exited, status=0/SUCCESS)
Jun 13 23:55:37 qiita-gitlab systemd[1]: Starting "Run unattended upgrade additional script"...
Jun 13 23:55:37 qiita-gitlab unattended-upgrades-script.sh[6833]: Post processing after unattended-upgrade.
Jun 13 23:55:37 qiita-gitlab systemd[1]: unattended-upgrades-notify.service: Succeeded.
Jun 13 23:55:37 qiita-gitlab systemd[1]: Finished "Run unattended upgrade additional script".
Post processing after unattended-upgrade.
がログに表示されており、スクリプトが呼び出されることが確認できました。
あとは、必要に応じてスクリプトを拡張するだけです。
おしまい
unattended-upgradesを使って、パッケージの自動更新ができるようになりました。
パッケージの自動更新が機能として可能でも、システムには様々な条件がありますので適切に機能させるのは難しいものです。
状況や用途が許すのであれば、寝ている間に自動更新やOSの再起動ができるので運用負担が軽減できます。
仮に定期実行によってパッケージ更新をしなかったとしても、設定だけしておいてunattended-upgradeコマンドを手動で実行することで、パッケージ更新から再起動までを1コマンドで実行できるようになるので、それだけでも利用価値があると思います。