この投稿では、TheQuestionでの長年の夢である、個々のタスクごとに個別に自動的に展開される開発環境をどのように実現したかを説明します。
当初から、開発は次のように構築されていました。
- GitHubには
master
ブランチがあり、継続的な統合が構成されています。単一のテストサーバーへの展開を完全に自動化し、そのアーキテクチャで可能な限り運用を繰り返します。 - 新しいタスクはそれぞれ開発者の分岐ブランチで実行され、
master
のリクエストプールが開き、最終的にそこでマージされます。
master
ブランチのCIは、非常に通常の方法で配置されていると言う価値があります。
- githubにプッシュ
- TeamCityは新しいコミットを確認して作成します
- 自動テストの実行
- Dockerコンテナーが予定されています
- Ansibleはコンテナをデプロイします
あまり変わらないように、このシーケンスとツールを維持したかったのです。
1つの開発者の明らかな欠点は、一度に1つのブランチしか見ることができず、未完了のタスクが相互に干渉し、一定の競合を解決する必要があることです。 私たちの目標は、GitHubで新しいブランチが作成されるとすぐに、別の開発者がGitHubで作成されることでした。
一見、タスクは難しくありません-クラウドプラットフォームのAPIを確認し、新しいブランチでの最初のコミットが開始される前に、このブランチ用に別のサーバーを作成します-シンプルで、すでに1台のマシンでスキャンが行われています、Ansible!
しかし、重要な問題が1つあります。データベースです。 控えめなマシンでの圧縮ダンプ(まだダウンロードする必要があります)からの完全スキャンには、約2時間かかります。 もちろん、すべてをより効率的なマシンにデプロイするか待つだけでかまいませんが、クラウドAPIを使用してすべてを書き直したくはありません(別の場所に移動するときにすべてを書き直さなければならないという事実にもかかわらず)。 したがって、このソリューションでは、1台の平均的なマシンを使用します。
Teamcity
これはほとんどカスタマイズする必要のない素晴らしいツールです。 彼に必要なことは、どのブランチを使用しているかをスクリプトに伝えることだけです。
だから、唯一のBuild Step: command line
変更Build Step: command line
から
cd clusters/dev make
になった
export branch_name=%teamcity.build.branch% cd clusters/dev make
Docker
乙女が1人いると、インフラストラクチャの各部分は、アプリケーションアプリケーションの一部であるかどうかに関係なく、Sphinx、Redis、Nginx、またはPostgreSQLが別々のコンテナ内で起動されました。 --network-mode=host
指示で開始されました。つまりip:port
コンテナの各ip:port
ホストマシンのlocalhost:port
と一致していました。
ご理解のとおり、これは複数のバージンでは機能しません。まず、コンテナは1つのブランチのコンテナとのみ通信する必要があり、次に、 nginx
は必要な各コンテナの内部IPを知っている必要があります。
次に、 Docker network
が助けになり、コンテナの起動が
docker run /path/to/Dockerfile
で
docker network create ${branch_name} --opt com.docker.network.bridge.name=${branch_name} docker run --network=${branch_name} -e branch_name=${branch_name} /path/to/Dockerfile
これにより、次のことがわかります。
- Dockerネットワークの名前はブランチの名前と一致します
- ネットワークインターフェイスの名前がブランチの名前と一致する
- 各ブランチのコンテナは1つのdockerネットワークに配置され、名前によって通信できるようにします(dockerは各
bridge
ネットワーク内にDNSレコードを作成します) - コンテナ内に、ブランチの名前で環境変数が作成されます。これはさまざまな構成を生成するために必要です
PostgreSQL
前と同様に--network=host
を指定してコンテナで実行します。これにより、DBMSは1つになりますが、ブランチごとに-独自のユーザーと独自のデータベースがあります。
新しいデータベースをすばやく展開するタスクは、テンプレートによって完全に解決されます。
CREATE DATABASE db_name TEMPLATE template_name
加えて、毎日販売のデータベースの新しいコピーを持ちたいので、ブランチを作成するときにそれを信頼します( --network=host
別のコンテナに流れ--network=host
)
これを行うには、2つのベースを作成します。 毎晩、新しいダンプを1つに展開するのに2時間かかります。
pg_restore -v -Fc -c -d template_new dump_today.dump
そして成功した場合:
DROP template_today; CREATE DATABASE template_today TEMPLATE template_new;
その結果、毎朝新しいテンプレートがあり、次のダンプが破られて展開に失敗した場合でも残ります。
新しいブランチを作成するとき、テンプレートからベースを作成します
CREATE USER db_${branch_name}; CREATE DATABASE db_${branch_name} OWNER db_${branch_name} TEMPLATE template_today;
したがって、ブランチ用に個別のデータベースを作成するには2時間ではなく20分かかり、Dockerコンテナ内からの接続は、常にホストマシンIPを指すeth0
インターフェイスを介して行われます。
nginx
また、ホストマシンにインストールし、 docker inspect
を使用して構成を収集します。このコマンドは、コンテナに関する完全な情報を提供します。そこから、構成テンプレートで置き換えるIPアドレスが必要になります。
また、ネットワークインターフェイスの名前がブランチの名前と一致するため、1つのスクリプトですべてのバージンの構成を一度に生成できます。
for network in $(ip -o -4 as | awk '{ print $2 }' | cut -d/ -f1); do if [ "${network}" == "eth0" ] || [ "${network}" == "lo" ] || [ "${network}" == "docker0" ]; then continue fi IP=$(docker inspect -f "{{.NetworkSettings.Networks.${network}.IPAddress}}" ${container_name}) sed -i "s/{{ ip }}/${IP}/g" ${nginx_conf_path} sed -i "s/{{ branch_name }}/${network}.site.url/g" ${nginx_conf_path} done
ブランチを削除する
master
を除く各ブランチの寿命が短いため、ブランチに関連するすべてのもの(nginx config、containers、base)を定期的に削除する必要があります。
残念ながら、ブランチが削除されたことをTeamCityに伝える方法を見つけることができなかったので、私は考えなければなりませんでした。
次のブランチがデプロイされると、その名前のファイルがマシン上で呼び出されます:
touch /branches/${branch_name}
これにより、所有しているすべてのブランチだけでなく、最後の変更の時刻(ファイルが変更された時刻と一致する)も思い出すことができます。 ブランチをすぐに削除するのではなく、使用を停止してから1週間後に削除すると非常に便利です。 次のようになります。
#!/usr/bin/env bash MAX_BRANCH_AGE=7 branches_to_delete=() for branch in $(find /branches -maxdepth 1 -mtime +${MAX_BRANCH_AGE}); do branch=$(basename ${branch}) if [ ${branch} == "master" ]; then continue fi branches_to_delete+=(${branch}) done dbs=() for db in $(docker exec -it postgresql gosu postgres psql -c "select datname from pg_database" | \ grep db_ | \ cut -d'_' -f 2); do dbs+=(${db}) done for branch in ${branches_to_delete[@]}; do for db in ${dbs[@]}; do if [ ${branch} != ${db} ]; then continue fi # branch file rm /branches/${branch} # nginx rm /etc/nginx/sites-enabled/${branch} # containers docker rm -f $(docker ps -a | grep ${branch}- | awk '{ print $1 }') # db docker exec -i postgresql gosu postgres psql <<-EOSQL DROP USER db_${branch}; DROP DATABASE db_${branch}; EOSQL done done service nginx reload
いくつかの落とし穴
すべてが機能し、 master
をつかまえるとすぐに-彼は準備ができませんでした。 master
iproute2
master
キーワードであることが判明したため、 ifconfig
を一緒に使用してIPコンテナーを定義しました。
だった:
ip -o -4 as ${branch_name} | awk '{ print $2 }' | cut -d/ -f1
になりました:
ifconfig ${branch_name} | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'
thq-1308
ブランチが( Jira
からのタスク番号によって)作成されるとすぐに、アセンブルされませんでした。 そしてすべてはダッシュのためです。 いくつかの場所で邪魔になります:PostgreSQLとDocker Inspect出力テンプレート。
その結果、ホストIPがわかります。
docker inspect -f "{{.NetworkSettings.IPAddress}}" ${network}-theq
新しいデータベースのすべてのテーブルの所有者を変更します。
tables=`gosu postgres psql -h ${DB_HOST} -qAt -c "SELECT tablename FROM pg_tables WHERE schemaname = 'public';" "${DB_NAME}"` for tbl in $tables ; do gosu postgres psql -h ${DB_HOST} -d "${DB_NAME}" <<-EOSQL ALTER TABLE $tbl OWNER TO "${DB_USER}"; EOSQL done
一般に、それですべてです。 完全なコマンド、スクリプト(最後のコマンドを除く)、ansibleの役割は与えませんでした-特別なことは何もありませんが、その点を見逃していないことを願っています。 コメントのすべての質問に答える準備ができています。