自分用の Docker Registry を作る


private registry が欲しくなってきたぞ

これまでは自分用のちょいとした Docker コンテナサーバーを作る際、サーバーに Dockerfile をコピーしてその場でイメージを作る流れで立てていました。実際自分用だとこれで済んでしまう気がします。

Docker Hub へのアップロードは通常イコールでパブリック公開になるのですが、自分向けコンテナの多くは文字通り自分向けなので汎用性の無い自作コードで動いていたり秘密鍵を内部に抱えていたりで公開しにくいなあといつも考えていた次第。
そんな感じなので自作した Docker イメージを積極的に push する場面がなかったのですね。

CoreOS とか Docker サービスとかが広がってくるとコンテナのオーケストレーションというのがテーマになってきます。
実際に fleet や Kubernetes をいじってみるとデプロイの単位がコンテナイメージで、これがどのマシンにおいても Docker hub からイメージをダウンロードするだけで動くという透過性をベースにしていることがわかります。
このへんで Docker コンテナのイメージと Docker Hub の重要性がじわじわと理解できるようになってきました。

ですが、未だ私のコンテナ作成技術が未熟で、先ほど書いた様に秘密鍵を抱えたコンテナとかを作りがちなのですよね。
そうなると、自分だけが使う private registry が自前サーバーにあると便利だなと思うわけです。
registry そのものもコンテナとして公開されていて簡単に入手できるので、用意してみましょう。

自分向けに Docker の実験をやりまくったり、registry として機能するサーバーを 1つ用意して taigei (大鯨)と名前をつけました。(これがやりたかった)
その taigei 構築時の記録です。

関連情報

docker-registry の設置方法については以下の記事を参照しました。

$shibayu36->blog; / 社内用Docker Registryを立てる
http://blog.shibayu36.org/entry/2013/12/24/194134

SOTA / 認証付きのDocker Private registryを立てる
http://deeeet.com/writing/2014/10/02/docker-private-registry-auth/

認証用のプロキシとコンテナの接続

Docker が push/pull を行うときにポートを指定せず URL だけを指定すると https(port 443) を標準で使います。Docker registry の default だと port 5000 なのでポート指定が必須になってしまいます。
また標準では認証無しなので、http の BASIC認証を前段に立ててユーザー認証を追加してあげなくてはいけません。

そういった背景から、Docker registry とセットで httpd を用意して認証&リバースプロキシとして使用します。

Docker コンテナに対してのプロキシ設定としては上図の様に Docker ホスト側に Apache なり nginx なりを用意してやるのが簡単であり、私もこれまではそのようにしていました。

ですが、CoreOS などはこういった構成にできず全てをコンテナとして管理すべきですので、プロキシ自体もコンテナにしてしまった方が可搬性に富むというものです。
今回、この構成にチャレンジしたら思ったよりも大変だったというのが今回の記事を書いた意図となります。

registry コンテナは参考記事にあるように pull して run するだけなのですが、多くの記事では --port 5000:5000 で接続しています。これでは認証付きプロキシを用意しても port 5000 がホストから外部に向けて絶賛公開中になってしまいます。
registry コンテナへの接続は外部からできないように --expose=5000 としてブリッジにのみ公開し、proxy コンテナからは --link を通じて接続します。
リンクで繋ぐと言うことは registry サーバーの IP と port を環境変数を通じて proxy に渡すということですので、そこをどうするかが設計のキモになっていきます。

リンクで接続する proxy コンテナの作成

--link myreg:registry などとして proxy コンテナを起動すると REGISTRY_* で始まる環境変数が色々とセットされます。
その環境変数から接続先などを指定しつつ proxy を起動する必要があるのですが、nginx だと conf 内に環境変数を持ち込むには perl モジュール等を使って頑張って行う感じになります。perl モジュールが使えるパッケージってどれ?とか色々面倒なところが多すぎたので今回は nginx を諦めて apache2 を proxy として利用することにしました。
apache の conf では ${環境変数名} でどこにでも使えるので簡単です。

Dockerfile はあまり変なことはしていなくて普通な感じでは無いかと思います。

Dockerfile
FROM debian:wheezy
MAINTAINER rerofumi

RUN echo 'deb http://ftp.jp.debian.org/debian/ wheezy main' > /etc/apt/sources.list

ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update; \
    apt-get -y upgrade; \
    apt-get -y dist-upgrade; \
    apt-get -y install apache2; \
    apt-get clean; \
    rm -rf /var/lib/apt/lists/*

ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_PID_FILE /var/run/apache2.pid
ENV APACHE_RUN_DIR /var/run/apache2
ENV APACHE_LOG_DIR /var/log/apache2
ENV APACHE_LOCK_DIR /var/lock/apache2

# --- web setup
RUN mkdir -p /etc/ssl/local
ADD sslproxy.conf /etc/apache2/sites-available/
ADD htpasswd /var/www/.htpasswd

RUN a2enmod ssl
RUN a2enmod proxy
RUN a2enmod proxy_http
RUN a2enmod headers
RUN a2ensite sslproxy.conf

VOLUME ["/etc/ssl/local", "/var/log/apache2"]
EXPOSE 443
CMD ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

予め書いておいてコピーする設定ファイルはプロキシサイトコンフィグの sslproxy.conf と BASIC認証用パスワードファイルの htpasswd の 2つです。
SSLの鍵ファイル置き場と、ログファイルの置き場をホスト側にできる様ボリュームを設定しています。

sslproxy.conf は docker-registry の contrib に含まれる apache 用設定サンプルをベースに書き起こしています。
https://github.com/docker/docker-registry/blob/master/contrib/apache.conf

sslproxy.conf
<IfModule mod_ssl.c>
  <VirtualHost _default_:443>
    ServerName taigei.myhost.name

    SSLEngine On
    SSLCertificateFile    /etc/ssl/local/${SITESSL}.crt
    SSLCertificateKeyFile /etc/ssl/local/${SITESSL}.key

    LogLevel warn
    CustomLog ${APACHE_LOG_DIR}/ssl_access.log combined
    ErrorLog ${APACHE_LOG_DIR}/ssl_error.log

    Header set Host "taigei.myhost.name"
    RequestHeader set X-Forwarded-proto "https"

    ProxyRequests off
    ProxyPreserveHost on
    ProxyPass / http://${REGISTRY_PORT_5000_TCP_ADDR}:${REGISTRY_PORT_5000_TCP_PORT}/
    ProxyPassReverse / http://${REGISTRY_PORT_5000_TCP_ADDR}:${REGISTRY_PORT_5000_TCP_PORT}/

    <Location "/">
      AuthType Basic
      require valid-user
      AuthName "Docker Registry local"
      AuthUserFile /var/www/.htpasswd
    </Location>

    # Allow ping and users to run unauthenticated.
    <Location /v1/_ping>
      Satisfy any
      Allow from all
    </Location>

    BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
  </VirtualHost>
</IfModule>

taigei とホスト名が固定で入っているので、適時ご自分の環境に読み替えてください。
この辺も環境変数で渡すと完全に可搬なコンテナになりそうですね。

うまくいかなくてえらい長い時間悩んだのですが、ポイントは Header/RequestHeader をヘッダーモジュールで追加しているところにあります。これがないとプロキシ先の docker-registry が自分の内部IPを応答先として返してしまうのでプロキシが失敗します。

registry の起動

実際に起動するときは registry → proxy の順に起動して、registry をリンクで指定するのですが、そのへんをコマンドラインからやるのは辛いので fig を使って管理します。
参考までに。

fig.yml
myreg:
  image: registry
  volumes:
    - /var/opt/docker/registry:/tmp/registry
  expose:
    - "5000"  

httpdproxy:
  image: rerofumi/registryproxy
  volumes:
    - /var/opt/docker/httpd_proxy/log:/var/log/apache2
    - /var/opt/docker/httpd_proxy/sslkey:/etc/ssl/local
  links:
    - myreg:registry
  ports:
    - "443:443"
  environment:
    SITESSL: w-local

ローカル registry の使い方

こうしてできあがった自分用 registry への URL が https://taigei.myhost.name/ とします。

ローカル registry の認証

$ docker login taigei.myhost.name
でログインしておきます。
この認証は docker hub のものとは別なので、docker hub へのログインと push/pull はこれまで通り行われます。

ローカル registry への push/pull

コンテナイメージを作成する際に ユーザー名/コンテナイメージ名 とスラッシュ前にユーザー名を書くのは docker hub での約束事でした。このイメージにつけたユーザー名で docker hub にログインするということですね。
このスラッシュ前をローカル registry のアドレスにすることで、ローカルに push することができるようになります。
$ docker push taigei.myhost.name/testcontainer
この規則があるおかげで、docker hub にログインしたままでもローカル registry と区別ができ、ローカル用のものをうっかり docker hub にあげてしまうといったことが起こりにくくなっています。
pull するときもスラッシュの前にアドレスをつければそこに繋いで pull しようとするので、他の指定は要りません。

ローカル registry に登録されているイメージの検索

docker hub の場合は docker search で検索することができました。
ローカル registry は search を使っての検索ができません。(認証なしサーバーならできるかも)
WEBブラウザ等で https://taigei.myhost.name/v1/search&q= にアクセスすることで検索結果を json で取得できます。q=の後ろには検索文字列を指定してくださいね。