タグ付に困る製品名。何かに使うようなので調べてます。
tl;dr
- Nexus Repository Manager 3が提供するDocker Registry機能のhosted,proxy,groupの動作を試した
- 現時点(2019/07/08)でregistry-mirrorsとしての利用が出来ていない
中身が薄いので画像入れてごまかしてます。
特徴
Nexus Repository Manager 3は、パッケージの提供場所となるリポジトリを扱うソフトウェア。キャッシュとして使ったり、プライベートに使ったり。
プラグインで扱えるリポジトリを増やすことも出来る。一覧はDocumentで。アンオフィシャルだけどCPANもある(Perl好き)
今回の目標
こんな感じの構成を組む。表現力の乏しさが垣間見えるポンチ絵がこちらです。
この構成にすることで以下が実現できる(はず)
- Hostedリポジトリ
- ローカルでイメージを管理したい
- Proxyリポジトリ
- プロキシとしてアクセスを集約したい
- Docker Hubさんへの通信負荷を減らしたい、Pullを早くしたい
- Groupリポジトリ
- 独自イメージやDocker Hubイメージを意識せずに扱いたい(※今回出来てません)
頑張りましょう。
導入
以下を読みながら進めている。
導入する環境はメモリ4GBのCentOS 7.5なLinux環境。
Java Runtime Environmentの導入
# JREの導入
sudo yum install -y java-1.8.0-openjdk.x86_64
# 確認
java -version
nexusユーザ作成
sudo useradd nexus -s /sbin/nologin
Nexus Repository Manager 3のインストール
curl -LO https://download.sonatype.com/nexus/3/latest-unix.tar.gz
tar xzvf latest-unix.tar.gz
NEXUS_DIR_NAME=$(ls | grep "^nexus-3" | tail -n 1)
sudo mv "$NEXUS_DIR_NAME" /opt
sudo ln -ns "$NEXUS_DIR_NAME" /opt/nexus-3
sudo chown -R nexus:nexus /opt/nexus-3*
sudo chmod -R go-rw /opt/nexus-3*
作業ディレクトリの確保
Nexus Repository Manager 3は相対ディレクトリ../sonatype-work
を参照しようとするので、ここにディレクトリを作っておく
sudo mkdir /opt/sonatype-work
sudo chown nexus:nexus /opt/sonatype-work
sudo chmod -R go-rw /opt/sonatype-work
サービス登録
sudo tee /etc/systemd/system/nexus.service << 'EOF'
[Unit]
Description=nexus service
After=network.target
[Service]
Type=forking
LimitNOFILE=65536
ExecStart=/opt/nexus-3/bin/nexus start
ExecStop=/opt/nexus-3/bin/nexus stop
User=nexus
Restart=on-abort
[Install]
WantedBy=multi-user.target
EOF
# systemdファイル読み込み
sudo systemctl daemon-reload
# 自動起動設定 & 起動
sudo systemctl enable nexus.service
sudo systemctl start nexus.service
# 起動確認
systemctl status nexus.service
ブラウザでhttp://localhost:8081
へアクセスできることを確認する。(リモートサーバ上ならSSHで繋いでポートフォワーディングなどする)
Nexus Repository Manager 3の設定(主にDocker Registry)
adminユーザのログイン
Nexus Repository Manager 3を起動するとadminのパスワードが以下に記録されるので確認する
sudo -u nexus cat /opt/sonatype-work/nexus3/admin.password
ブラウザの右上のSigninからアクセスして、ユーザ名にadmin、パスワードに上記のコマンドで確認したパスワードを入力する。その後、パスワードリセットを促されるので適宜行う。
匿名のアクセスを受け付けるようにするかどうかだが、今回は無効にしておく。
Nexus Repository Manager 3上にDockerのリポジトリを作成する
ここでいう「リポジトリ」はNexus Repository Manager 3の用語。
- proxy: その名の通りプロキシするリポジトリ。プロキシ先にDocker Hubを指定すれば、Docker Hubのイメージを取ってきて、溜め込むことが出来る。
- hosted: 自前イメージを置いておくリポジトリ。Pushする先。
- group: 上記を統合したリポジトリを提供できる。Hostedリポジトリにあるプライベートなイメージと。ProxyリポジトリにあるDocker Hubのイメージを一緒に扱えるようになる。
また、pushするときはhosted、pull等をするときはgroupを使う。groupにpushしてもエラーになるので注意。。
というわけで、使うときはhostedとgroupのために2ポート開ける必要がある。
StorageはDefault(ローカルディスク)があるので、今回はそれを使う。
HostedなDockerリポジトリを作成
Repository →Repositoriesから作成する。
- docker (hosted)を選ぶ
- HTTPSを有効にして5001を指定する
- Enable Docker V1 APIを有効にする(手元のdockerクライアントがV1で投げるようなので。)
ProxyなDockerリポジトリを作成
- docker (Proxy)を選ぶ
- Enable Docker V1 APIを有効にする
- Remote Storageに
https://registry-1.docker.io
を指定 - Docker Indexに
Use Docker Hub
を指定
GroupなDockerリポジトリを作成
- docker (Group)を選ぶ
- HTTPSを有効にして5000を指定する
- Enable Docker V1 APIを有効にする
- Member repositoriesへ先ほど作成したリポジトリをMembersに追加する
自己署名証明書によるSSL対応
今回はローカルのみの動作で良いものとして自己署名証明書を使う。以下の記事を参考に構築してた
- https://help.sonatype.com/repomanager3/formats/docker-registry/ssl-and-repository-connector-configuration
- https://help.sonatype.com/repomanager3/security/configuring-ssl
- https://support.sonatype.com/hc/en-us/articles/217542177
keystoreの場所は/opt/nexus-3/etc/jetty/jetty-https.xml
に記述されている位置を参照する。デフォルトで<install-dir>/etc/ssl/keystore.jks
を参照する。しかし、アップデート時に再設定が面倒になりそうなので別の場所に配置する。
keystoreのパスワードはすべてpassword
にしている。もし変更する場合は、/opt/nexus-3/etc/jetty/jetty-https.xml
の修正も必要なので注意。
# keystoreを作成する先のpath
KEYSTORE_PATH=/opt/sonatype-work/nexus3/etc/ssl/keystore.jks
# ドメイン
NEXUS_DOMAIN=$(hostname)
# keystoreを保存するディレクトリを作成
sudo -u nexus mkdir -p "$(dirname $KEYSTORE_PATH)"
# keystoreを作成
sudo -u nexus keytool -genkeypair -keystore "$KEYSTORE_PATH" -storepass password -keypass password -alias jetty -keyalg RSA -keysize 2048 -validity 3650 -deststoretype pkcs12 -dname "CN=*.${NEXUS_DOMAIN}, OU=Example, O=Sonatype, L=Unspecified, ST=Unspecified, C=US" -ext "SAN=DNS:${NEXUS_DOMAIN},DNS:localhost" -ext "BC=ca:true"
sudo chmod 0600 "$KEYSTORE_PATH"
# keystoreを確認
sudo -u nexus keytool -v -list -keystore "$KEYSTORE_PATH" -storepass password
設定でkeystoreの配置を変え、HTTPS接続を有効化する。
sudo -u nexus tee -a /opt/sonatype-work/nexus3/etc/nexus.properties << 'EOF'
# keystore等のパスを変更
ssl.etc = /opt/sonatype-work/nexus3/etc/ssl
# `${jetty.etc}/jetty-https.xml` を追加し、HTTPSをサポートさせる
nexus-args=${jetty.etc}/jetty.xml,${jetty.etc}/jetty-http.xml,${jetty.etc}/jetty-https.xml,${jetty.etc}/jetty-requestlog.xml
# (おまけ)HTTPSで管理画面にアクセスできるようにする
application-port-ssl = 8443
EOF
再起動
sudo systemctl restart nexus
再起動してからListenするまで時間がかかるのでしばらく待つ。
最後にdockerの信頼できる証明書として登録・更新
keytool -printcert -sslserver ${NEXUS_DOMAIN}:5000 -rfc | sudo tee /etc/pki/ca-trust/source/anchors/${NEXUS_DOMAIN}.crt
keytool -printcert -sslserver localhost:5000 -rfc | sudo tee /etc/pki/ca-trust/source/anchors/localhost.crt
sudo update-ca-trust
registry-mirrorの設定(※2019/07/08時点では機能しない)
Dockerデーモンの設定を変更し、イメージをプライベートレジストリから返すようにする。
しかし、Docker側のバグがあり期待通りに機能しない。理由は後ろの方に書いた
sudo tee /etc/docker/daemon.json << EOF
{
"registry-mirrors": ["https://${NEXUS_DOMAIN}:5000"]
}
EOF
# デーモン再起動して設定読み直し
sudo systemctl restart docker
動作確認
- hostedの動作をpushできることで確認する
- groupとproxyの動作をpull、run、searchを用いて行う
プライベートレジストリを経由してイメージをpullしてrun。
mkdir sample
cd sample
echo 'console.log("Hello World")' > app.js
# プライベートレジストリへログイン(とりあえずadminで繋がる、Userを作りRoleを与えればそのユーザで繋がる)
sudo docker login localhost:5000
sudo docker run -v $(pwd):/tmp localhost:5000/node:10 node /tmp/app.js
次にDockerfileからイメージを作成し、それをプライベートレジストリにPush。
cat << 'EOF' > Dockerfile
from localhost:5000/node:10
COPY app.js /tmp/app.js
EOF
sudo docker build -t fukasawah/fukasawah-node-hello:1 .
# イメージを試す(イメージにapp.jsが含まれているのでマウント無しで使える)
sudo docker run fukasawah/fukasawah-node-hello:1 node /tmp/app.js
イメージが問題ないようなのでHostedリポジトリ(ポート番号:5001)へPushする。
# タグをPush用に複製する
sudo docker tag fukasawah/fukasawah-node-hello:1 localhost:5001/fukasawah-node-hello:1
# プライベートレジストリへログイン(とりあえずadminで繋がる)
sudo docker login localhost:5001
# イメージをPushする
sudo docker push localhost:5001/fukasawah-node-hello:1
PushしたイメージをGroupリポジトリ(ポート番号:5000)経由で取得して実行(Hostedではないことに注意)
sudo docker run -v $(pwd):/tmp localhost:5000/fukasawah-node-hello:1 node /tmp/app.js
プライベートレジストリを使い、node
に関するイメージを検索。
# docker-hub上にあるイメージをプロキシリポジトリを介して検索
sudo docker search localhost:5000/node
# pushしたイメージを検索
sudo docker search localhost:5000/fukasawah-node-hello
# Hostedリポジトリからnodeを検索
sudo docker search localhost:5001/node
お試し計測
一番気になってたregistry-mirrorとして使ったときのPullにかかる時間の改善具合を見たかった。
しかし、registry-mirrorが機能しなかったし、明示的に指定してPullしてもあまり効果なかった。
そして環境が貧弱で真の結果ではない気がするので伏せておく。興味があれば開いてお読みください。
- Azure VM DS1v2を2台用意
- docker registry
- docker daemon + docker client (操作側)
- ディスクは Standard HDD (これが良くない)
- 対象は「
node:10
(プライベートレジストリ未使用)」「$NEXUS_DOMAIN:5000/node:10
(プライベートレジストリ利用)」- registry-mirrorは諸事情で機能しなかったので。
前準備
NEXUS_DOMAIN=foo.example
# 自己署名証明書を信頼する
openssl s_client -showcerts -connect ${NEXUS_DOMAIN}:5000 < /dev/null | awk "/BEGIN CERTIFICATE/,/END CERTIFICATE/" | sudo tee /etc/pki/ca-trust/source/anchors/${NEXUS_DOMAIN}.crt
sudo update-ca-trust
# ログイン
sudo docker login "${NEXUS_DOMAIN}:5000"
# registry-mirrorを有効
sudo tee /etc/docker/daemon.json << EOF
{
"registry-mirrors": ["https://${NEXUS_DOMAIN}:5000"]
}
EOF
# registry-mirrorを無効
sudo tee /etc/docker/daemon.json << EOF
{
}
EOF
sudo systemctl restart docker
# docker infoを見ると、Registry Mirrosの項目が追加される
sudo docker info
1回測る毎に以下を実行
# 情報でRegistry Mirrorsの情報を確認する
sudo docker info | grep -A5 "Registry Mirrors"
# イメージを全て削除したりゴミを削除したり
sudo docker images -q | sudo xargs docker rmi -f
sudo docker system prune
# 実行
time sudo docker pull node:10
# or
time sudo docker pull $NEXUS_DOMAIN:5000/node:10
Mirror有 | Mirror無 | |
---|---|---|
1回目 | 1:22.09 | 1:17.36 |
2回目 | 1:18.53 | 1:18.10 |
3回目 | 1:15.62 | 1:18.22 |
(単位は 分:秒.ミリ秒
)
うーん、誤差ですね。展開に時間がかかりすぎている。
ダウンロード自体はどちらも10秒ぐらいで終わるが、その後の展開に70秒以上かかっているという感じ(Standard HDDのせいだと思う)
感想
自己証明書のSSL対応しようとしたり、ベンチマーク取ろうとしたり、実はまだregistry-mirrorが使えなかったり、Anonymouseも試したけどようわからん、という感じで無駄に時間がかかってしまった。
意外とLinux上でごにょごにょとやってしまったのでDockerで1発で動かせるようにした方が良さそう。
ボタンポチポチでリポジトリが立ち上がるのでお手軽感はある。Mavenやnpmのリポジトリを使うとなっても、ある程度は操作が一緒なので、同じ感覚で建てられて良さそう。(実際に運用できるかは自信がない)
registry-mirrorが使えれば・・・という感じ。
遭遇したエラー
先にDockerの環境を。ちょっと古いが、CentOS 7の安定板がこれらしい。
$ sudo docker version
Client:
Version: 17.05.0-ce
API version: 1.29
Go version: go1.7.5
Git commit: 89658be
Built: Thu May 4 22:06:25 2017
OS/Arch: linux/amd64
Server:
Version: 17.05.0-ce
API version: 1.29 (minimum version 1.12)
Go version: go1.7.5
Git commit: 89658be
Built: Thu May 4 22:06:25 2017
OS/Arch: linux/amd64
Experimental: false
HostedリポジトリへPushしてエラー
以下のような感じ。
# sudo docker push localhost:5000/fukasawah-node-hello:1
The push refers to a repository [localhost:5000/fukasawah-node-hello]
ea53379b117c: Layer already exists
954f92adc866: Layer already exists
adca1e83b51a: Layer already exists
73982c948de0: Layer already exists
84d0c4b192e8: Layer already exists
a637c551a0da: Layer already exists
2c8d31157b81: Layer already exists
7b76d801397d: Layer already exists
f32868cde90b: Layer already exists
0db06dff9d9a: Layer already exists
error parsing HTTP 404 response body: invalid character '<' looking for beginning of value:
Push先を間違えているかもしれない。ちゃんとHostedなDockerのRepositoryのポートになっているかどうか確認する。これで4時間溶かした
再起動時にCould not configure HTTPS connector on port (ポート番号) for docker repository (リポジトリ名)
のログが出てHTTPS接続できない
DockerのHTTPSコネクタを有効にした後に出た。
2019-07-07 19:20:44,714+0000 WARN [FelixStartLevel] *SYSTEM org.sonatype.nexus.repository.docker.internal.DockerConnectorFacetImpl - Could not configure HTTPS connector on port 5000 for docker repository docker
org.sonatype.nexus.bootstrap.jetty.UnsupportedHttpSchemeException: Unsupported HTTP Scheme: https
at org.sonatype.nexus.internal.jetty.ConnectorRegistrarImpl.validate(ConnectorRegistrarImpl.java:128)
at org.sonatype.nexus.internal.jetty.ConnectorRegistrarImpl.addConnector(ConnectorRegistrarImpl.java:84)
at org.sonatype.nexus.repository.docker.internal.DockerConnectorFacetImpl.doStart(DockerConnectorFacetImpl.java:121)
at org.sonatype.nexus.repository.FacetSupport.start(FacetSupport.java:156)
at org.sonatype.nexus.repository.docker.internal.DockerConnectorFacetImpl$$EnhancerByGuice$$613f4292.CGLIB$start$20(<generated>)
at org.sonatype.nexus.repository.docker.internal.DockerConnectorFacetImpl$$EnhancerByGuice$$613f4292$$FastClassByGuice$$c00a5460.invoke(<generated>)
原因はjettyがhttpsの設定ファイルを読みこんでおらず、Keystoreを認識できていなかった。デフォルトでは読んでくれない。
なのでプロパティで読むようにする必要があった。以下のファイルを以下のように変更
# ${jetty.etc}/jetty-https.xml を追加している
nexus-args=${jetty.etc}/jetty.xml,${jetty.etc}/jetty-http.xml,${jetty.etc}/jetty-https.xml,${jetty.etc}/jetty-requestlog.xml
registry-mirrorsが機能しない
これに関するIssueが上がっていた。見た感じ同じ現象に見える。
registry-mirrorsは設定しておくと、docker pull node:10
とすれば、registry-mirrorsにあるレジストリから取得を試みる、というのが期待する動きなのだが認証の問題でそうならない。
以下、そのときのdockerデーモンのログ。プライベートレジストリにアクセスしたが認証失敗し、Docker Hubの方を見に行ってしまっている。
Jul 8 19:03:52 localhost dockerd: time="2019-07-08T08:03:52.920737964Z" level=debug msg="Trying to pull node from https://remote-addrss:5000/ v2"
Jul 8 19:03:52 localhost dockerd: time="2019-07-08T08:03:52.965148289Z" level=debug msg="Increasing token expiration to: 60 seconds"
Jul 8 19:03:53 localhost dockerd: time="2019-07-08T08:03:53.002164344Z" level=info msg="Attempting next endpoint for pull after error: unauthorized: access to the requested resource is not authorized"
Jul 8 19:03:53 localhost dockerd: time="2019-07-08T08:03:53.002212643Z" level=debug msg="Trying to pull node from https://registry-1.docker.io v2"
Jul 8 19:03:55 localhost dockerd: time="2019-07-08T08:03:55.126096378Z" level=debug msg="Pulling ref from V2 registry: node:10"
この時、認証情報は使っていないのではなく、docker hubにログインしたときの認証情報を使っているらしい。
実際にdocker hubに登録したID/Passwordを使って、Nexus上のユーザを作成し適切にRoleを割り当てたら、一応registory-mirrorとして機能した。しかしnexusのログレベルをDebugにするとログにパスワードが平文で流れていくので、おとなしく修正を待った方が良さそう。
後は、Issueにも書いてありますが、前段にNginx等のリバースプロキシを置いて、認証情報をNginx側で上書きして、Dockerデーモンからは認証関係なくアクセスできるようにする、という手法もあるみたい。Nginxにアクセスできる人は誰でも使えるようになってしまいますが、プライベートレジストリなのでそんなに問題はない気がする。
これがうまく扱えないと魅力が9割減だと思うので、改善されると良いなー。
不明点
- イメージの管理方法に何かあるか(ブラックリスト、ホワイトリスト方式とか)
- 古くなったイメージの更新をやってくれるのか(腐ったイメージを使うのは避けたい)
- キャッシュ用途の場合、ディスクの空き容量が心配(いい感じに古いのは削除してくれるのかどうか)
- 冗長構成 (High Availability)
- 他の機能
- Blob Storage(他のバックエンド、増設など)
- Routing Rules
- Cleanup Policies
- Content Selectors
- Task
- Anonymous認証
- LDAP連携
- IQ Server連携
- Webhooks
- Rest API