DockerとS3を用いた、yum / apt リポジトリ作成・運用の継続的インテグレーション

Docker を用いた rpm / deb パッケージ作成の継続的インテグレーション - ゆううきブログ の続き。 前回は、rpm / deb パッケージを作るために、CentOS、Debianなど各種ディストリビューションを揃える手間をかけずに、Docker コンテナ上でパッケージングして、ついでに Jenkins で CI するみたいなことを書いた。

今回は、作成したパッケージを yum / apt リポジトリに登録して yum / apt コマンドでパッケージインストール/アップデートできるようになるまで継続的インテグレーションするという話。

問題点

  • yum / apt リポジトリ用の専用ホストを立てて、そこで apache とかで静的ファイルをホストするのはめんどくさい。
    • 特に、mackerel-agent みたいなユーザにインストールしてもらうパッケージの場合、リポジトリを公開しないといけなくて、冗長化とか面倒はさらに増える。
  • リポジトリ作成のために必要なメタファイル群を作成するために、createrepo や reprepro コマンドがインストールされた環境が必要となる。
    • OSX とかではインストールできない。(がんばったらできるのかもしれない)

Docker と S3 で解決

  • yum / apt リポジトリ用の専用ホストを立てて、そこで apache とかで静的ファイルをホストするのはめんどくさい。

前者については、S3 に静的ファイルをアップロードすることにより、サーバもたなくてよいので簡単。 手順は簡単で、S3のWebコンソールで適当にバケットを作って、createrepo または reprepro で作成したファイルをs3cmd とか aws-cli でバケットにアップロードする。 バケットには、bucket-name.s3.amazonaws.com. のようなエンドポイントがつくので、各ホストで リポジトリの設定を書くだけ。

さらに、Jenkins で master ブランチが更新されたときにパッケージの作成と同時に S3 にアップロードすることで、勝手にリポジトリに最新パッケージが入るので便利。今触ってるのは一般公開する代物なので、リリースは慎重に手でやってるけど、社内リポジトリとかならJenkinsで自動化したらよさそう)

  • リポジトリ作成のために必要なファイル群を作成するために、createrepo や reprepro コマンドがインストールされた環境が必要となる。

手元は OSX でも、Docker 上の各ディストリビューションでディストリ依存な createrepo や reprepro をインストール・実行できる。

ディレクトリ構成と Dockerfile

  • ディレクトリ構成 (yum)
tree -a
.
├── .s3cfg
├── Dockerfile
├── files
│   └── mackerel-agent-x.x.x.noarch.rpm
└── macros
  • ディレクトリ構成 (apt)

  • Dockerfile (yum)

# docker build -t 'hatena/mackerel/yum-repo' .
# docker run -t 'hatena/mackerel/yum-repo'

FROM centos
ENV PATH /usr/local/bin:/usr/sbin:/sbin:/usr/bin:/bin
RUN yum update -y
RUN rpm -ivh http://ftp.iij.ad.jp/pub/linux/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm
RUN yum install --enablerepo=epel -y createrepo s3cmd

RUN install -d -m 755 /centos/x86_64
ADD files /centos/x86_64/
RUN /usr/bin/createrepo --checksum sha /centos/x86_64

ADD .s3cfg /.s3cfg
WORKDIR /centos/
CMD /usr/bin/s3cmd -P sync . s3://yum.mackerel.io/centos/
  • Dockerfile (apt)
# docker build -t 'hatena/mackerel/apt-repo' .
# docker run -t 'hatena/mackerel/apt-repo'

FROM tatsuru/debian
ENV PATH /usr/local/bin:/usr/sbin:/sbin:/usr/bin:/bin
ENV DEBIAN_FRONTEND noninteractive

RUN apt-get -y update
RUN apt-get -y install reprepro s3cmd
RUN apt-get clean

RUN mkdir -p /deb
ADD files /deb/
WORKDIR /debian
RUN mkdir -p db dists pool
ADD conf /debian/conf
RUN reprepro includedeb mackerel /deb/*.deb
RUN reprepro export

ADD .s3cfg /.s3cfg
CMD /usr/bin/s3cmd -P sync . s3://apt.mackerel.io/debian/

リポジトリの設定

独自リポジトリなので、インストール先ホストではリポジトリを設定する必要がある。 yum / apt で、それぞれ次のように設定するとよい。

  • yum の場合

/etc/yum.repos.d/mackerel.repo

[mackerel]
name=mackerel-agent
baseurl=http://yum.mackerel.io/centos/\$basearch
  • apt の場合
echo "deb http://apt.mackerel.io/debian/ mackerel contrib" >> /etc/apt/sources.list.d/mackerel.list

所感

Docker または Vagrnat を使うと、いちいちディストリビューション環境用意しなくてよいし、余分なサーバをもたなくて済む。 これは、Virtualbox や VMware で仮想ホストを立ち上げても同じだけど、Docker や Vagrant は CLI で操作できてプログラマブル(特に Docker はREST APIがある)なので、自動化しやすい。 さらに、Docker なら毎回クリーンな環境からリポジトリを構築しやすいのでゴミが入りにくいというメリットがある。 (Vagrnat でもできるけど、壊して再構築するのに時間がかかる)

前にも書いたけど、Docker 使うと、今まで特別な環境でしかできなかったことを気軽に手元でできるようになったりする。 Docker が Immutable Infrastructure 専用の何かみたいに言われることがあって、結局まだそんなにうまく使えないよねみたいな話あるけど、用途はいろいろあると思う。

Docker を用いた rpm / deb パッケージ作成の継続的インテグレーション

サーバ管理ツールのエージェント みたいなソフトウェアをインストールしやすくするために、rpm / deb パッケージを作りたい。

しかし、rpm / deb パッケージ化するためには、それぞれ CentOS(RedHat)、Debian(Ubuntu) 環境でパッケージ化することになる。 社内ではこれまでパッケージ化の専用ホストがいて、そこで spec ファイルや init スクリプトを置いて rpmbuild コマンドとか debuild コマンドを叩いてパッケージを作成していた。 さらに、アプリケーションエンジニアからインフラエンジニアに依頼するという形をとっていた。

この方法の問題点として、以下の3つがある。

  • spec ファイルや init スクリプトなどをプロジェクトの Git リポジトリで管理しづらい。つまり、レビューとかがやりにくい。
  • リリースフローを自動化しづらい。具体的には、リリースの度に専用ホストにログインして手でコマンド叩いてことになる。(社内リリース的なのは毎日やる)
  • 1台の専用ホストを複数人で共有することになるので、コンフリクトしやすい。

Docker で解決

rpm / deb パッケージを Docker コンテナ内で作成することにより、上記の問題点が解決できる。

  • spec ファイルや init スクリプトなどをプロジェクトの Git リポジトリで管理しづらい。つまり、レビューとかがやりにくい。

boot2docker とか Vagrant を使えば開発者の Mac でもビルドできるようになったので、いちいち専用ホストでファイル編集とかしなくてよくなった。 リポジトリ内で完結しているので、アプリケーションエンジニアでもインフラエンジニアでも、普通のアプリケーションと同じ間隔で、specファイルとか編集して、pullreq & レビューできる。

  • リリースフローを自動化しづらい。具体的には、リリースの度に専用ホストにログインして手でコマンド叩いてことになる。(社内リリース的なのは毎日やる)

Jenkins でテスト成功後に、同じジョブでパッケージビルドさせることで、明示的にパッケージ化するコストがなくなった。 さらに、テストが成功しているものだけパッケージ化されるので、変なリビジョンのコードがパッケージされてないか人間が確認しなくてよい。

  • 1台の専用ホストを複数人で共有することになるので、コンフリクトしやすい。

Docker コンテナという隔離環境を毎回作っては捨てるので作業がコンフリクトしない。コード的なコンフリクトはGitで解決できる。

ディレクトリ構成と Dockerfile

  • ディレクトリ構成 (rpm)
.
├── BUILD
│   └── mackerel-agent
├── Dockerfile
├── RPMS
│   └── noarch
├── SOURCES
│   ├── mackerel-agent.conf
│   ├── mackerel-agent.initd
│   ├── mackerel-agent.logrotate
│   └── mackerel-agent.sysconfig
├── SPECS
│   └── mackerel-agent.spec
└── SRPMS
  • ディレクトリ構成 (deb)
.
├── Dockerfile
└── debian
    ├── README.Debian
    ├── changelog
    ├── compat
    ├── control
    ├── copyright
    ├── docs
    ├── mackerel-agent
    ├── mackerel-agent.bin
    ├── mackerel-agent.conf
    ├── mackerel-agent.debhelper.log
    ├── mackerel-agent.default
    ├── mackerel-agent.initd
    ├── mackerel-agent.logrotate
    ├── rules
    └── source
        └── format
  • Dockerfile (rpm)
FROM centos
RUN yum update -y
RUN yum install -y rpmdevtools

RUN mkdir -p /rpmbuild
ADD ./ /rpmbuild/  # 上記のディレクトリをまとめてDockerコンテナ内に取り込む
RUN chown root:root -R /rpmbuild
CMD rpmbuild -ba /rpmbuild/SPECS/mackerel-agent.spec
  • Dockerfile (deb)
FROM tatsuru/debian
RUN apt-get update && apt-get install -yq --force-yes build-essential devscripts debhelper fakeroot --no-install-recommends

RUN mkdir -p /debuild/debian /deb
ADD ./debian /debuild/debian
RUN chown -R root:root /debuild
WORKDIR /debuild
CMD debuild --no-tgz-check -uc -us; mv ../*.deb /deb/

ディレクトリ構成と Dockerfile は上記のような感じで、

docker build -t 'hatena/mackerel/rpm-builder' .
docker run -t 'hatena/mackerel/rpm-builder' -v ./build/:/rpmbuild/RPMS/noarch:rw

とかやると、Docker のボリューム機能により、手元の build ディレクトリに rpm ファイルができる。 (Mac の場合は、ホストOS上のVM上でコンテナが動くので、VMのファイルシステムに設置される)。 これで、パッケージファイルの出来上がりで、こういうのをJenkinsにやらせてる。 Jenkins の成果物にしておくと、適当なところからダウンロードしやすくて便利。

思うところ

別に Docker である必要もなくて Vagrant 使ってもよい。 ただ、古いファイルとかが紛れ込まないように環境をどんどん使い捨てたいので、Vagrant よりは Docker のほうが起動が速いのでうれしさがある。 さらに、Jenkins サーバで Vagrant 動かすよりは Docker のほうがセットアップしやすい。

あと、Jenkins で何回も Docker コンテナを立てては殺す場合に、ゴミ掃除に気を使うことになる。 Docker 使ってたらサーバがゴミ捨て場みたいになってた話 #immutableinfra - ゆううきブログ

Dockerは、アプリケーションをコンテナで動作させるような Immutable Infrastructure 的なコンテキストで注目されてる。 しかし、今まで特別な環境でしかできなかったことを気軽に手元でできるようになったりするので、もっと幅広い使い道があると思う。 今回は Docker のおかげで、アプリエンジニアとインフラエンジニアで無駄なコミュニケーションコストが発生することなく、いい感じの開発フローができた。

なんか DevOps っぽい。

TODO

serverspec 的なもので、パッケージインストール後の状態を自動テストしたい。 さらに、agent 起動後の振る舞いテストとかまでできるとよい。

Docker 使ってたらサーバがゴミ捨て場みたいになってた話 #immutableinfra

Immutable Infrastructure Conference #1 : ATND

でLTしてきた。

内容はきれいにゴミを捨てましょうという話以上のものは特にない。

背景の説明が少し雑だったので補足すると、Jenkins のジョブスクリプトで、git push する度に docker run していたら ゴミがどんどんたまっていったという感じ。 1 push あたり、アプリコンテナ、DBコンテナとか合わせて3コンテナぐらい起動してるから開発が活発だと、どんどんゴミがたまる。 さらに補足すると、Device mapper がらみのゴミは、aufs 使うとかなり解決できそうな感じはしてる。 (Device mapper だとブロックデバイスレベルでイメージ差分を表現するので、デバイス毎(差分)毎に mount が走るみたいな実装になってるけど、aufs だとファイルシステム単位で複数のディレクトリを束ねてマウントするみたいなことができるので、無駄なマウントが走りにくそう。多分。)

Disposable Infrastructureを特にコンテナ型仮想化技術を使って実現しようとする場合は、ゴミがどれくらいできるのかは結構重要な指標なんじゃないかと思う。 (ゴミがひどいから枯れてるハイパーバイザ型仮想化でやるという選択もあるかもしれない)

バッドノウハウがたまったり、Docker 自体が安定してきたので、最近は rpm/deb パッケージのビルドを Docker on Jenkins でやらせるようにして開発効率をあげたりしてる。

ともあれ、今現在ゴミ掃除にしか興味がなくなってる。

効率よくゴミ掃除するために、Docker の内部実装の勉強してて、そういう話をコンテナ型仮想化勉強会とかいうやつでしゃべるかもしれない。

第3回 コンテナ型仮想化の情報交換会@大阪 (コンテナ型VMや関連するカーネル等の技術が話題の勉強会) : ATND

Docker + Mesos + Marathon + Graphite + Fluentd + Sensuを組み合わせたデプロイ管理ツールの話

開発合宿でDevOps界隈やモニタリング界隈で流行りのツールを組み合わせてBlue Green Deploymentできる何かを作りました。

同じチームで開発したid:shiba_yu36 先生やid:wtatsuru 先生が既にブログを書いてますが、自分の視点で書いてみます。(13/12/24追記: より詳細な内容が新規に書かれたのでリンク先を入れ替えました)

僕は主に、各ツールから得られる情報をまとめて管理し、デプロイを実行するデプロイ管理ツールを作成していましたので、それについて書きます。 普段は運用の修行をしていたり、サーバ管理ツールなどを作っていたりします。

背景

参照すべき資料はたくさんありますが、以下のエントリがよくまとまっていると思います。

2014年のウェブシステムアーキテクチャ - stanaka's blog

上記エントリに書かれている開発フローを引用します。

またDockerを利用することで、開発、テスト、プロダクション環境を全て同一のDockerイメージを使うことが可能となります。具体的なフローとしては、次のようになります。

  • 手元で開発しつつ、Dockerイメージを作成
  • Dockerイメージをイメージリポジトリに登録
  • JenkinsでCIテストする
    • テストが通るとデプロイ可能な状態に
  • Dockerイメージを本番サーバにデプロイ
  • 本番サーバで起動イメージを差し替えてDocker再起動
    • ロードバランサーへの組込み

本番サーバへのデプロイや、Docker再起動、ロードバランサーへの組込みは、複数のホストを効率的に管理するためのクラスタ管理ツールの役割りとなります。

Statelessサーバに限定すると、Dockerやクラスタ管理ツールをうまく使えば、Blue Green DeploymentやAuto Scaleを比較的容易に実現できそうだなということがわかってきました。 ただ、実現するためのパーツは揃っていそうだけど、各ツールを具体的にどうやって組み合わせたらいいのかよくわからない、というのが現状なのかなと思っています。 そこで、今回はStateless Server環境におけるBlue Green Deploymentの実現に的をしぼって、DockerやMesosの組み合わせ方を考えてみました。

実現したこと

安全に簡単に高速にデプロイ
  • [安全] デプロイのたびに環境を作り直す。デプロイ前に本番環境と同等の環境を用意する。さらに、以前の環境に切り戻せる。
  • [簡単] ボタン1つでデプロイできる。どの物理ホストに何個のコンテナを配置するかを考えなくて良い。
  • [高速] 環境の作成からデプロイまでが速い (およそ2分程度)

全体フローとアーキテクチャ

f:id:y_uuki:20131222173449p:plain

  • ① 人間がGit Repositoryにpush
  • ② pushを検知したJenkinsがdocker imageを作成し、そのimageをコンテナとして起動しコンテナ内でアプリケーションのテストを実行
  • ③ Jenkinsがhttps://github.com/dotcloud/docker-registry:docker private registryにdockerのimageを保存
  • ④ Jenkinsがデプロイ管理ツールに新しい環境の作成依頼
  • ⑤ デプロイ管理ツールがMarathonに依頼 Marathon -> Mesos -> Mesos-Slave -> mesos-dockerを経由してリソースの空いたホストにコンテナを作成
  • ⑥ Marathonから作成されたコンテナの情報(ホストとport番号など)を取得
  • ⑦ ユーザが本番環境として使いたい環境を選択してデプロイボタンを押す
  • ⑧ デプロイ管理ツールはnginx.confを書き換えて、本番環境の切り替えを行う。

図では表現しきれていませんが、新環境を本番環境に切り替える前にバージョン番号を付与したURLで動作の確認ができたりします。 また今回はmasterブランチが変更されたときのみ環境が作成されますが、branchごとに環境を作成するといったことも出来そうです。

Marathon と Mesosについては、このコンテナを何個作ってくださいといい感じにリソースの空いたホストを選んでコンテナを作ってくれる便利な何かという認識でいます。 Mesosの具体的なリソース管理方法などはまだまだ勉強不足です。

これ以外に環境の切り戻しの指標とするために、FluentdとSensuによりアクセスログと各コンテナのメトリクスをGraphiteに送信し、GraphiteのAPIを叩いてグラフ画像をデプロイ管理ツールに表示させています。

SensuとGraphiteによるモニタリングについては、以前に何か書いてます。

モニカジ京都に参加して、SensuとGraphiteの話をしました #monitoringcasual - ゆううきブログ

LXCのメトリクス取得の具体的な方法については、id:wtatsuru 先生が書いてくれると思います。

なぜデプロイ管理ツールが必要か

今回のデプロイ管理ツールには、各ホストおよびコンテナの情報やデプロイ番号を集約させています。 このような中央集権管理が必要な理由はいくつかありますが、現段階ではシステムが何かのアクションをする判断を自動で行うことは難しいからだと考えています。例えば、git pushしてJenkinsが回って新しい環境ができたけど、デプロイ前に一旦人間の手で動作チェックしたいという要求は必ずでてくると思います。

今回のシステムに近いものをデプロイ管理ツールなしで、例えばSerfを使って実現することもできますが、人間にデプロイするタイミングを一旦預けたり、以前の環境に切り戻すなどを実現することがかなり難しいと考えます。

サービスに必要なコンテナ数が爆発的に増加し、World Wide Webのようにとても中央で管理していられないというような状況になったときに、ある程度自律して動作する分散システムを構築する必要があるかもしれません。

デプロイ管理ツールの実装

f:id:y_uuki:20131222173756p:plain

次にデプロイ管理ツールの実装について簡単に説明します。

  • サービス情報、ホスト情報、コンテナ情報、デプロイバージョン情報を管理する4つのテーブルでデータ管理
  • docker registryから対応するサービスの最新のimage情報を取得し、デプロイバージョンテーブルに格納
  • MarathonのAPIを叩いて、デプロイ番号に対応したコンテナ群を生成
  • 生成されたコンテナのステータス(runningなど)をMarathonのSubscription APIを使って取得し、コンテナ情報テーブルを更新
  • 各情報を一覧できるビュー
    • Graphiteからグラフ画像を取得
  • デプロイボタン
    • nginxのupstreamの設定を指定した環境に書き換える
    • nginxとデプロイ管理ツールは同居しているので、単純に”nginx reload”を実行するだけ。(ここはひどい。本来はnginxに専用のデーモンを立てて、そのデーモンが設定の書き換えやreloadを実行するべきだったけど時間がなかった。)
  • 情報更新の際にIRCに通知
  • Perl、オリジナルの軽量フレームワークで書かれている

MarathonのAPI周りについては、先輩が何か書いてくれると思います。(Marathon のAPIはドキュメントとかなくて大変な感じでした)

補足

今回は上述のようなアーキテクチャに収束しましたが、これがベストな解だとは思っていません。 Blue Grenn Deploymentの実現方法の単なる一例にすぎないですし、今後の各ツールの成熟や機能追加により、特にデプロイ管理ツールの機能構成は変化すると思います。

サーバ管理ツールとの統合

YAPC::Asia 2013ではてなのサーバ管理ツールの話のはなしをしました - ゆううきブログ

今回のシステムをサーバ管理ツールに組み込むということを考えられます。 現在、サーバ管理ツールに物理ホスト、仮想ホストの情報などを集約させていますが、ここにコンテナ情報とデプロイ番号の情報も合わせて管理すると、サーバ管理ツールとデプロイ管理ツール間でデータが分散せずに済むと思います。 今後は、Dockerコンテナの管理に対応したサーバ管理ツール/モニタリングツールが必要になってくると思います。

課題

  • 先輩方も書いてるけど、プロダクション環境で使うためには、DockerやMesos、Marathonといった今回実現した仕組みに必要不可欠なツールの成熟が必要。
  • FluentdとSensuの役割がかぶっているので、できればFluentdでメトリクスをとりたい
  • Mesosの仕組みがあまり理解できていないところが多い
  • Stateful Serverどうする?
  • サーバ管理ツールと統合したい

感想

  • 結構安定して動いていて、気軽にデプロイできるので、デプロイボタンを連打して遊んでいました。
  • デプロイ管理ツールの名前はRocket::Launcherという名前になってて、Rocket::Launcherでデプロイすることをランチャーと呼びます。
  • 更なる詳細の説明とかどうしてもOSS化してほしいみたいな要望があればがんばります