アジャイル DevOps
あらゆるものをバージョン管理する
ソフトウェア・システムのすべての構成要素をバージョン管理しなければならない理由を学ぶ
コンテンツシリーズ
このコンテンツは全#シリーズのパート#です: アジャイル DevOps
このコンテンツはシリーズの一部分です:アジャイル DevOps
このシリーズの続きに乞うご期待。
あらゆるものをバージョン管理するとは、その言葉どおり、インフラストラクチャーから、構成、アプリケーション・コード、そしてデータベースに至るまでのすべてを管理するということです。このすべてをバージョン管理することにより、ソフトウェア・システムを作り出すための唯一正式な構成要素を持つこととなり、ソフトウェア・システム (そして、ソフトウェアを作成するために必要なすべてのもの) を 1 つの総体的な単位としてとらえることができるようになります。チームがすべてのものをバージョン管理すれば、始終アプリケーション・コードのどのバージョンがどのデータベースに対応しているのかを調べたり、ソフトウェア・アプリケーションのどのバージョンがどの環境で動作するのかを調べたりすることはありません。そのようなチームは、ソフトウェア・システムを構成するソース・ファイルを共有サーバー上に置くことも、ラップトップ上のフォルダー内に隠すことも、バージョン管理されていないデータベースに組み込むこともしません。
すべてのものをバージョン管理していれば、許可されたチーム・メンバーの誰もが随時、(アプリケーション・コード、構成、インフラストラクチャー、およびデータで構成される) ソフトウェア・システムのどのバージョンでも再現することができます。また、バージョン管理リポジトリー (ほんの一例を挙げると、Subversion、Git、CVS、Rational ClearCase など) にコミットされたバイナリー以外の成果物のみ (ただし、変更されることのないライブラリーは例外です) を使用して、ソフトウェア・システム全体を作成することができます。
私の経験では、すべてのものをバージョン管理するという概念を理解するのは簡単ですが、この概念が完全に適用されている例を目にすることはほとんどありません。もちろん、アプリケーション・コードや、構成の一部、そしておそらくデータについては、バージョン管理が行われているのを目にするでしょう。ソフトウェア開発で使用するライブラリーを管理するために、依存関係のリポジトリーやツール (例えば、Nexus) を使用しているチームもあれば、共有ドライブとバージョン管理システムの組み合わせを使用しているチームもあります。けれども、構成のすべて、構成が依存するコンポーネントのすべて (yum
、apt-get
、rpm
などのパッケージ・リポジトリーのコンポーネント)、そしてデータベースとそのデータベースを構成するデータを作成するために必要なスクリプトのすべてをバージョン管理している企業を見かけることはあまりありません。
すべてのものをバージョン管理しているかどうかを判断するには、単純に「バージョン管理システムから特定のリビジョンを取得する 1 つのコマンドを実行して、特定のバージョンの (インフラストラクチャー、データ、ソフトウェア、構成を備えた) 完全なソフトウェア・システムを再現できるかどうか?」を考えてみてください。これができなければ、すべてのものをバージョン管理していることにはなりません。
すべてのものをバージョン管理する際に重要となる前提条件は、すべてのソース成果物をスクリプトの形にすることです。このことは、インフラストラクチャー、データ、構成、アプリケーション・コードに当てはまります。唯一の例外として、決して変更を加えることなく使用されるライブラリーとパッケージ (JAR ファイルや RPM パッケージなど) はスクリプト化する必要はありませんが、すべてのソース成果物がスクリプト化されていれば、これらの成果物を簡単にバージョン管理することができます。
この記事では、各種のソフトウェア成果物をコードに記述する方法と、これらのコードを効果的に利用する方法を説明します。記事に記載するコードは、各コンポーネントをスクリプトとして実行およびバージョン管理できるように定義する方法を例示するものであり、コンポーネント・タイプごとのスクリプトを作成および実行する方法を説明するものではありません。この記事で説明するコンポーネントについては、連載「アジャイル DevOps」および連載「万人のためのオートメーション」の記事にさらに詳しい例が記載されています。
アプリケーション・コード
アプリケーション・コードは、ソフトウェア・システムの構成要素のなかでも、バージョン管理しなければならないものとして、おそらく最も明白な要素です。アプリケーション・コードの一例として、リスト 1 に、特定のデータを取得するためにオブジェクトのメソッドを呼び出す単純な Java クラス (UserServiceImpl
) を記載します。
リスト 1. Java アプリケーション・コード
... public Collection findAllStates() { UserDao userData = new UserDaoImpl(); Collection states = userData.findAllStates(UserDao.ALL_STATES); return states; } ...
図 1 に、新しいアプリケーション・コードのソース・ファイル (リスト 1 に記載した UserServiceImpl.java) を GitHub でホストされている Git バージョン管理リポジトリーにコミットする方法を示します。
図 1. 新規ソース・コード・ファイルを Git リポジトリーにコミットおよびプッシュするコマンド

ソフトウェア・アプリケーションを作成するために必要なアプリケーション・コードはすべて、バージョン管理リポジトリーにコミットしてください。他のすべてのソース・ファイル (インフラストラクチャー・コード、データ、構成) にも、これと同じプロセスを使用します。
インフラストラクチャー
アプリケーション・ソース・ファイルの場合とまったく同じように、インフラストラクチャーもコードとして定義することができます (「アジャイル DevOps: インフラストラクチャーの自動化」を参照)。したがって、インフラストラクチャーもバージョン管理システムでバージョン管理することができます。インフラストラクチャーのスクリプトにはマニフェスト、モジュール、クックブックなどが指定される場合もありますが、いずれにしても、環境を作成するために実行できるテキスト・ベースのスクリプトであることに変わりはありません。
インフラストラクチャーをコードに定義することがベスト・プラクティスであるとしたら、人々は一般にどのような手法を採っているのでしょうか?それは、環境を毎回手作業で構成する「作品」を寄せ集めるという手法か、手作業による手順と自動スクリプトの実行を組み合わせるという手法です。いずれにしても、エンジニアが毎回その一連のステップを実行しなければならないことから、どちらの手法もボトルネックとなります。このボトルネックを是正するために、説明書に 1 つひとつのステップを入念に記述しようとする人もいるかもしれませんが、問題は、説明書に誤りやステップの記述漏れがある場合や、一連のステップを実行するオペレーターが正確に手順に従わない場合もあることです。したがって、1 つのコマンドで実行できるコードにインフラストラクチャーを完全に記述することが、唯一の解決策となります。
例えば、リスト 2 に記載する Puppet マニフェストは、PostgreSQL データベースをインストールする手順をコードに記述しています。このコードは、コマンドラインから実行することも、CI (Continuous Integration: 継続的インテグレーション) サーバーから実行することもできます。
リスト 2. PostgreSQL のインストールを記述する Puppet マニフェスト
class postgresql { package { "postgresql8-server": ensure => installed, } exec { "initdb": unless => "[ -d /var/lib/postgresql/data ]", command => "service postgresql initdb", require => Package["postgresql8-server"] } ...
このマニフェストは単独で、サーバーのダウンロードから、インストール、実行までを行います。さらに他のマニフェストを追加することによって、環境全体をスクリプトに記述することができます。これらのスクリプトをバージョン管理リポジトリーにチェックインすれば、インフラストラクチャーのすべてのリビジョンを追跡できるようになるため、より効果的な変更管理が実現します。
構成
構成は、環境によって異なる情報を定義します。そのような情報の例には、ディレクトリーとファイルの場所、ホスト名、IP アドレス、サーバー・ポートなどがあります (リスト 3 を参照)。環境を作成する際に実行されるスクリプトでは、この構成情報を使用して、ビルド、デプロイメント、そしてテストを実行します。
リスト 3. properties ファイルに定義された構成
jboss.home=/usr/local/jboss jboss.server.hostname=jenkins.example.com jboss.server.port=8080 jboss.server.name=default
リスト 4 のコードは、構成情報を NoSQL データベースにロードする Ruby スクリプトです。
リスト 4. 動的構成情報を NoSQL データベースに書き込むスクリプト
AWS::SimpleDB.consistent_reads do domain = sdb.domains["stacks"] item = domain.items["#{opts[:itemname]}"] file.each_line do|line| key,value = line.split '=' item.attributes.set( "#{key}" => "#{value}") end end
リスト 4 の構成情報 (IP アドレス、ドメイン名、マシン・イメージなど) はすべて動的に取得できるため、この構成でハード・コーディングする部分はまったくありません。構成のすべてを動的にすることは不可能な場合でも、クラウドを使用すれば、ほとんどのソフトウェア・デリバリー・システムの悩みの種となりがちな、ハード・コーディングされた構成の部分を大幅に減らすことができます。
データ
リレーショナル・データベースの構造は、DDL (Data Definition Language) スクリプトで定義することができます。DDL スクリプトには、データベース、テーブル、プロシージャーなど、データを除くあらゆるものを作成するためのスクリプトが含まれます。データを定義する場所は、DML (Data Manipulation Language) スクリプトです。DML スクリプトには、挿入、更新、削除のステートメントが含まれます。
リスト 5 に、データベース・テーブルを作成する手順を実行する DDL スクリプトの一部を抜粋します。
リスト 5. データベース・テーブルを作成する DDL
CREATE SEQUENCE hibernate_sequence START WITH 1 INCREMENT BY 1 NO MINVALUE \ NO MAXVALUE CACHE 1; ALTER TABLE public.hibernate_sequence OWNER TO cd_user; CREATE TABLE note ( id bigint NOT NULL, version bigint NOT NULL, cd_id bigint NOT NULL, \ note character varying(10000) NOT NULL, note_date_time timestamp without time zone \ NOT NULL); ALTER TABLE public.note OWNER TO cd_user; ...
リスト 6 に、Liquibase XML スクリプトの抜粋を記載します。Liquibase は、データベース変更管理用のオープンソースのドメイン特化言語 (DSL) です。
リスト 6. 既存のデータベースの列を変更する Liquibase スクリプト
<changeSet id="9" author="jayne"> <addColumn tableName="distributor"> <column name="phonenumber" type="varchar(255)"/> </addColumn> </changeSet> ...
このようにデータベースの作成、データ、そしてデータベースの変更は、スクリプトに定義することができます。これらのスクリプトはビルド・プロセスの一貫として実行され、バージョン管理リポジトリーで完全にバージョン管理されます。
ビルドとデプロイメント
ビルドは、すべてのソース・ファイルをコンパイルして 1 つのディストリビューションにパッケージ化します。アプリケーション・コードの場合、ディストリビューションは WAR ファイルなどのバイナリーになるのがほとんどです。ビルドでインフラストラクチャー・スクリプトを実行して、環境を作成することもできます。作成される環境を仮想インスタンス、またはインスタンスを定義可能なイメージにすることも可能です。さらにビルドでは、データベース・スクリプトからデータベースを生成することもできます。ビルドで使用するのは、構成ファイルまたはデータベースに定義された構成です。リスト 7 に、ビルドのディレクトリーおよび構成を定義する Maven ビルド・スクリプトの一部を抜粋します。
リスト 7. Maven ビルド・スクリプトの抜粋
... <build> <finalName>embeddedTomcatSample</finalName> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>appassembler-maven-plugin</artifactId> <version>1.1.1</version> <configuration> <assembleDirectory>target</assembleDirectory> <programs> <program> <mainClass>launch.Main</mainClass> <name>webapp</name> </program> </programs> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>assemble</goal> </goals> </execution> </executions> </plugin> </plugins> </build> ...
いわゆる「デプロイメント自動化ツール」と呼ばれるツールを提供しているベンダーはいくつかありますが、その呼び名には多少誤りがあります。それは、これらのツールが行うのはデプロイメントの自動化ではなく、オーケストレーションだからです。これらのツールでデプロイメントのステップと順序を記述することはできても、実際のデプロイメントは一連のスクリプトや手動のプロセスによって行われます。デプロイメント成果物とワークフローのバージョン管理をサポートするツールというものには、めったにお目にかかれません。これらのツールのなかには内部でバージョン管理を行うものもありますが、そのツールを常に使用しているのでない限り、ソフトウェア・システムの特定のリビジョンを探す場合にはほとんど役に立ちません。ツールを常に使用するとしても、デプロイメントのバージョン管理とその他のソース成果物のバージョン管理は切り分けられています。しかし、このようなベンダーのツールを使用している場合でも、デプロイメントのバージョン管理を他と切り分ける必要はありません。代わりの手法となるのは、デプロイメント全体を、Capistrano などのデプロイメント自動化 DSL で記述することです。デプロイメントをスクリプトにすれば、デプロイメントのバージョン管理が可能になり、1 つのコマンドを実行するだけで完全なデプロイメントを実行することができます。自動化されたデプロイメントに自動テストを組み合わせて、オーケストレーション・ツールでそのデプロイメント・スクリプトを実行してください。
Capistrano は、複数のプラットフォームでのデプロイメントを記述するための DSL です。Capistrano を使用すると、複数のノードおよび環境の役割にまたがるデプロイメントに対し、サーバーの停止、ファイルのコピー、ワークフローの適用などのタスクを定義することができます。リスト 8 に、Capistrano スクリプトの抜粋を記載します。
リスト 8. Capistrano でのデプロイメント・スクリプトの抜粋
namespace :deploy do task :setup do run "sudo chown -R tomcat:tomcat #{deploy_to}" run "sudo service httpd stop" run "sudo service tomcat6 stop" end ...
デプロイメントを記述するには、DSL を使用するのが効果的な方法です。DSL を使用してデプロイメントのすべてのステップをスクリプトにすれば、これらのスクリプトは独自仕様のツールと密接に結合されないため、継続的デリバリー・パイプラインを実施しているチームにぴったりの方法となります。デプロイメントをスクリプトとして定義した後は、デリバリー・パイプラインの他のすべてのコンポーネントと同じようにデプロイメントをバージョン管理することができます。
システム
進歩的なチームの間では、インフラストラクチャー、構成、データ、およびアプリケーション・コードをバージョン管理することが望ましいという意見が大多数を占めるようになってきていますが、バージョン管理できるコンポーネントはこれらのほかにもあります。それは内部システム (CI 環境を定義するための構成など) です。ソフトウェア・デリバリー・システムの各部を作成するために使用している環境が機能しなくなったとしたら、ソフトウェア・システムがどうなるかを考えてみてください。こうした場合に備えて、ソフトウェア・システムの環境を完全にスクリプト化した後に、インフラストラクチャー自動化ツールを使用して、ソフトウェア・デリバリー・システムを作成するための環境を完全にスクリプト化することができます。このインフラストラクチャー・コードと、CI 構成に対して行われた変更 (CI ジョブ構成など) はバージョン管理されます。リスト 9 に、Jenkins サーバーとジョブ構成をバージョン管理する例を記載します。
リスト 9. Jenkins サーバーと構成変更をバージョン管理する単純な bash
スクリプト
#!/bin/bash -v # Change into your jenkins home. cd /usr/share/tomcat6/.jenkins # Add any new conf files, jobs, users, and content. git add *.xml jobs/*/config.xml plugins/*.hpi .gitignore # Ignore things we don't care about cat > .gitignore <<EOF log *.log *.tmp *.old *.bak *.jar .* updates/ jobs/*/builds jobs/*/last* jobs/*/next* jobs/*/*.csv jobs/*/*.txt jobs/*/*.log jobs/*/workspace EOF # Remove anything from git that no longer exists in jenkins. git status --porcelain | grep '^ D ' | awk '{print $2;}' | xargs -r git rm # And finally, commit and push git commit -m 'Automated commit of jenkins configuration' -a git push
ソフトウェア・システムの他のあらゆる構成部分と同様に、このスクリプトをバージョン管理リポジトリーでバージョン管理することができます。
バージョン管理の対象
今回の記事では、任意の時点でソフトウェアをリリースすることを可能にする継続的デリバリー・プラットフォームを開発チームと運用チームが協力して作成するときには、すべてのものがバージョン管理の対象となること、そしてすべてのものがバージョン管理されることを説明しました。インフラストラクチャー、データ、アプリケーション・リソースのすべてがバージョン管理されていれば、ソフトウェア・システムを提供するシステムを構成するコンポーネントもバージョン管理することができます。ユーザーのために開発するソフトウェア・システム、そしてこれらのソフトウェア・システムをユーザーに提供する際に使用する内部システムのすべてのリソースをスクリプト化することで、あらゆるもののバージョン管理が可能になります。
次回の記事では、動的構成管理について説明します。この手法では、静的な環境固有のプロパティーを構成に使用することがなくなります。
ダウンロード可能なリソース
関連トピック
- 「List of revision control software」と「Comparison of revision control software」: この 2 つの Wikipedia の記事からわかるように、さまざまなタイプのバージョン管理ソフトウェアを利用できます。
- 「Automation for the people: Hands-free database migration」(Paul Duvall 著、developerWorks、2008年8月): Liquibase を利用してデータベースの変更を管理してください。
- 「Continuous Delivery in the Cloud: Dynamic Configuration」(Brian Jakovich 著、Stelligent、2012年10月): クラウド・インフラストラクチャーで、デリバリー・パイプラインのプロパティーを動的に構成できます。
- 「Continuous Delivery in the Cloud: Deployment Automation」(Brian Jakovich 著、Stelligent、2012年10月): Capistrano を使用して、自動化されたデプロイメントを定義して実行することができます。
- 「Continuous Delivery in the Cloud: Infrastructure Automation」(Brian Jakovich 著、Stelligent、2012年10月): Puppet を使用して、インフラストラクチャーをコードとして定義できます。
- Twitter で developerWorks をフォローしてください。
- Rational ClearCase: Rational ClearCase は高度なバージョン管理、ワークスペース管理、並行開発のサポート、そしてビルド監査により、生産性を向上させます。
- IBM Tivoli Provisioning Manager: Tivoli Provisioning Manager は物理サーバー、仮想サーバー、ソフトウェア、ストレージ、およびネットワークを自動化して、動的なインフラストラクチャーを実現します。
- IBM Tivoli System Automation for Multiplatforms: Tivoli System Automation for Multiplatforms は、エンタープライズ規模のアプリケーションおよび IT サービスの高可用性および自動化を実現します。
- ご自分に最適な方法で IBM 製品を評価してください。評価の方法としては、製品の試用版をダウンロードすることも、オンラインで製品を試してみることも、クラウド環境で製品を使用することもできます。また、SOA Sandbox では、数時間でサービス指向アーキテクチャーの実装方法を効率的に学ぶことができます。