0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ubuntu 20.04→22.04 の `do-release-upgrade` で `ubuntu-minimal could not be located` が出た原因が Python urllib の 403 だった話

0
Posted at

はじめに

Ubuntu 20.04 LTS から Ubuntu 22.04 LTS へ do-release-upgrade でアップグレードしようとしたところ、次のエラーで停止する事例に遭遇しました。

After updating your package information, the essential package
'ubuntu-minimal' could not be located.

This may be because you have no official mirrors listed in your software sources,
or because of excessive load on the mirror you are using.
See /etc/apt/sources.list

一見すると、

  • /etc/apt/sources.list が壊れている
  • 公式Ubuntuミラーを見ていない
  • ubuntu-minimal がAPTから見えていない
  • Ubuntu 20.04 / 22.04 / 24.04 のリポジトリが混在している

といった問題に見えます。

しかし、今回のケースでは少し違いました。

結論からいうと、ubuntu-minimal が存在しなかったわけではなく、do-release-upgrade の内部処理が Ubuntu 22.04(jammy)の Release ファイルを Python urllib 経由で取得しようとした際に 403 Forbidden で弾かれたことが原因と考えられます。

最終的には、Ubuntuの参照ミラーを archive.ubuntu.com / security.ubuntu.com から jp.archive.ubuntu.com に変更することで、アップグレードが先へ進みました。


環境

今回の環境は以下です。

Ubuntu 20.04.6 LTS
Codename: focal

アップグレード対象は、

Ubuntu 20.04 focal → Ubuntu 22.04 jammy

です。

もともとは Ubuntu 22.04 → 24.04 の手順を見て作業していましたが、実際のサーバーは Ubuntu 20.04 でした。

そのため、まず必要なのは 20.04 → 22.04 のアップグレードでした。


発生したエラー

sudo do-release-upgrade を実行すると、以下のように停止しました。

After updating your package information, the essential package
'ubuntu-minimal' could not be located.

This may be because you have no official mirrors listed in your software sources,
or because of excessive load on the mirror you are using.
See /etc/apt/sources.list

その後、処理は中断されます。

Restoring original system state
Aborting

まず確認したこと

現在のUbuntuバージョン

lsb_release -a

結果:

Distributor ID: Ubuntu
Description:    Ubuntu 20.04.6 LTS
Release:        20.04
Codename:       focal

現在は Ubuntu 20.04 focal であることが確認できました。


/etc/apt/sources.list の確認

cat /etc/apt/sources.list

主な内容は以下でした。

deb http://archive.ubuntu.com/ubuntu focal main restricted
deb http://archive.ubuntu.com/ubuntu focal-updates main restricted
deb http://archive.ubuntu.com/ubuntu focal universe
deb http://archive.ubuntu.com/ubuntu focal-updates universe
deb http://archive.ubuntu.com/ubuntu focal multiverse
deb http://archive.ubuntu.com/ubuntu focal-updates multiverse
deb http://archive.ubuntu.com/ubuntu focal-backports main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu focal-security main restricted
deb http://security.ubuntu.com/ubuntu focal-security universe
deb http://security.ubuntu.com/ubuntu focal-security multiverse

focal main が存在しており、Ubuntu 20.04 用としては大きく壊れているようには見えませんでした。


通常のAPTでは ubuntu-minimal は見えていた

次に、通常のAPTで ubuntu-minimal が見えるか確認しました。

sudo apt update
apt-cache policy ubuntu-minimal

結果:

ubuntu-minimal:
  Installed: 1.450.3
  Candidate: 1.450.3

ここが重要です。

通常のAPTでは、ubuntu-minimal はちゃんと見えています。

つまり、少なくともこの時点で、

ubuntu-minimal が本当に存在しない

という状態ではありませんでした。


do-release-upgrade のログを確認

次に、do-release-upgrade のログを確認しました。

grep -n "No 'ubuntu-minimal'" /var/log/dist-upgrade/main.log /var/log/dist-upgrade/*/main.log

結果として、毎回同じようなエラーが記録されていました。

ERROR No 'ubuntu-minimal' available/downloadable after sources.list rewrite+update

さらに、該当箇所の前後を確認しました。

nl -ba /var/log/dist-upgrade/main.log | sed -n '45,90p'

すると、重要な行が見つかりました。

DEBUG error from httplib: 'HTTP Error 403: Forbidden'

しかも、archive.ubuntu.com 側だけでなく、security.ubuntu.com 側でも同様に 403 になっていました。

流れとしては以下のように読めます。

do-release-upgrade が focal → jammy へ切り替える確認を行う
↓
jammy の Release ファイルを取得しに行く
↓
Python/httplib 経由で HTTP Error 403: Forbidden
↓
jammy のパッケージ情報を作れない
↓
ubuntu-minimal が available/downloadable ではないと判定
↓
アップグレード中止

つまり、77行目付近の

No 'ubuntu-minimal' available/downloadable after sources.list rewrite+update

は、直接の原因というより、その前の 403 Forbidden の結果として出ている最終判定と考えられます。


curl では 200 OK だった

ここで、実際に curl で同じURLへアクセスしてみました。

curl -I http://archive.ubuntu.com/ubuntu/dists/jammy/Release
curl -I https://archive.ubuntu.com/ubuntu/dists/jammy/Release
curl -I http://security.ubuntu.com/ubuntu/dists/jammy/Release
curl -I https://security.ubuntu.com/ubuntu/dists/jammy/Release

結果は、いずれも 200 OK でした。

つまり、単純に

サーバーから jammy の Release ファイルへアクセスできない

という話ではありません。

curl では通るのに、do-release-upgrade 内部では 403 になります。


Python urllib で再現確認

do-release-upgrade のログには httplib と出ていたため、Python経由で同じURLへアクセスしてみました。

python3 - <<'PY'
import urllib.request

urls = [
    "http://archive.ubuntu.com/ubuntu/dists/jammy/Release",
    "https://archive.ubuntu.com/ubuntu/dists/jammy/Release",
    "http://security.ubuntu.com/ubuntu/dists/jammy/Release",
    "https://security.ubuntu.com/ubuntu/dists/jammy/Release",
]

for url in urls:
    print("URL:", url)
    try:
        with urllib.request.urlopen(url, timeout=20) as r:
            print("STATUS:", r.status)
    except Exception as e:
        print("ERROR:", repr(e))
    print()
PY

結果は、すべて 403 Forbidden でした。

HTTP Error 403: Forbidden

ここでかなり原因が絞れました。

curl では 200 OK
Python urllib では 403 Forbidden
do-release-upgrade でも 403 Forbidden

つまり、Python urllib 系のアクセスだけが拒否されている可能性が高くなりました。


User-Agent を変えると Python でも 200 になった

さらに、Python urllib の User-Agent を curl 風に変えて再確認しました。

python3 - <<'PY'
import urllib.request

urls = [
    "http://archive.ubuntu.com/ubuntu/dists/jammy/Release",
    "https://archive.ubuntu.com/ubuntu/dists/jammy/Release",
    "http://security.ubuntu.com/ubuntu/dists/jammy/Release",
    "https://security.ubuntu.com/ubuntu/dists/jammy/Release",
]

for url in urls:
    print("URL:", url)
    try:
        req = urllib.request.Request(
            url,
            headers={"User-Agent": "curl/7.68.0"}
        )
        with urllib.request.urlopen(req, timeout=20) as r:
            print("STATUS:", r.status)
    except Exception as e:
        print("ERROR:", repr(e))
    print()
PY

すると、200 が返りました。

つまり、

Python urllib の標準User-Agent → 403
Python urllib + curl風User-Agent → 200
curl → 200

という状態です。

この結果から、CloudflareなどのBot判定・WAFにより、Python urllib の標準User-Agentが拒否されていた可能性が高いと考えました。


参考:Cloudflare と Python urllib の 403 事例

今回の現象に近い既知事例として、Cloudflare配下のサイトで Python urllib のデフォルトUser-Agentが 403 Forbidden になる報告があります。

Cloudflare Community

Cloudflare Community では、Cloudflare API がデフォルトの Python-urllib/3.x User-Agent を拒否し、User-Agentを変更すると通るようになったという報告があります。

https://community.cloudflare.com/t/api-call-suddenly-returns-403-forbidden/396383

Python urllib だけ 403 になる日本語検証記事

Python標準ライブラリの urllib でだけ 403 Forbidden になり、User-Agentを指定すると通るという検証記事です。

https://nikkie-ftnext.hatenablog.com/entry/python-urllib-403-cloudflare-user-agent

Python.org Discussions

Python.org のフォーラムでも、Cloudflare proxy 配下のURLに対して urllib.request.urlopen() が 403 になるという相談が出ています。

https://discuss.python.org/t/overcoming-cloudflare-urlopen-issue/106024

Stack Overflow Meta

Stack Overflowでも、Python-urllib で始まるUser-Agentが拒否される挙動について議論されています。

https://meta.stackoverflow.com/questions/310498/why-is-user-agent-python-urllib-rejected

今回の症状は、これらの事例とかなり近い挙動でした。


archive.ubuntu.com 周りの接続性問題

また、archive.ubuntu.com 周辺では、403 Forbidden やタイムアウトに関する報告もあります。

CircleCI Support

CircleCI のサポート記事では、archive.ubuntu.com への接続で 403 Forbidden や timeout が発生する場合があり、代替ミラーへの切り替えが回避策として紹介されています。

https://support.circleci.com/hc/en-us/articles/37474192881179-Resolving-Unable-to-connect-to-archive-ubuntu-com-403-Forbidden-Error-in-CircleCI

CircleCI Discuss

CircleCI Discuss でも、archive.ubuntu.com への接続問題が報告されています。

https://discuss.circleci.com/t/connection-issues-with-apt-get-from-archive-ubuntu-com/48094

ただし、これらは主にCI環境での apt-get の接続性問題であり、今回のような do-release-upgrade 内部の Python urllib による 403 と完全に同一の症例ではありません。

あくまで、

archive.ubuntu.com 周辺で 403 や接続性問題が起こり得る

という補足材料として見ています。


対処:Python urllib でも通るミラーへ変更

次に、国内ミラーで Python urllib が通るか確認しました。

python3 - <<'PY'
import urllib.request

urls = [
    "http://jp.archive.ubuntu.com/ubuntu/dists/jammy/Release",
    "https://jp.archive.ubuntu.com/ubuntu/dists/jammy/Release",
    "http://ftp.jaist.ac.jp/pub/Linux/ubuntu/dists/jammy/Release",
    "https://ftp.jaist.ac.jp/pub/Linux/ubuntu/dists/jammy/Release",
    "http://ftp.riken.jp/Linux/ubuntu/dists/jammy/Release",
    "https://ftp.riken.jp/Linux/ubuntu/dists/jammy/Release",
]

for url in urls:
    print("URL:", url)
    try:
        with urllib.request.urlopen(url, timeout=20) as r:
            print("STATUS:", r.status)
    except Exception as e:
        print("ERROR:", repr(e))
    print()
PY

jp.archive.ubuntu.com、JAIST、RIKEN などは Python urllib 標準アクセスでも 200 になりました。

今回は jp.archive.ubuntu.com を使うことにしました。

さらに、実際のアップグレードで使われる可能性がある suite も確認しました。

python3 - <<'PY'
import urllib.request

base = "http://jp.archive.ubuntu.com/ubuntu"
suites = ["jammy", "jammy-updates", "jammy-security", "jammy-backports"]

for suite in suites:
    url = f"{base}/dists/{suite}/Release"
    print("URL:", url)
    try:
        with urllib.request.urlopen(url, timeout=20) as r:
            print("STATUS:", r.status)
    except Exception as e:
        print("ERROR:", repr(e))
    print()
PY

すべて 200 でした。


/etc/apt/sources.list を変更

変更前にバックアップを取ります。

sudo cp -a /etc/apt/sources.list /etc/apt/sources.list.bak.$(date +%F-%H%M)

archive.ubuntu.comsecurity.ubuntu.comjp.archive.ubuntu.com に変更します。

sudo sed -i 's|http://archive.ubuntu.com/ubuntu|http://jp.archive.ubuntu.com/ubuntu|g' /etc/apt/sources.list

sudo sed -i 's|http://security.ubuntu.com/ubuntu|http://jp.archive.ubuntu.com/ubuntu|g' /etc/apt/sources.list

変更確認:

grep -R "^[^#]" /etc/apt/sources.list

すべて jp.archive.ubuntu.com に変わっていることを確認します。

deb http://jp.archive.ubuntu.com/ubuntu focal main restricted
deb http://jp.archive.ubuntu.com/ubuntu focal-updates main restricted
...
deb http://jp.archive.ubuntu.com/ubuntu focal-security main restricted

その後、APT更新を確認します。

sudo apt update

さらに ubuntu-minimal が見えることを確認します。

apt-cache policy ubuntu-minimal

結果:

Installed: 1.450.3
Candidate: 1.450.3

do-release-upgrade を再実行

まず確認:

sudo do-release-upgrade -c

New release '22.04.x LTS' available. のように表示されればOKです。

SSH作業の場合は、tmux の中で本番実行するのが安全です。

tmux new -s ubuntu-upgrade2
sudo do-release-upgrade

既存の tmux セッション名と被る場合は、ubuntu-upgrade3 など別名で構いません。

今回はミラー変更後、以前の ubuntu-minimal could not be located の箇所を越えて、通常のアップグレード手順に戻りました。


注意点

1. いきなり sources.list を編集しない

今回のようなエラーが出た場合でも、まずは確認が必要です。

最低限、以下を確認した方がよいです。

lsb_release -a
cat /etc/apt/sources.list
sudo apt update
apt-cache policy ubuntu-minimal

通常APTで ubuntu-minimal が見えているかどうかは重要です。


2. do-release-upgrade の再実行を繰り返さない

同じ原因で止まっている場合、再実行しても同じ場所で止まります。

ログを見る方が早いです。

grep -n "No 'ubuntu-minimal'" /var/log/dist-upgrade/main.log /var/log/dist-upgrade/*/main.log
nl -ba /var/log/dist-upgrade/main.log | sed -n '45,90p'

3. apt のロックファイルを安易に消さない

途中で以下のようなエラーが出ることがあります。

Could not get lock /var/lib/apt/lists/lock

この場合、do-release-upgradeapt のプロセスがまだ動いている可能性があります。

安易にロックファイルを削除せず、まず確認します。

ps -fp <PID>
pgrep -a "do-release-upgrade|apt|dpkg|jammy"
tmux ls

4. cloud-init 管理の sources.list に注意

今回の環境では、/etc/apt/sources.list の冒頭に以下のような注意書きがありました。

Note, this file is written by cloud-init on first boot of an instance
modifications made here will not survive a re-bundle.

cloud-init 管理の環境では、sources.list の扱いに注意が必要です。

一時的なアップグレード対応として変更する場合でも、必ずバックアップを取ってから作業した方がよいです。


今回の原因まとめ

今回の原因は、かなり高い確度で以下だと考えています。

do-release-upgrade が 20.04 focal → 22.04 jammy へ切り替える確認中、
Python urllib/httplib 経由で archive.ubuntu.com / security.ubuntu.com の
jammy Release ファイルを取得しようとしたところ 403 Forbidden になった。

そのため jammy 側のパッケージ情報を作れず、
結果として ubuntu-minimal が available/downloadable ではないと判定された。

重要なのは、ubuntu-minimal が本当に存在しなかったわけではないという点です。

通常のAPTでは ubuntu-minimal は見えていました。

apt-cache policy ubuntu-minimal
Installed: 1.450.3
Candidate: 1.450.3

しかし、do-release-upgrade 内部の取得処理では 403 になっていました。

最終的に、

archive.ubuntu.com / security.ubuntu.com
→ Python urllib 標準アクセスで 403

jp.archive.ubuntu.com
→ Python urllib 標準アクセスで 200

だったため、ミラーを jp.archive.ubuntu.com に変更することで回避できました。


参考リンク

Cloudflare / Python urllib 関連

Cloudflare Community: API call suddenly returns 403/Forbidden
https://community.cloudflare.com/t/api-call-suddenly-returns-403-forbidden/396383

Python標準ライブラリのurllibでだけ403 Forbiddenとなる
https://nikkie-ftnext.hatenablog.com/entry/python-urllib-403-cloudflare-user-agent

Python.org Discussions: Overcoming cloudflare urlopen issue
https://discuss.python.org/t/overcoming-cloudflare-urlopen-issue/106024

Stack Overflow Meta: Why is user-agent Python-urllib rejected?
https://meta.stackoverflow.com/questions/310498/why-is-user-agent-python-urllib-rejected

archive.ubuntu.com 接続性問題

CircleCI Support: Resolving Unable to connect to archive.ubuntu.com, 403 Forbidden Error in CircleCI
https://support.circleci.com/hc/en-us/articles/37474192881179-Resolving-Unable-to-connect-to-archive-ubuntu-com-403-Forbidden-Error-in-CircleCI

CircleCI Discuss: Connection issues with apt-get from archive.ubuntu.com
https://discuss.circleci.com/t/connection-issues-with-apt-get-from-archive-ubuntu-com/48094

Ubuntuミラー関連

Ubuntu Mirrors JP
https://mirrors.ubuntu.com/JP.txt

Ubuntu aptが遅かったので jp.archive.ubuntu.com を変更
https://kuni92.net/2022/10/ubuntu-apt-jparchiveubuntucom.html


おわりに

ubuntu-minimal could not be located と出ると、まず sources.list の破損やリポジトリ設定ミスを疑います。

しかし今回のように、

通常APTでは ubuntu-minimal が見える
curl では jammy Release が取れる
でも do-release-upgrade 内部では 403 になる

というケースもあります。

特に今回のような、

curl は 200
Python urllib は 403
User-Agent を変えると 200
別ミラーなら Python urllib でも 200

という挙動は、Cloudflare / Bot判定 / User-Agent / ミラー経路が絡んだ少しレアな事例だと思います。

同じエラーで詰まった場合は、ubuntu-minimal そのものだけでなく、do-release-upgrade が内部で使う通信経路も疑ってみるとよいかもしれません。


---

補足として、上の記事内の外部根拠は、Cloudflare Communityで `Python-urllib/3.x` のUser-Agentが403になった報告、Python urllibだけ403になる検証記事、Python.orgでのCloudflare proxy下の `urlopen` 403相談、CircleCIによる `archive.ubuntu.com` の403/timeoutと代替ミラー案内を確認した上で入れています。:contentReference[oaicite:0]{index=0}
::contentReference[oaicite:1]{index=1}

(おわり)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?