IPv6 only環境でcertbotをpipインストールする (Let's EncryptでSSL証明書を自動更新する)
Let's EncryptでSSL証明書の更新を自動で行いたいため、Rocky Linux 8のIPv6 only環境でcertbotをpipインストールしたときのメモを残します。
TL;DR
公式のsnapからインストールする手順だとIPv6サーバがなく途中で躓いてしまったため、snap経由ではなくpipコマンドでcertbotをインストールしました。 一日一回の自動実行はcrontabではなくsystemctlでcertbot.timerを設定しました。 SELinuxのポリシー設定のあるサーバでnginxの設定に躓いたので回避策を書きました。
- Rocky Linux 8 (CentOS8互換ディストリビューション)
- snapのサーバがIPv6に対応していない
- 仕方なくpipでcertbotをインストールする
- certbotを試しに実行してみる
- systemctlでcertbot.timerを設定する
- SELinuxを無効化せずにnginxを動かす
- nginxのVirtualHost設定ファイル例
1. Rocky Linux 8 (CentOS8互換ディストリビューション)
今回はひとまず Rocky Linux 8 (CentOS8互換ディストリビューション) でcertbotを動かしてみます。何も考えずにsnapを使いたい場合はUbuntuを使うのが一番良いです。ただ、簡単インストールできてしまうことの裏返しとして、プログラムの動作構成をあまりよく知らずに使えてしまうので、あえて茨の道を渡ってみるのも良いと思います。ちなみにApline Linuxはディストリビューションのシステム構成の問題でそもそもsnapはインストールできません(対応できておらずサポートされていません)。今回試すsnapを利用しないpipからのインストール手順であれば、Apline Linuxでもcertbotを動かすことができると思います。
2. snapのサーバがIPv6対応していない
Rocky Linux 8の環境でcertbotをインストールするために、まず公式で推奨されているsnapをインストールしようとするのですが、、、
sudo dnf install -y epel-release sudo dnf install -y snapd sudo systemctl enable --now snapd.socket sudo ln -s /var/lib/snapd/snap /snap sudo snap install core
上記コマンドをIPv6 only環境で実行すると、最後のsnapコマンド実行時に下記のようなエラーが発生してしまいます。
$ sudo snap install core error: cannot install "core": persistent network error: Post "https://api.snapcraft.io/v2/snaps/refresh": dial tcp 185.125.188.58:443: connect: network is unreachable
これはsnapのパッケージ管理サーバ api.snapcraft.io
がAAAAレコードを持たず、サーバとIPv6通信ができないことが原因です。
この件は、2017年からBug #1710022 “Snap store APIs (api.snapcraft.io) are not reachab...” : Bugs : Snap Store Serverというタイトルでバグ報告がされていますが、2022年になってもまだ何も進展がなく、Snap Store ServerにIPv6対応のAAAAレコードが追加されるような気配がありません…。
3. 仕方なくpipでcertbotをインストールする
snapはあきらめて、certbotの最新版をPython3.9のpip経由でインストールすることにします。前準備としてpip3コマンドをインストールします。
sudo dnf -y install python39-pip
pip自体のバージョンを pip3 install --upgrade pip
コマンドを実行して最新版にバージョンアップします。
$ pip3 --version pip 20.2.4 from /usr/lib/python3.9/site-packages/pip (python 3.9) $ sudo pip3 install --upgrade pip WARNING: Running pip install with root privileges is generally not a good idea. Try `pip3 install --user` instead. Collecting pip Downloading pip-22.0.4-py3-none-any.whl (2.1 MB) Installing collected packages: pip WARNING: The scripts pip, pip3 and pip3.9 are installed in '/usr/local/bin' which is not on PATH. Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location. Successfully installed pip-22.0.4 $ pip3 --version pip 22.0.4 from /usr/local/lib/python3.9/site-packages/pip (python 3.9)
certbotの動作に必要な依存ライブラリを予めOSパッケージからインストールしておきます。
sudo dnf -y install python39-devel python39-cffi python39-requests python39-urllib3 sudo dnf -y install augeas-libs libffi-devel openssl-libs
certbotをpip経由でインストールします。nginxとapacheのプラグインを同時にインストールしています。
sudo pip3 install certbot certbot-nginx certbot-apache
/usr/local/binにcertbotがインストールされるため、/usr/binにシンボリックリンクを貼っておきます。
sudo ln -s /usr/local/bin/certbot /usr/bin/certbot
4. certbotを試しに実行してみる
インストールされたcertbotのバージョンを確認します。
$ certbot --version certbot 1.27.0
--dry-runオプションを指定して、試しにcertbot renewを実行してみます。
$ sudo certbot renew --dry-run Saving debug log to /var/log/letsencrypt/letsencrypt.log - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No simulated renewals were attempted. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
何も前回の設定がなければ、certbotコマンドが無事動いているようなので、ひとまずこれでokです。おめでとうございます。
5. systemctlでcertbot.timerを設定する
certbot renewalを実行するサービスの定義certbot.serviceを作成します。ExecStartの行で実行するcertbot renewのコマンドを指定するのですが、--post-hookの引数をつけることで、Webサーバの設定を再起動してSSL証明書を再読み込みするようにしています。
cat<<EOF>/etc/systemd/system/certbot.service [Unit] Description=Let's Encrypt certbot renewal [Service] Type=oneshot ExecStart=/usr/bin/certbot renew --quiet --agree-tos --post-hook "systemctl reload nginx.service" EOF
ここではcrontabではなく、certbot.timerを定義して一日一回dailyで定時実行されるようにします。
cat<<EOF>/etc/systemd/system/certbot.timer [Unit] Description=Let's Encrypt certbot renewal (daily) [Timer] OnCalendar=0/12:00:00 RandomizedDelaySec=1h Persistent=true [Install] WantedBy=timers.target EOF
このファイルの中でRandomizedDelaySec=1hを指定することで実行時間を1時間の誤差をわざと設けて、全世界で同じ時刻にLet's Encryptのサーバにアクセスして過負荷になることを防いでおきます。このテクニックはArch Linuxのcertbot設定ファイルを参考にしました。
systemctlコマンドでcertbot.timerを起動して、再起動時でも有効になるように設定します。
sudo systemctl start certbot.timer sudo systemctl enable certbot.timer
ファイルの内容を書き換えた場合は、systemctl daemon-reloadコマンドを実行して設定ファイルを再読み込みします。 そして、systemctl list-timersコマンドを実行して、次回起動される.serviceコマンドの一覧を出力します。
sudo systemctl daemon-reload sudo systemctl list-timers
実行結果の中にcertbot.timer certbot.serviceの行があればokです。
$ sudo systemctl list-timers NEXT LEFT LAST PASSED UNIT ACTIVATES Sat 2022-05-07 08:09:58 UTC 32min left Sat 2022-05-07 07:06:44 UTC 30min ago dnf-makecache.timer dnf-makecache.service Sat 2022-05-07 11:13:44 UTC 3h 36min left Fri 2022-05-06 11:13:44 UTC 20h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service Sat 2022-05-07 12:19:07 UTC 4h 41min left Sat 2022-05-07 07:37:05 UTC 11s ago certbot.timer certbot.service Sun 2022-05-08 00:00:00 UTC 16h left Sat 2022-05-07 00:00:44 UTC 7h ago unbound-anchor.timer unbound-anchor.service 4 timers listed. Pass --all to see loaded but inactive timers, too.
もしも、certbotが勝手に自動起動されると困る場合は、以下のコマンドを実行して、certbot.timerを停止して無効にします。
sudo systemctl stop certbot.timer sudo systemctl disable certbot.timer
確認のためにsystemctl list-timers --allを実行して、certbot.timerの行が n/a (not available) になっていることを確認します。
$ sudo systemctl list-timers --all NEXT LEFT LAST PASSED UNIT ACTIVATES Sat 2022-05-07 08:09:58 UTC 29min left Sat 2022-05-07 07:06:44 UTC 33min ago dnf-makecache.timer dnf-makecache.service Sat 2022-05-07 11:13:44 UTC 3h 33min left Fri 2022-05-06 11:13:44 UTC 20h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service Sun 2022-05-08 00:00:00 UTC 16h left Sat 2022-05-07 00:00:44 UTC 7h ago unbound-anchor.timer unbound-anchor.service n/a n/a Sat 2022-05-07 07:37:05 UTC 3min 8s ago certbot.timer certbot.service 4 timers listed.
6. SELinuxを無効化せずにnginxを動かす
まだnginxをインストールしていない場合はOSパッケージからインストールします。
sudo dnf -y install nginx
nginxのサービスを起動して、再起動時にも自動起動を有効にします。
$ sudo systemctl start nginx $ sudo systemctl enable nginx Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.
certbot certonly --nginxで証明書を取得します。(certonlyオプションを省略するとnginxの設定ファイルを書き換えてくれます)
$ sudo certbot certonly --nginx Saving debug log to /var/log/letsencrypt/letsencrypt.log Enter email address (used for urgent renewal and security notices) (Enter 'c' to cancel): 自分のメールアドレスを入力する - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must agree in order to register with the ACME server. Do you agree? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (Y)es/(N)o: 規約を読んで同意するならyを入力する - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Would you be willing, once your first certificate is successfully issued, to share your email address with the Electronic Frontier Foundation, a founding partner of the Let's Encrypt project and the non-profit organization that develops Certbot? We'd like to send you email about our work encrypting the web, EFF news, campaigns, and ways to support digital freedom. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (Y)es/(N)o: 特にメールで案内が欲しくなければnを入力する Account registered. Please enter the domain name(s) you would like on your certificate (comma and/or space separated) (Enter 'c' to cancel): 自分のサーバのFQDNを入力する(例:ssl.example.com) Requesting a certificate for ssl.example.com Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/ssl.example.com/fullchain.pem Key is saved at: /etc/letsencrypt/live/ssl.example.com/privkey.pem This certificate expires on 2022-08-07. These files will be updated when the certificate renews. NEXT STEPS: - The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - If you like Certbot, please consider supporting our work by: * Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate * Donating to EFF: https://eff.org/donate-le - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
もしも以下のようなエラーが出る場合は途中のacme-challengeに失敗してしまっています。nginxがうまく動いていないようです。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Processing /etc/letsencrypt/renewal/ssl.example.com.conf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Simulating renewal of an existing certificate for ssl.example.com Certbot failed to authenticate some domains (authenticator: nginx). The Certificate Authority reported these problems: Domain: ssl.example.com Type: unauthorized Detail: XXXX:XXXX:100:XXX:XX:cafe:4e:1: Invalid response from https://ssl.example.com/.well-known/acme-challenge/hlbtXXXXeDKRQI9f0lNFBSLY6v4PyRfwoFY8GwOHAKo: 404 Hint: The Certificate Authority failed to download the challenge files from the temporary standalone webserver started by Certbot on port 80. Ensure that the listed domains point to this machine and that it can accept inbound connections from the internet. Failed to renew certificate ssl.example.com with error: Some challenges have failed.
ファイルの権限設定が正しいのにnginxでエラーが発生してしまう場合(webのrootディレクトリも読み取れない場合)は、getenforceコマンドを実行してSELinuxの設定の有無を確認してみましょう。
$ getenforce Disabled
もしもDisabledだったら、SELinuxは無効になっているので、ファイルの権限設定を見直して、Webのルートディレクトリがnginxの動作権限で読み込みができるかどうか、上位のディレクトリも含めて再確認してください。
$ getenforce Enforcing
もしもEnforcingだったら、SELinuxが有効になっていて監査にひっかかっているので、適当な作業ディレクトリでSELinuxのポリシーでどこにひっかかっているかを確認します。
$ sudo grep nginx /var/log/audit/audit.log | audit2allow -m nginx module nginx 1.0; require { type var_t; type httpd_t; class file { getattr read }; } #============= httpd_t ============== allow httpd_t var_t:file { getattr read };
httpd_tコンテキスト(Apacheやnginxの動作権限)に対して、var_tファイルの読み取り権限がないようなので、許可するポリシーファイルを作成します。
$ sudo grep nginx /var/log/audit/audit.log | audit2allow -M nginx ******************** IMPORTANT *********************** To make this policy package active, execute: sudo semodule -i nginx.pp
少し時間がかかりますが、作成したポリシーファイルnginx.ppを適用します。
$ sudo semodule -i nginx.pp
もう一つ別の方法として、ポリシーを変更せずに、/var/www/html以下のファイルに対してrestoreconコマンドを実行してhttpd_sys_content_tのラベルを追加してあげるという方法もあります。ファイルのラベルを確認するにはls -Zオプションで、ファイルのSELinuxコンテキストを変更するには、chcon、semanage fcontext、restoreconコマンドが使えます。
$ cd /var/www/html $ ls -Z unconfined_u:object_r:var_t:s0 index.html $ sudo restorecon *.html $ ls -Z unconfined_u:object_r:httpd_sys_content_t:s0 index.html
これでもうまくいかない場合は、セキュリティは弱くなりますが、httpd_tコンテキストに対してpermissiveにして全許可します。
$ sudo semanage permissive -a httpd_t
それでもうまくいかない場合は、SELinuxをオフにする、というのが長らくの定説ですが、ちょっと悲しいですね。
$ setenforce 0 $ getenforce Permissive
一時的にSELinuxを無効化して、SELinuxが原因かどうかを確認して、サーバ内外のファイアウォールでTCP/80,TCP/443が外から通信できないとか、どうやらSELinuxは冤罪で別のことが原因でありそうであれば元通りに設定を戻しておいてあげます。
$ setenforce 1 $ getenforce Enforcing
以下のURLにSELinux上でnginxを動かす方法が載っていますので、SELinuxのポリシー設定は一度慣れておくと良いでしょう。
7. nginxのVirtualHost設定ファイル例
ちなみに、私が使っているnginxのVirtualHost設定ファイル例を以下に掲載します。IPv6 onlyの設定になっています。 シェル変数DOMAINに設定したいVirtualHostのFQDNを指定してVirtualHost毎に.confファイルを作成しています。
DOMAIN="ssl.example.com" cat<<EOF| sudo tee /etc/nginx/conf.d/$DOMAIN.conf map \$http_upgrade \$connection_upgrade { default upgrade; '' close; } server { ## listen 80; # IPv4 listen [::]:80; # IPv6 server_name $DOMAIN; return 301 https://\$host\$request_uri; } server { ## listen 443 ssl http2; # IPv4 listen [::]:443 ssl http2; # IPv6 server_name $DOMAIN; access_log /var/log/nginx/$DOMAIN.access.log; location / { root /var/www/$DOMAIN; index index.html; } ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } EOF
複数の.confファイルを作成することによって、SSL証明書はFQDN個別に生成して1台のnginxサーバで複数のVirtualHostを運用できます。
nginxの設定ファイルの文法チェックを-tコマンドで実施した後、systemctlで設定ファイルを再読み込みします。
sudo nginx -t sudo systemctl reload nginx
エラーが出なければokです。それでは、良いLet's Encryptライフをお過ごしください!
参考文献
- User Guide — Certbot 1.27.0 documentation
- Certbot - ArchWiki
- Get Certbot — Certbot 1.27.0 documentation
- Installing snap on CentOS | Snapcraft documentation
- あらためてEPELリポジトリの使い方をまとめてみた - Qiita
- Certbot を python3 で CentOS7 へインストール - AR ホームベーカリー
- nginxでホームディレクトリを公開ディレクトリに設定するふ - Qiita
- .devドメインと.appドメインがHTTPSを強制する仕組み - Qiita
- Let’s EncryptによるSSLサーバー証明書の取得、自動更新設定(2021年3月版) | 稲葉サーバーデザイン
- certbot/configurator.py at 2017669544b1d296f10339321f5f66b6b5f158bf · certbot/certbot · GitHub
- Modifying SELinux Settings for Full NGINX and NGINX Plus Functionality
- 【Ubuntu】Let’s Encrypt+nginxでSSL/TLS(https接続)を設定する方法 | VPS Life
- certbot(Letsecnrypt)コマンド・オプションを真面目に理解する - Qiita
IPv6 only 環境で Docker / Alpine Linux を動かす
2022年5月、IPv4アドレスが枯渇してきていることもあって、IPv6 onlyな環境がVPSの一般サービスとして出てきました。 OSレベルではIPv4/IPv6のデュアルスタックで両方のネットワーク環境にサーバもクライアントも対応しつつあるのですが、 世の中にはまだAAAAレコードを持たないHTTP/HTTPSサーバも結構あって、DockerをIPv6 only環境で動かすのに一苦労しました。
今回、私自身が Rocky Linux 8.5 の IPv6 only 環境で Docker / Alpine Linux を動かした記録をメモで残しておきます。
Dockerのインストール
まず最初にdnfコマンドを使ってDockerをインストールします。
sudo dnf update -y sudo dnf install -y dnf-utils device-mapper-persistent-data lvm2 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf makecache sudo dnf install -y docker-ce sudo systemctl start docker sudo systemctl enable docker
(最近のCentOS系Linuxではyumは推奨されていないので、dnfコマンドを使用しました。)
Dockerを動かすユーザの権限設定
root以外でdockerを動かす場合、自分自身のユーザ($USER)をdockerグループに所属させておきます。
sudo groupadd docker # groupadd: group 'docker' already exists sudo usermod -aG docker $USER
IPv6対応のdaemon.jsonを作成
dockerデーモンの起動オプション/etc/docker/daemon.jsonにIPv6対応の設定を追加します。
sudo vi /etc/docker/daemon.json
作成するファイルの中身は以下の通りです。
{ "ipv6": true, "fixed-cidr-v6": "fd11:2233:4455:6677::/64", "registry-mirrors": ["https://mirror.gcr.io"] }
Dockerのデフォルトのレジストリregistry-1.docker.io
がIPv4のアドレスしか返さないのでIPv6 only環境だと通信できません。この場合、IPv6に対応しているGoogleのレジストリミラー https://mirror.gcr.io
を設定しておきます。
fixed-cidr-v6で設定しているfd11:2233:4455:6677::/64
は、自分で設定するユニークローカルアドレス (ULA: unique local address) で、IPv6のプライベートアドレスに相当するものです。自分で使う場合は、fdから始まるアドレスで任意のアドレスを指定してください。
dockerの再起動
これらの設定を有効にするにはdockerを再起動します。
sudo systemctl restart docker
もしもエラーが出た場合はdaemon.jsonの設定ファイルを見直して修正します。
ip6tablesでNATを追加
daemon.jsonのfixed-cidr-v6で設定した内部ネットワークfd11:2233:4455:6677::/64
から外に通信ができるようにNATを設定します。
sudo ip6tables -t nat -A POSTROUTING -s fd11:2233:4455:6677::/64 ! -o docker0 -j MASQUERADE
引数に-t natを指定してNATテーブルが正しく追加されているかどうか確認します。
sudo ip6tables -t nat -L
以下の出力にMASQUERADE all fd11:2233:4455:6677::/64 anywhere
の行があれば大丈夫です。
Chain PREROUTING (policy ACCEPT) target prot opt source destination Chain INPUT (policy ACCEPT) target prot opt source destination Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all fd11:2233:4455:6677::/64 anywhere Chain OUTPUT (policy ACCEPT) target prot opt source destination
再起動時のrc.localの設定
再起動してもNATの設定が有効となるように、rc.localの設定をします。
sudo vi /etc/rc.d/rc.local
rc.localは過去の互換性のために残されているファイルですが、最後に以下の一行を追加します。
#!/bin/bash # THIS FILE IS ADDED FOR COMPATIBILITY PURPOSES # # It is highly advisable to create own systemd services or udev rules # to run scripts during boot instead of using this file. # # In contrast to previous versions due to parallel execution during boot # this script will NOT be run after all other services. # # Please note that you must run 'chmod +x /etc/rc.d/rc.local' to ensure # that this script will be executed during boot. touch /var/lock/subsys/local ip6tables -t nat -A POSTROUTING -s fd11:2233:4455:6677::/64 ! -o docker0 -j MASQUERADE
rc.localの設定が初めてであれば、以下のコマンドを実行して再起動時に実行されるようにします。
sudo chmod +x /etc/rc.d/rc.local sudo systemctl start rc-local
docker infoで現在の設定を確認
docker infoを実行して現在の設定を確認します。
docker info
こんな感じの出力がされるはずです。
Client: Context: default Debug Mode: false Plugins: app: Docker App (Docker Inc., v0.9.1-beta3) buildx: Docker Buildx (Docker Inc., v0.8.1-docker) scan: Docker Scan (Docker Inc., v0.17.0) Server: ~中略~ Docker Root Dir: /var/lib/docker Debug Mode: false Registry: https://index.docker.io/v1/ Labels: Experimental: false Insecure Registries: 127.0.0.0/8 Registry Mirrors: https://mirror.gcr.io/ Live Restore Enabled: false
Registry Mirrors:にhttps://mirror.gcr.io/があればokです。
Alpine Linux の Dockerfile を作成
適当なディレクトリでDockerfileを作成します。
vi Dockerfile
Dockerfileの中身は以下の通りです。
FROM alpine:3.15.4 RUN apk update && apk add bind-tools curl CMD ["/bin/sh"]
alpine:3.15.4のイメージをpullした後、digコマンドとcurlコマンドが使いたいのでbind-toolsとcurlのapkパッケージをインストールして、デフォルトでシェルを起動しています。
docker build
タグ名は何でも良いのですが、ipv6/alpineでdocker buildします。
docker build -t ipv6/alpine .
ビルド時に以下のような感じでIPv6のサーバからapkのパッケージがダウンロードできれば成功です。
Sending build context to Docker daemon 3.072kB Step 1/3 : FROM alpine:3.15.4 3.15.4: Pulling from library/alpine df9b9388f04a: Pull complete Digest: sha256:4edbd2beb5f78b1014028f4fbb99f3237d9561100b6881aabbf5acce2c4f9454 Status: Downloaded newer image for alpine:3.15.4 ---> 0ac33e5f5afa Step 2/3 : RUN apk update && apk add bind-tools curl ---> Running in a1ebb762c17e fetch https://dl-cdn.alpinelinux.org/alpine/v3.15/main/x86_64/APKINDEX.tar.gz fetch https://dl-cdn.alpinelinux.org/alpine/v3.15/community/x86_64/APKINDEX.tar.gz v3.15.4-78-ge239b26b75 [https://dl-cdn.alpinelinux.org/alpine/v3.15/main] v3.15.4-79-gaf675e239c [https://dl-cdn.alpinelinux.org/alpine/v3.15/community] OK: 15855 distinct packages available (1/18) Installing fstrm (0.6.1-r0) (2/18) Installing krb5-conf (1.0-r2) (3/18) Installing libcom_err (1.46.4-r0) (4/18) Installing keyutils-libs (1.6.3-r0) (5/18) Installing libverto (0.3.2-r0) (6/18) Installing krb5-libs (1.19.3-r0) (7/18) Installing json-c (0.15-r1) (8/18) Installing protobuf-c (1.4.0-r0) (9/18) Installing libuv (1.42.0-r0) (10/18) Installing xz-libs (5.2.5-r1) (11/18) Installing libxml2 (2.9.13-r0) (12/18) Installing bind-libs (9.16.27-r0) (13/18) Installing bind-tools (9.16.27-r0) (14/18) Installing ca-certificates (20211220-r0) (15/18) Installing brotli-libs (1.0.9-r5) (16/18) Installing nghttp2-libs (1.46.0-r0) (17/18) Installing libcurl (7.80.0-r1) (18/18) Installing curl (7.80.0-r1) Executing busybox-1.34.1-r5.trigger Executing ca-certificates-20211220-r0.trigger OK: 15 MiB in 32 packages Removing intermediate container a1ebb762c17e ---> 18d7e9a3b51b Step 3/3 : CMD ["/bin/sh"] ---> Running in ccda19d240b3 Removing intermediate container ccda19d240b3 ---> ff8bbe4b67c8 Successfully built ff8bbe4b67c8 Successfully tagged ipv6/alpine:latest
エラーが出なければ成功です。おめでとうございます。
※ registry-1.docker.io でエラーが出る場合
もしもdocker build時に以下のエラーが出る場合は、registry-1.docker.io がIPv6のAAAAレコードを持っていないため、IPv4アドレスで通信しようとして失敗しています。
Sending build context to Docker daemon 4.096kB Step 1/3 : FROM alpine:3.15.4 Get "https://registry-1.docker.io/v2/": dial tcp 34.203.135.183:443: connect: network is unreachable
この場合は、前述のdaemon.jsonで"registry-mirrors": ["https://mirror.gcr.io"]を指定して、IPv6で通信が可能なミラーサーバを指定する必要があります。 一度、失敗するとdocker内で変にダウンロードURLがキャッシュされてしまうようで、設定を変更したら、FROM alpine:3.15.4をFROM alpine:3.15.2のように別のバージョンをダウンロードして試してみてください。
※ dl-cdn.alpinelinux.org でエラーが出る場合
docker build時に以下のエラーが出る場合は、docker内からIPv6 only環境での外への通信ができないで失敗しています。
fetch https://dl-cdn.alpinelinux.org/alpine/v3.15/main/x86_64/APKINDEX.tar.gz ERROR: https://dl-cdn.alpinelinux.org/alpine/v3.15/main: temporary error (try again later) WARNING: Ignoring https://dl-cdn.alpinelinux.org/alpine/v3.15/main: No such file or directory fetch https://dl-cdn.alpinelinux.org/alpine/v3.15/community/x86_64/APKINDEX.tar.gz ERROR: https://dl-cdn.alpinelinux.org/alpine/v3.15/community: temporary error (try again later) WARNING: Ignoring https://dl-cdn.alpinelinux.org/alpine/v3.15/community: No such file or directory
前述のdaemon.jsonで設定した"fixed-cidr-v6": "fd11:2233:4455:6677::/64"で指定した内部ネットワークから外に通信ができないといけないため、ip6tablesのNATの設定が有効になっているかどうかを確認する必要があります。
また、2022年5月3日現在、dl-cdn.alpinelinux.orgがデュアルスタックになってIPv6のAAAAレコードを持っているため、IPv6 only環境でも通信ができるはずです。
$ dig dl-cdn.alpinelinux.org. A ; <<>> DiG 9.11.26-RedHat-9.11.26-6.el8 <<>> dl-cdn.alpinelinux.org. A ~中略~ ;; QUESTION SECTION: ;dl-cdn.alpinelinux.org. IN A ;; ANSWER SECTION: dl-cdn.alpinelinux.org. 1420 IN CNAME dualstack.d.sni.global.fastly.net. dualstack.d.sni.global.fastly.net. 30 IN A 151.101.110.133
$ dig dl-cdn.alpinelinux.org. AAAA ; <<>> DiG 9.11.26-RedHat-9.11.26-6.el8 <<>> dl-cdn.alpinelinux.org. AAAA ~中略~ ;; QUESTION SECTION: ;dl-cdn.alpinelinux.org. IN AAAA ;; ANSWER SECTION: dl-cdn.alpinelinux.org. 1484 IN CNAME dualstack.d.sni.global.fastly.net. dualstack.d.sni.global.fastly.net. 30 IN AAAA 2a04:4e42:1a::645
もしも、AAAAレコードを返さないサーバだった場合、IPv4アドレスで通信しようとして失敗してしまいますので、この場合は、Dockerfile で RUN sed -r "s;//dl-cdn.alpinelinux.org/alpine;//ftp.udx.icscoe.jp/Linux/alpine;g" -i /etc/apk/repositories
を実行するなどして、IPv6で通信が可能なミラーサーバを指定してください。ちなみにAlpine Linuxのオフィシャルのミラーサーバの一覧は https://mirrors.alpinelinux.org/ で確認できます。
docker run
ビルド時に指定したタグ名ipv6/alpineでdocker runします。-iオプションでインタラクティブに対話しながらシェルを実行できます。
docker container run -it ipv6/alpine
無事にシェルに入れたら、curlコマンドでIPv6のサーバとHTTP通信できるかどうか確認します。
/ # cd /tmp /tmp # curl http://google.com/ <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>301 Moved</TITLE></HEAD><BODY> <H1>301 Moved</H1> The document has moved <A HREF="http://www.google.com/">here</A>. </BODY></HTML> /tmp #
301のレスポンスが表示されればIPv6 onlyで疎通できたので成功です。おめでとうございます。
※ busyboxのwgetはIPv6対応が不完全
ちなみに、busyboxのwgetはIPv6対応が完全ではないため、apk add wgetしてwgetコマンドを置き換えておくか、curlコマンドを使うようにしておくと良いでしょう。
IPv6 only環境でインターネットがもっと使えるようになると嬉しいですね。
参考文献
回避策:日本語ユーザー名で vagrant up すると incompatible character encodings: Windows-31J and UTF-8 (Encoding::CompatibilityError)
Windowsの日本語ユーザー名でログインした状態で vagrant up を実行すると incompatible character encodings: Windows-31J and UTF-8 (Encoding::CompatibilityError)
で止まってしまう件について、vagrantのスクリプトの中身を見て、いろいろ試した結果、回避策が見つかりました。Googleで検索すると同じような問題で結構困っている人が多そうなので、記事にして公開します。
前提条件
Vagrantfile の中身
- C:\Users\日本語利用者\vagrant\Vagrantfile
# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure("2") do |config| config.vm.box = "generic/alpine315" end
vagrant up のエラー内容
Windowsの日本語ユーザー名でログインした状態で vagrant up を実行すると incompatible character encodings: Windows-31J and UTF-8 (Encoding::CompatibilityError)
で止まってしまう。
C:\Users\日本語利用者\vagrant>vagrant up Bringing machine 'default' up with 'virtualbox' provider... ==> default: Box 'generic/alpine315' could not be found. Attempting to find and install... default: Box Provider: virtualbox default: Box Version: >= 0 ==> default: Loading metadata for box 'generic/alpine315' default: URL: https://vagrantcloud.com/generic/alpine315 ==> default: Adding box 'generic/alpine315' (v3.6.12) for provider: virtualbox default: Downloading: https://vagrantcloud.com/generic/boxes/alpine315/versions/3.6.12/providers/virtualbox.box default: default: Calculating and comparing box checksum... ==> default: Successfully added box 'generic/alpine315' (v3.6.12) for 'virtualbox'! ==> default: Importing base box 'generic/alpine315'... Traceback (most recent call last): 65: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/batch_action.rb:86:in `block (2 levels) in run' 64: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/machine.rb:201:in `action' 63: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/machine.rb:201:in `call' 62: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/environment.rb:614:in `lock' 61: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/machine.rb:215:in `block in action' 60: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/machine.rb:246:in `action_raw' 59: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/runner.rb:89:in `run' 58: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/util/busy.rb:19:in `busy' 57: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/runner.rb:89:in `block in run' 56: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/builder.rb:149:in `call' 55: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:48:in `call' 54: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/plugins/providers/virtualbox/action/check_virtualbox.rb:26:in `call' 53: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:48:in `call' 52: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/builtin/call.rb:53:in `call' 51: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/runner.rb:89:in `run' 50: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/util/busy.rb:19:in `busy' 49: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/runner.rb:89:in `block in run' 48: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/builder.rb:149:in `call' 47: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:48:in `call' 46: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:127:in `block in finalize_action' 45: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:48:in `call' 44: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/builtin/handle_box.rb:56:in `call' 43: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:48:in `call' 42: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:127:in `block in finalize_action' 41: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:48:in `call' 40: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/builtin/config_validate.rb:25:in `call' 39: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:48:in `call' 38: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/builtin/call.rb:53:in `call' 37: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/runner.rb:89:in `run' 36: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/util/busy.rb:19:in `busy' 35: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/runner.rb:89:in `block in run' 34: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/builder.rb:149:in `call' 33: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:48:in `call' 32: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:127:in `block in finalize_action' 31: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:48:in `call' 30: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/plugins/providers/virtualbox/action/check_accessible.rb:18:in `call' 29: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:48:in `call' 28: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/plugins/providers/virtualbox/action/customize.rb:40:in `call' 27: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:48:in `call' 26: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/builtin/prepare_clone.rb:15:in `call' 25: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:48:in `call' 24: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/plugins/providers/virtualbox/action/prepare_clone_snapshot.rb:17:in `call' 23: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/action/warden.rb:48:in `call' 22: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/plugins/providers/virtualbox/action/import.rb:13:in `call' 21: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/plugins/providers/virtualbox/action/import.rb:55:in `import' 20: from C:/HashiCorp/Vagrant/embedded/mingw64/lib/ruby/2.7.0/forwardable.rb:235:in `import' 19: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/plugins/providers/virtualbox/driver/version_6_0.rb:71:in `import' 18: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/plugins/providers/virtualbox/driver/base.rb:398:in `execute' 17: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/util/retryable.rb:17:in `retryable' 16: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/plugins/providers/virtualbox/driver/base.rb:403:in `block in execute' 15: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/plugins/providers/virtualbox/driver/base.rb:465:in `raw' 14: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/util/busy.rb:19:in `busy' 13: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/plugins/providers/virtualbox/driver/base.rb:466:in `block in raw' 12: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/util/subprocess.rb:22:in `execute' 11: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/util/subprocess.rb:154:in `execute' 10: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/util/safe_chdir.rb:24:in `safe_chdir' 9: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/util/safe_chdir.rb:24:in `synchronize' 8: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/util/safe_chdir.rb:25:in `block in safe_chdir' 7: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/util/safe_chdir.rb:25:in `chdir' 6: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/util/safe_chdir.rb:26:in `block (2 levels) in safe_chdir' 5: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/vagrant-2.2.19/lib/vagrant/util/subprocess.rb:155:in `block in execute' 4: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/childprocess-4.1.0/lib/childprocess/abstract_process.rb:81:in `start' 3: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/childprocess-4.1.0/lib/childprocess/windows/process.rb:70:in `launch_process' 2: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/childprocess-4.1.0/lib/childprocess/windows/process_builder.rb:27:in `start' 1: from C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/childprocess-4.1.0/lib/childprocess/windows/process_builder.rb:48:in `create_command_pointer' C:/HashiCorp/Vagrant/embedded/gems/2.2.19/gems/childprocess-4.1.0/lib/childprocess/windows/process_builder.rb:48:in `join': incompatible character encodings: Windows-31J and UTF-8 (Encoding::CompatibilityError) C:\Users\日本語利用者\vagrant>
原因
vagrantで利用されている、いくつかのrubyのスクリプトで、正しく文字エンコーディングを扱えていないため、エラーが発生してしまう。
- ファイルパスに日本語が含まれている「C:\Users\日本語利用者\vagrant\Vagrantfile」
- vagrant のデフォルトのホームディレクトリが「C:\Users\日本語利用者.vagrant.d」
- VirtualBox のデフォルトの仮想マシンフォルダーが「C:\Users\日本語利用者\VirtualBox VMs」
ややこしいのは、1.は気づきやすいけど、2.と3.は裏の動作で見えにくいので気づきにくいという罠。
回避策(1)
set RUBYOPT=-EUTF-8:Windows-31J set VAGRANT_HOME=C:/HashiCorp/.vagrant.d
実行結果(1)
C:\Users\日本語利用者\vagrant>set RUBYOPT=-EUTF-8:Windows-31J C:\Users\日本語利用者\vagrant>set VAGRANT_HOME=C:/HashiCorp/.vagrant.d C:\Users\日本語利用者\vagrant>vagrant up Bringing machine 'default' up with 'virtualbox' provider... ==> default: Box 'generic/alpine315' could not be found. Attempting to find and install... default: Box Provider: virtualbox default: Box Version: >= 0 ==> default: Loading metadata for box 'generic/alpine315' default: URL: https://vagrantcloud.com/generic/alpine315 ==> default: Adding box 'generic/alpine315' (v3.6.12) for provider: virtualbox default: Downloading: https://vagrantcloud.com/generic/boxes/alpine315/versions/3.6.12/providers/virtualbox.box default: default: Calculating and comparing box checksum... ==> default: Successfully added box 'generic/alpine315' (v3.6.12) for 'virtualbox'! ==> default: Importing base box 'generic/alpine315'... ==> default: Matching MAC address for NAT networking... ==> default: Checking if box 'generic/alpine315' version '3.6.12' is up to date... ==> default: Setting the name of the VM: vagrant_default_1650718925475_52597 ==> default: Clearing any previously set network interfaces... ==> default: Preparing network interfaces based on configuration... default: Adapter 1: nat ==> default: Forwarding ports... default: 22 (guest) => 2222 (host) (adapter 1) ==> default: Running 'pre-boot' VM customizations... ==> default: Booting VM... ==> default: Waiting for machine to boot. This may take a few minutes... default: SSH address: 127.0.0.1:2222 default: SSH username: vagrant default: SSH auth method: private key default: default: Vagrant insecure key detected. Vagrant will automatically replace default: this with a newly generated keypair for better security. default: default: Inserting generated public key within guest... default: Removing insecure key from the guest if it's present... default: Key inserted! Disconnecting and reconnecting using new SSH key... ==> default: Machine booted and ready! ==> default: Checking for guest additions in VM... C:\Users\日本語利用者\vagrant>
回避策(2)
日本語のユーザー名ではなく、英数字のユーザー名で(なければローカルアカウントを作成して)ログインし直して vagrant up を実行する。
- Windows10でローカルアカウントを作成する方法がわかりにくいという別の罠が…
- 設定→アカウント→家族とそのほかのユーザー→その他のユーザーをこのPCに追加
- このユーザーはどのようにサインインしますか?→このユーザーのサインイン情報がありません
- Microsoft アカウントを持たないユーザーを追加する(これでローカルアカウントを作成できる)
- このPCを使うのはだれですか?→ユーザー名に英数字を入力してアカウントを作成する
回避策(3)
vagrantで正しく文字エンコーディングを扱えるようにパッチを送る。これができれば終止符。
参考文献
Alpine Linuxの自前パッケージをビルドする方法
この記事はRecruit Advent Calendar 2021 - Adventarの24日目(12/24)のエントリーです。 adventar.org
1. Alpine Linuxとは?
Alpine LinuxはDockerイメージ作成でも良く使われるLinuxディストリビューションの一つで、組み込み用途で使われていたbusyboxを標準で利用し、豪華な構文を持ったbashではなくシンプルなash、機能の多いglibcではなく簡素なmusl-libcを採用していて、トータルのバイナリサイズがとても小さいという特徴があります。Alpine Linuxでは、apkコマンド(Debian系だとapt、RedHat系だとyumに相当)を利用してパッケージインストールができるのですが、この記事では自前のapkパッケージをビルドする方法について解説します。
2. ユーザの作成
Alpine Linux上でパッケージをビルドするユーザtakesakoを作成し(このユーザ名は自分の名前に変えてください)、abuildグループに所属させます。この作業はrootで行います。
adduser takesako addgroup takesako abuild
標準ではsudoパッケージがインストールされていないので、apkコマンドを利用してパッケージをインストールし、visudoコマンドで/etc/sudoersを編集してwheelグループに権限を付与します。
apk add sudo visudo addgroup takesako wheel
もしもあなたが(emacs派、nano派など)宗教上の理由でvisudoコマンドを使いたくない場合は、以下のように設定しても大丈夫です。
echo "takesako ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/takesako chmod 440 /etc/sudoers.d/takesako
ちなみに、Alpine Linuxでは設定が豊富なsudoではなく、OpenBSDで開発が進められていたdoasコマンドも利用できますので、そちらを使うのも良いでしょう。
3. alpine-sdkのインストール
apkパッケージのビルドに必要なalpine-sdkパッケージをインストールします。
apk add alpine-sdk
これは中身のないメタパッケージとなっており、依存関係のあるパッケージ群が自動でインストールされます。
alpine-sdkのAPKBUILDファイル(~/aports/main/alpine-sdk/APKBUILD
)は以下の通りです。
# Maintainer: Natanael Copa <ncopa@alpinelinux.org> pkgname=alpine-sdk pkgver=1.0 pkgrel=1 url="https://git.alpinelinux.org/" pkgdesc="Alpine Software Development Kit meta package" depends="abuild build-base git" arch="noarch" license="GPL-2.0" build() { # meta package return 0 } package() { mkdir -p "$pkgdir" }
APKBUILDのリファレンスはhttps://wiki.alpinelinux.org/wiki/APKBUILD_Referenceにありますが、
depends="abuild build-base git"
という個所で必要な他のパッケージの依存関係を指定していて、apk addしたときに依存関係のあるabuildパッケージと、build-baseパッケージと、gitパッケージが足りなければ自動でインストールされます。
4. 自分の名前とメールアドレスの設定
パッケージをビルドするユーザでログインして、git configで自分の名前とメールアドレスを設定します。
login takesako git config --global user.name "Your Full Name" git config --global user.email "your@email.address"
他に/etc/abuild.confファイルにも自分の名前とメールアドレスを記載する場所があるので、
sudo vi /etc/abuild.conf
コマンドを実行して、以下を書き換えます。
PACKAGER="Your Full Name <your@email.address>" MAINTAINER="$PACKAGER"
5. キャッシュディレクトリの権限追加
ダウンロードしたソースコードを置くキャッシュディレクトリとして/var/cache/distfiles
があるので、abuildグループ権限で書き込みができるようにしておくと便利です。
sudo mkdir -p /var/cache/distfiles sudo chgrp abuild /var/cache/distfiles sudo chmod g+w /var/cache/distfiles
6. 公開鍵と秘密鍵の生成
abuild-keygenというスクリプトが用意されているので、-a(--append)と-i(--install)オプションを指定して公開鍵と秘密鍵を生成します。
abuild-keygen -a -i
鍵の保存場所を聞かれますが、そのままエンターキーを押してデフォルトの場所のままにしておいても良いでしょう。
>>> Generating public/private rsa key pair for abuild Enter file in which to save the key [/home/takesako/.abuild/your@email.address-1a2b3c4d.rsa]: Generating RSA private key, 2048 bit long modulus (2 primes) ................+++++ .........................+++++ e is 65537 (0x010001) writing RSA key >>> Installing /home/takesako/.abuild/your@email.address-1a2b3c4d.rsa.pub to /etc/apk/keys... >>> >>> Please remember to make a safe backup of your private key: >>> /home/takesako/.abuild/your@email.address-1a2b3c4d.rsa >>>
ここでビルドしたapkファイルを他のマシン上でインストールするには、ここで生成した公開鍵の.rsa.pubファイルを/etc/apk/keys/
以下に置く必要があります。(※.pubのない.rsaファイルは秘密鍵です)
7. APKBUILDの雛形作成
APKBUILDファイルの雛形作成には、newapkbuildコマンドを利用すると便利です。
newapkbuild 3.7.0-r0 - generate a new APKBUILD Usage: newapkbuild [-n PKGNAME] [-d PKGDESC] [-l LICENSE] [-u URL] [-a | -C | -m | -p | -y | -r] [-s] [-c] [-f] [-h] PKGNAME[-PKGVER] | SRCURL Options: -n Set package name to PKGNAME (only use with SRCURL) -d Set package description to PKGDESC -l Set package license to LICENSE, use identifiers from: <https://spdx.org/licenses/> -u Set package URL -a Create autotools package (use ./configure ...) -C Create CMake package (Assume cmake/ is there) -m Create meson package (Assume meson.build is there) -p Create perl package (Assume Makefile.PL is there) -y Create python package (Assume setup.py is there) -r Crate rust package (Assume Cargo.toml is there) -s Use sourceforge source URL -c Copy a sample init.d, conf.d, and install script -f Force even if directory already exists -h Show this help
たとえば、何もオプションを指定せずに、
newapkbuild aaa
コマンドを実行すると、aaa/APKBUILD
に以下のファイルが生成されます。
# Contributor: Yoshinori Takesako <takesako@namazu.org> # Maintainer: Yoshinori Takesako <takesako@namazu.org> pkgname=aaa pkgver= pkgrel=0 pkgdesc="" url="" arch="all" license="" depends="" makedepends="" install="" subpackages="$pkgname-dev $pkgname-doc" source="" builddir="$srcdir/" build() { # Replace with proper build command(s) : } check() { # Replace with proper check command(s) : } package() { # Replace with proper package command(s) : }
これにビルドに必要な情報を埋めていけば、APKBUILDファイルの完成です。
8. 実用例:cycfx2progパッケージの作成
実用例として、cycfx2progパッケージを作成してみます。cycfx2progはEZ-USBなどのFX2系組み込みデバイスのファームウェア書き込みに利用するコマンドです。
mkdir -p test/cycfx2prog cd test/cycfx2prog vi APKBUILD
作成するAPKBUILDファイルは以下の通りです。
# Contributor: Yoshinori Takesako <takesako@namazu.org> # Maintainer: Yoshinori Takesako <takesako@namazu.org> pkgname=cycfx2prog pkgver=0.47 pkgrel=0 pkgdesc="download 8051 program into the FX2 board" url="https://www.triplespark.net/elec/periph/USB-FX2/software/" arch="all" license="GPL2" depends="libusb-compat" makedepends="libusb-compat-dev" install="" subpackages="" source="https://www.triplespark.net/elec/periph/USB-FX2/software/$pkgname-$pkgver.tar.gz Makefile.patch " builddir="$srcdir/$pkgname-$pkgver" build() { cd "$builddir" make } check() { return 0 } package() { cd "$builddir" install -D -m 755 cycfx2prog "$pkgdir"/usr/bin/cycfx2prog }
あと、Makefile.patchファイルも以下で作っておきます。
cat<<EOF>Makefile.patch --- cycfx2prog-0.47/Makefile +++ cycfx2prog-0.47-new/Makefile @@ -9,7 +9,7 @@ # NOTE: Also add sources to the "dist:" target! cycfx2prog: cycfx2prog.o cycfx2dev.o - $(CC) $(LDFLAGS) cycfx2prog.o cycfx2dev.o -o cycfx2prog + $(CC) cycfx2prog.o cycfx2dev.o -o cycfx2prog $(LDFLAGS) EOF
これは$(LDFLAGS)を後ろに書かないと、libusbのリンクに失敗する問題を修正しています。
9. ソースコードのダウンロードとchecksumの更新
abuild checksumコマンドを実行して、ソースコードのダウンロードとchecksumの更新を行います。
abuild checksum
実行すると、以下のようにダウンロード画面が表示されます。
>>> cycfx2prog: Fetching https://www.triplespark.net/elec/periph/USB-FX2/software/cycfx2prog-0.47.tar.gz % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 8768 100 8768 0 0 5299 0 0:00:01 0:00:01 --:--:-- 5301 >>> cycfx2prog: Updating the sha512sums in APKBUILD...
ダウンロード終了後、sha512ハッシュ値が計算され、APKBUILDファイルにchecksumの値が書き込まれます。
sha512sums="089895f0c4b45012f9f9fc607a30c2e2897f360d270973354fa739cc456d2728080733461f6a3681422049599947461c05e5d9e7e598fc3c9fd6d5a7d89e346c cycfx2prog-0.47.tar.gz ac62c7b1a13d144f5ceacc425a6c1487bf390273d4cf33cfe1c3ee5498d47b3c80c72ab5b8f189171d294da3bb001b42b1978d46937cf59578b35772c724d679 Makefile.patch"
10. パッケージビルドの実行
abuild -rコマンドを実行して、パッケージをビルドします。
alpine:~/cycfx2prog$ abuild -r >>> cycfx2prog: Building test/cycfx2prog 0.47-r0 (using abuild 3.7.0-r0) started Fri, 24 Dec 2021 13:52:20 +0000 >>> cycfx2prog: Checking sanity of /home/test/cycfx2prog/APKBUILD... >>> cycfx2prog: Analyzing dependencies... >>> cycfx2prog: Installing for build: build-base libusb-compat libusb-compat-dev (1/5) Installing libusb (1.0.24-r1) (2/5) Installing libusb-compat (0.1.5-r4) (3/5) Installing libusb-dev (1.0.24-r1) (4/5) Installing libusb-compat-dev (0.1.5-r4) (5/5) Installing .makedepends-cycfx2prog (20211224.135220) Executing busybox-1.32.1-r6.trigger OK: 276 MiB in 103 packages >>> cycfx2prog: Cleaning up srcdir >>> cycfx2prog: Cleaning up pkgdir >>> cycfx2prog: Fetching https://www.triplespark.net/elec/periph/USB-FX2/software/cycfx2prog-0.47.tar.gz >>> cycfx2prog: Fetching https://www.triplespark.net/elec/periph/USB-FX2/software/cycfx2prog-0.47.tar.gz >>> cycfx2prog: Checking sha512sums... cycfx2prog-0.47.tar.gz: OK Makefile.patch: OK >>> cycfx2prog: Unpacking /var/cache/distfiles/cycfx2prog-0.47.tar.gz... >>> cycfx2prog: Makefile.patch patching file Makefile Hunk #1 succeeded at 9 with fuzz 2. gcc -pipe -c -O2 -fno-rtti -fno-exceptions -DCYCFX2PROG_VERSION=\"0.47\" -W -Wall -Wformat cycfx2prog.cc gcc -pipe -c -O2 -fno-rtti -fno-exceptions -DCYCFX2PROG_VERSION=\"0.47\" -W -Wall -Wformat cycfx2dev.cc In file included from cycfx2dev.cc:18: cycfx2dev.cc: In member function 'int CypressFX2Device::_ProgramIHexLine(const char*, const char*, int)': cycfx2dev.cc:393:16: warning: comparison of unsigned expression in '>= 0' is always true [-Wtype-limits] 393 | assert(nbytes>=0 && nbytes<256); | ~~~~~~^~~ gcc -pipe cycfx2prog.o cycfx2dev.o -o cycfx2prog -lusb >>> cycfx2prog: Entering fakeroot... >>> cycfx2prog*: Running postcheck for cycfx2prog >>> cycfx2prog*: Preparing package cycfx2prog... >>> cycfx2prog*: Stripping binaries fatal: not a git repository (or any of the parent directories): .git fatal: not a git repository (or any of the parent directories): .git >>> cycfx2prog*: Scanning shared objects >>> cycfx2prog*: Tracing dependencies... libusb-compat so:libc.musl-x86.so.1 so:libusb-0.1.so.4 >>> cycfx2prog*: Package size: 48.0 KB >>> cycfx2prog*: Compressing data... >>> cycfx2prog*: Create checksum... >>> cycfx2prog*: Create cycfx2prog-0.47-r0.apk >>> cycfx2prog: Build complete at Fri, 24 Dec 2021 13:52:21 +0000 elapsed time 0h 0m 1s >>> cycfx2prog: Cleaning up srcdir >>> cycfx2prog: Cleaning up pkgdir >>> cycfx2prog: Uninstalling dependencies... (1/5) Purging .makedepends-cycfx2prog (20211224.135220) (2/5) Purging libusb-compat-dev (0.1.5-r4) (3/5) Purging libusb-compat (0.1.5-r4) (4/5) Purging libusb-dev (1.0.24-r1) (5/5) Purging libusb (1.0.24-r1) Executing busybox-1.32.1-r6.trigger OK: 275 MiB in 98 packages >>> cycfx2prog: Updating the test/x86 repository index... >>> cycfx2prog: Signing the index...
エラーがなければ、~/packages/test/x86/
ディレクトリに以下のファイルが書き込まれます。
ls -l ~/packages/test/x86/ total 20 -rw-r--r-- 1 test test 754 Dec 24 14:09 APKINDEX.tar.gz -rw-r--r-- 1 test test 13795 Dec 24 14:09 cycfx2prog-0.47-r0.apk
ここでcycfx2prog-0.47-r0.apk
ファイルができていれば完成です。
ね、簡単でしょ。
株式会社Scalarの技術顧問に就任しました
2021年度IPA未踏IT人材発掘・育成事業公募概要の竹迫PMメッセージの中で略歴として記載のある通り、2020年6月より株式会社Scalarの技術顧問に就任しました。
株式会社Scalarの技術顧問として、セキュリティ技術やエンジニア組織に関する相談や方針・設計に関するレビューを行うパートタイムのアドバイザー契約が中心となりますが、急成長するITベンチャーでよく発生する組織課題の解決や運営の相談にも乗っています。技術発のベンチャー事業が成長するにつれて0→1、1→10、10→100のそれぞれのフェーズにおいて、組織と事業のギャップを乗り越えていく必要がありますが、将来スケールした100のフェーズの景色を明確にビジョンとして持っているScalarの共同創業者CEO深津さん・山田さんのお二方に惹かれ、技術顧問を引き受けることに決めました。
当初お話をいただいた時点ではまだ緊急事態宣言は発令されておらず、初回の打ち合わせだけ実際にオフィスに出向いて対面でミーティングする機会があったのですが、出社したのはその1度きりで後は全部Zoomでの会議になりました。新型コロナウイルス感染拡大防止のため日本の働き方が大きく変わる中、色々調整いただいて、本業の仕事はそのままで、月数回のフルリモートワークで兼業として携わっています。
なぜ?国産検索エンジン開発からデータベース技術への興味
1997年、最初の私のIT業界のファーストキャリアは大学在学中にアルバイトした広島にある日本ヒューレット・パッカードの子会社でのソフトウェア開発(ECサイト構築、社内業務システム開発、SSO基盤開発、Namazu for Win32検索システムの構築など)で、当時はHP-UX上に基幹系システムのOracleデータベースを構築する百戦錬磨の精鋭部隊が近くにいたり、Intelとhpが共同開発していたItaniumプロセッサーのコンパイラやOS上で発生する様々な競合問題をトラブルシュートをするサポート部隊がいたりと、非常に貴重な体験を積むことができました。当時のhpはミドルウェアから下の低レイヤーまでネットワーク・サーバ・OS・ストレージも自社開発のハードウェアのフルスタックな技術で課題解決できる強みを持っていました。ただ顧客の新規ソリューション提案においては自社のハードウェアを絡めた範囲に縛られていたりという制約もあって、次は自由度の高い独立系のソフトウェアベンチャーに身を置きたいという思いがあり、次はエンタープライズ向けのグループウェアの開発に挑戦していたドリームアーツという会社に就職することにしました。そこで開発していたプロダクトは顧客のニーズ毎にOracle、PostgreSQL、PowerGres Plusをバンクエンドとして選べて(古いDB2の対応コードも少し残っていました)、LinuxのAPサーバを複数台置いてmod_perlでスケールアウト出来るようなアーキテクチャとして設計開発していました。グループウェアは情報系システムではあるものの、バックエンドはミッションクリティカルなOracle RACの構成に対応できるようにしたりと、エンタープライズな運用文化にも対峙することができました。当時はRDBMSのblobの性能に課題があり、大きなファイルは裏側のNFSサーバに格納するようなシステム構造が残っており、性能要件と一貫性のバランスを保つのが非常に難しかった記憶があります。運用現場で発生する緊急度の高い課題に対して圧倒的当事者意識で対応することもあり多くの経験を積むことが出来ましたが、運用と開発のもっと前段階のソフトウェア設計時点で解決できるアーキテクチャを中長期のR&Dとして考えられないかという思いが芽生え、サイボウズ・ラボに転職しました。
そこで機会をいただいてセキュリティ&プログラミングキャンプ(一時期データベース開発者コースも検討されていたこともありました)やShibuya.pmなどのコミュニティ活動に関わるようになり、現在の株式会社Scalar共同創業者の山田浩之さん(CEO兼CTO)と知り合うきっかけがありました。2008年のIPA未踏プロジェクトで山田さんが全文検索エンジンLux IOの開発をしていたときに国産ストレージエンジン、データベースマネージャ開発コミュニティの一員として知り合っていたのですが(当時は経済産業省が国産検索エンジン開発を支援する3年間の情報大航海プロジェクトがあり、産学官の相互人材交流が盛んでした)、昨年2月頃に「最近、改ざん検知性(ビザンチン故障検知性)を持ったデータベースを研究・開発しているスタートアップを始めたんだけど」とお誘いのお話をいただいて、今では非常に珍しくなったデータベースのコアな技術者(※喜連川先生曰く絶滅危惧種)が株式会社Scalarに複数人在籍していることと、今後の事業の発展性・スケーラビリティに非常に興味があったので「ぜひ」と二つ返事で一緒に関わらせていただくことになりました。
Scalar DB 分散データベースマネージャをOSSで公開
株式会社Scalarの主力プロダクトの一つとして開発しているScalar DBはACID準拠でない分散データベースやストレージエンジンをACID準拠にするJavaライブラリで、オープンソース(Apache License 2.0)と商用ライセンスの元で公開されています。現時点でScalar DBが対応しているバックエンドの分散ストレージはCassandra、Azure Cosmos DB、Amazon DynamoDB(2021年2月時点)ですが、抽象化されたアーキテクチャとして設計されているため、対応コードを実装すれば理論的にはその他のデータベースにも対応できます。
先日のApacheCon@Home 2020でCassandra上でどのように分散トランザクションを実現しているかScalarのエンジニアによる国際発表がありました。
このレイヤーでトランザクションが担保できれば、顧客サイドでのバックエンドのデータベースの選択肢に自由が生まれるので(商用のRDBMSの他にNoSQLも選択できる)、エンタープライズの現場に導入するときのスケーラビリティと信頼性の技術課題が同時に解決できるメリットが大きく、将来性の非常に高いプロダクトだと思っています。
このアイディアを聞いた時は、20年前の商用tuxedoに対抗したOpen OLTP system MONTSUQIを思い出しました。信頼性の非常に高い大型汎用機用トランザクションモニタと最新ののモダンなクラウドの分散データベース技術が交差する温故知新なワクワクする技術です。
Scalar DL 改ざん耐性とスケーラビリティの両立を実現
株式会社Scalarが開発するもう一つの主力プロダクトScalar DLは、電子署名が付与されたスマートコントラクトを分散トランザクションの形式で実行する分散型台帳ソフトウェアです。
高い改ざん耐性とスケーラビリティの両立を実現する独自の分散データベース管理技術で、帳簿など保管義務がある書類をデジタル化して改ざんされていないことを証明する必要のあるユースケースに適しています。主なアプリケーションとして、エンタープライズ向けの契約管理・情報銀行・企業間連携システム・ERPなどへの応用があります。
Scalar DL サンプルアプリケーション
分散型台帳ソフトウェアScalar DLでいくつかのアプリケーションを実装するサンプルはQiita上に記事があります。
モダンな技術スタックで基盤の技術を構築しているので、実際に手元でコードを動かしてみると非常に面白いです。
余談
(本業ではまだ紙の書類への捺印のために毎月オフィスに出社する必要があるのですが)株式会社Scalarとの契約ではクラウド電子署名サービスを用いて業務委託契約書を締結しました。私の中で電子署名は初めての体験でしたが、こういった新しいことを積極的に会社の業務として取り入れていくバックオフィスの柔軟かつ堅実な姿勢に感銘を受けました。今後、日本のデジタルトランスフォーメーション(DX)を進めていくためには、他社の技術も含めて、従来の紙ではない新しい電子契約の在り方をドッグフーディングして自分の身の回りでできることから地道に置き換えていくことが重要だと思っています。
Scalarでは人材を募集しています
そんな株式会社Scalarではエンジニアリングチームで一緒に働くメンバーを絶賛募集中です。データベースのコアな技術に携わりたい人、SREでクラウドの最新技術に触りたい人、コアなチームをまとめて価値を最大化したいエンジニアリングマネージャーなど様々なポジションがあります。実際どんな感じなのか、質問や相談がありましたら、ぜひ気軽に竹迫のTwitterまで直接DMください。
https://angel.co/company/scalar-inc/jobs
今後Scalarではデータベース系の学会へのスポンサーや、論文発表なども積極的に取り組んでいきたいと思います。よろしくお願いいたします。
Donuts Radio #003【竹迫良範さん】podcast出演
Donuts Radio(根岸心さんが聞くエンジニアの人生の軌跡。 子ども時代の貴重なエピソードから、心に秘めている将来の展望までとことん語りつくすpodcast)に出演しました。
8人きょうだいの長男として大家族で育った竹迫さんは、中学生の頃、父親が購入したパソコンを譲り受け、独学でゲームを作成したことでプログラミングと出会いました。一方で幼少期の入院経験から医者を志したこともあったといいます。エンジニアと医者、一見するとかけ離れた職業ですが、そこには意外な共通点が…?! 大家族ならではの家庭事情や、数々の偶然が重なって広島から上京した経緯など、根岸も知らなかった話はもちろん、「若い人が挑戦できる場所」を積極的に提供したいと語る竹迫さんの、熱意あふれる対談となりました。
情熱大陸#1140 登大遊さん(サイバー技術開発集団)
2021年2月7日(日)23:00~毎日放送「情熱大陸」で日本が誇るIPA未踏OB天才プログラマー登さんがピックアップされていました。本邦初公開の貴重な映像もあり、必見です。
情熱大陸#1140 登大遊(IPA/NTT東日本/サイバー技術開発集団 統括)コロナ禍であえぐ全国の自治体を救え!天才プログラマーが挑む緊急テレワークシステム開発
2月7日(日)「情熱大陸」
— 情熱大陸 (@jounetsu) 2021年1月31日
サイバー技術開発集団 統括/登大遊
コロナ禍であえぐ全国の自治体を救え!天才プログラマーが挑む緊急テレワークシステム開発に迫る。#情熱大陸 #mbs #tbs #テレワーク pic.twitter.com/Kv2UVJNyzu
放送を見逃した方は1週間ほどTVerで視聴できるので、ぜひご覧ください。
※ 2月14日(日) 22:59 終了予定
tver.jp
放送中のTwitterのまとめ togetter.com
主役は登さんはじめシンテレワークシステムの構築に貢献されたNTT東日本・IPA関係者・J-LISの皆さんですが、サイバー技術研究室のモブ役として少しだけ竹迫も映っていました。