無印吉澤

Site Reliability Engineering(SRE)、ソフトウェア開発、クラウドコンピューティングなどについて、吉澤が調べたり試したことを書いていくブログです。

解説:Otto 0.1で dependency (AppType = docker) を指定した場合の動作

はじめに

Otto とは、9月のHashiConfで発表された、Vagrantの後継となるソフトウェアです。9月に Version 0.1.0 がリリースされ、現在の最新版は 0.1.2 です。

Ottoの設定ファイル "Appfile" には、アプリケーション間の依存関係を定義することができます。以下の例は、Rubyで書かれたWebアプリ(otto-getting-started)がMongoDBに依存していることを示しています(Getting Started GuideApp Dependencies ページより抜粋)。

application {
  name = "otto-getting-started"
  type = "ruby"

  dependency {
    source = "github.com/hashicorp/otto/examples/mongodb"
  }
}

dependencyのsourceが示す先にはそのアプリケーション(上記の例ではMongoDB)のAppfileがあります。そのAppfileにもdependencyが書かれていたら、Ottoは芋づる式にそれらのAppfileをダウンロードします。

Otto の目標は、このようにアプリケーションの依存関係が定義された1個の Appfile を元に、複数のアプリケーションが動作するVM群(またはコンテナ群)を一括セットアップできるようにすることです。この機能があることをもって、HashiCorpは、Otto は "Built for Microservices" である と標榜しています。

この目標に魅力を感じて、僕も最近少しずつOttoを試しています。ただ、Otto はリリースされたばかりの version 0.1 ということで、この機能(Dependency)はまだ未実装な部分が多いようです。自分の理解のためもあり、現状をまとめてみました。

結論

f:id:muziyoshiz:20151030234318p:plain

AppType = docker のアプリケーションを Dependency に記載した場合、ローカル環境とクラウドサービス(0.1はAWSのみ対応)へのデプロイ結果は以下のようになりました。

  • ローカル環境の構築時(otto dev実行時)

    • ローカル環境で1台のVirtualBox VMが起動され、その上でWebアプリが動作する。
    • このVMにDockerが自動インストールされて、VM上でMongoDBのコンテナが動作する。
    • VMにはConsulもインストールされ、VMからは mongodb.service.consul という名前でMongoDBに接続できる。
  • AWSへのデプロイ時(otto deploy実行時)

    • AWS上で1台のインスタンスが起動され、その上でWebアプリが動作する(インフラ構成=Flavor が "simple" の場合)。
    • このインスタンスにはDockerがインストールされず、従ってMongoDBのコンテナも動作しない。dependencyの設定は単純に無視される?
    • Consulはインストールされる。しかし、現状ではdependencyに関しては意味がない。

動作確認の結果

Getting Started Guide にある例を用いて、動作確認を行いました。以下、動作確認を行った際の手順、および実行結果の詳細です。

動作環境

  • OS: OS X Yosemite バージョン 10.10.5(14F27)
  • Otto: Mac OS X版 Otto 0.1.2

サンプルアプリのダウンロード

サンプルアプリ(Rubyで書かれたWebアプリケーション)をダウンロードします。

happyturn% git clone https://github.com/hashicorp/otto-getting-started.git

ここにはAppfileが含まれないので、App Dependencies にある内容でAppfileを作成します。

application {
  name = "otto-getting-started"
  type = "ruby"

  dependency {
    source = "github.com/hashicorp/otto/examples/mongodb"
  }
}

このmongodbディレクトリには、以下の内容の Appfile と、アプリケーションのIDが入った .ottoid ファイルが置かれています。これは、"mongo:3.0"という名前のdocker imageをダウンロードしてきて、"mongodb"という名前のアプリケーションとして動作させる、という設定です。

application {
    name = "mongodb"
    type = "docker-external"
}

customization "docker" {
    image = "mongo:3.0"
    run_args = "-p 27017:27017"
}

otto/Appfile at master · hashicorp/otto

コンパイル

otto compile コマンドを実行すると、このAppfileをもとに、Vagrant、Terraform、Consulなどの設定ファイルが自動生成されます。生成されたファイルは .otto フォルダ以下に格納されています。この処理を Otto では「コンパイル」と呼んでいます。

Otto 0.1.0 には、source = "github.com/hashicorp/otto/examples/mongodb" を指定するとコンパイルに失敗するというバグがあったのですが、最新版では解決しています。

happyturn% otto compile
==> Loading Appfile...
==> Fetching all Appfile dependencies...
    Fetching dependency: git::https://github.com/hashicorp/otto.git//examples/mongodb
==> Compiling...
    Application:    otto-getting-started (ruby)
    Project:        otto-getting-started
    Infrastructure: aws (simple)

    Compiling infra...
    Compiling foundation: consul
==> Compiling dependency 'mongodb'...
==> Compiling main application...
==> Compilation success!
    This means that Otto is now ready to start a development environment,
    deploy this application, build the supporting infrastructure, and
    more. See the help for more information.

    Supporting files to enable Otto to manage your application from
    development to deployment have been placed in the output directory.
    These files can be manually inspected to determine what Otto will do.

ログから、Dependency 'mongodb' のダウンロードも行われたのが分かります。mongodbのAppfileは .otto/appfile/deps/b80064b1b081e385b5c0cf62e2b2b3f0/Appfile にダウンロードされています。このフォルダ名は、otto-getting-startedとmongodbのいずれのOtto IDとも一致しないのですが、何のIDなのかは分かりません……。

ローカル環境への構築

コンパイル後に otto dev コマンドを実行すると、ローカル環境にこれらのアプリケーションが構築されます。この処理の内部では Vagrant が使われています。以下のログの冒頭4〜5行目にあるように、OttoはVagrantの出力をミラーしているだけのようです。

このログをよく見ると、途中でdockerのインストールや、mongodbコンテナのダウンロードを行っているのがわかります。

happyturn% otto dev
==> Creating local development environment with Vagrant if it doesn't exist...
    Raw Vagrant output will begin streaming in below. Otto does
    not create this output. It is mirrored directly from Vagrant
    while the development environment is being created.

Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'hashicorp/precise64'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'hashicorp/precise64' is up to date...
==> default: Setting the name of the VM: dev_default_1446212147870_20376
==> default: Fixed port collision for 22 => 2222. Now on port 2200.
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
    default: Adapter 2: hostonly
==> default: Forwarding ports...
    default: 22 => 2200 (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2200
    default: SSH username: vagrant
    default: SSH auth method: private key
    default:
    default: Vagrant insecure key detected. Vagrant will automatically replace
    default: this with a newly generated keypair for better security.
    default:
    default: Inserting generated public key within guest...
    default: Removing insecure key from the guest if it's present...
    default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
    default: The guest additions on this VM do not match the installed version of
    default: VirtualBox! In most cases this is fine, but in rare cases it can
    default: prevent things such as shared folders from working properly. If you see
    default: shared folder errors, please make sure the guest additions within the
    default: virtual machine match the version of VirtualBox you have installed on
    default: your host and reload your VM.
    default:
    default: Guest Additions Version: 4.2.0
    default: VirtualBox Version: 4.3
==> default: Configuring and enabling network interfaces...
==> default: Mounting shared folders...
    default: /vagrant => /Users/myoshiz/Otto/otto-getting-started
    default: /otto/foundation-1 => /Users/myoshiz/Otto/otto-getting-started/.otto/compiled/app/foundation-consul/app-dev
    default: /otto/foundation-mongodb-1 => /Users/myoshiz/Otto/otto-getting-started/.otto/compiled/dep-24cbbebc-fc41-53b4-c844-60d642fb523c/foundation-consul/app-dev-dep
    default: /otto-deps/mongodb-24cbbebc-fc41-53b4-c844-60d642fb523c => /Users/myoshiz/Otto/otto-getting-started/.otto/appfile/deps/b80064b1b081e385b5c0cf62e2b2b3f0
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: stdin: is not a tty
==> default: [otto] Installing Consul...
==> default: [otto] Installing dnsmasq for Consul...
==> default: [otto] Configuring consul service: otto-getting-started
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: stdin: is not a tty
==> default: Running provisioner: docker...
    default: Installing Docker (latest) onto machine...
==> default: Starting Docker containers...
==> default: -- Container: mongodb
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: stdin: is not a tty
==> default: [otto] Configuring consul service: mongodb
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: stdin: is not a tty
==> default: [otto] Setting locale to en_US.UTF-8...
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: [otto] Adding apt repositories and updating...
==> default: [otto] Installing Ruby 2.2 and supporting packages...
==> default: [otto] Configuring Ruby environment...
==> default: [otto] Installing Bundler...
==> default: [otto] Bundling gem dependencies...
==> default: [otto] Configuring Git to use SSH instead of HTTP so we can agent-forward private repo auth...

==> Caching SSH credentials from Vagrant...
==> Development environment successfully created!
    IP address: 100.115.125.105

    A development environment has been created for writing a generic
    Ruby-based app.

    Ruby is pre-installed. To work on your project, edit files locally on your
    own machine. The file changes will be synced to the development environment.

    When you're ready to build your project, run 'otto dev ssh' to enter
    the development environment. You'll be placed directly into the working
    directory where you can run 'bundle' and 'ruby' as you normally would.

    You can access any running web application using the IP above.

ローカル環境への構築結果の確認

これで、1台のVMが起動し、そのうえでRubyアプリケーションと、MongoDBが動作するコンテナが動くようになりました。VMが1台しか動いていないことは、以下のコマンドで確認できます。ちなみに、otto dev vagrant [subcommand] で、Vagrantの任意のサブコマンドを実行できます。このように、ローカル環境に関しては、OttoはVagrantの単なるラッパーです。

happyturn% otto dev vagrant status
==> Executing: 'vagrant status'
Current machine states:

default                   running (virtualbox)

otto dev ssh コマンドで、いま起動したVMにログインできます。

happyturn% otto dev ssh
==> Executing SSH. This may take a few seconds...
Welcome to Ubuntu 12.04 LTS (GNU/Linux 3.2.0-23-generic x86_64)

 * Documentation:  https://help.ubuntu.com/
New release '14.04.3 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Welcome to your Vagrant-built virtual machine.
Last login: Fri Sep 14 06:23:18 2012 from 10.0.2.2

dockerコマンドがすでにインストールされているので、以下のようにコンテナ上でMongoDBが動いていることを確認できます。

vagrant@precise64:/vagrant$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
mongo               3.0                 c08c92f4cb13        6 days ago          261.6 MB

vagrant@precise64:/vagrant$ docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                      NAMES
a49699c16bfc        mongo:3.0           "/entrypoint.sh mong   9 minutes ago       Up 9 minutes        0.0.0.0:27017->27017/tcp   mongodb

vagrant@precise64:/vagrant$ docker exec -it a496 bash
<8d0520a79fe446e0# ps -ef | grep mongod
vboxadd   5939  5667  2 13:37 ?        00:00:18 mongod
root     10144 10128  0 13:49 pts/2    00:00:00 grep --color=auto mongod

Consulの動作も、以下のように確認できます。Consulの設定が自動で行われており、mongodb.service.consul という名前でMongoDBに接続できるようになっています。

vagrant@precise64:/vagrant$ consul members
Node       Address         Status  Type    Build  Protocol  DC
precise64  10.0.2.15:8301  alive   server  0.5.2  2         dc1

vagrant@precise64:/vagrant$ curl http://mongodb.service.consul:27017
It looks like you are trying to access MongoDB over HTTP on the native driver port.

当然ではありますが、Install MongoDB on Ubuntu — MongoDB Manual 3.0 の手順に従ってVM上にmongo shellをインストールすれば、以下のコマンドで、コンテナ上で動作するMongoDBサーバに接続できました。

vagrant@precise64:/vagrant$ mongo mongodb.service.consul:27017/test
MongoDB shell version: 3.0.7
connecting to: mongodb.service.consul:27017/test
>

2015-11-24追記

consul membersの出力にあるように、Consul AgentはVM上の1台しか動作していません。コンテナ上にはConsul Agentはありません。

しかし、以下の2点により、結果的にmongodb.service.consul:27017でMongoDBに接続できるようになっています。

  • Appfileに書かれた設定により、ローカルホストの27017番への通信がコンテナに転送されること。この転送設定は、docker psの出力からも確認できる。
  • Ottoが自動生成したprovisionのスクリプトにより、VM上の/etc/consul.d/service.mongodb.jsonに「MongoDBはローカルホストにある」という設定が書き込まれること。以下に、このjsonファイルの内容を示す。
{
  "service": {
    "name": "mongodb",
    "tags": [],
    "port": 0
  }
}

AWSへのデプロイ

AWSへアプリケーションをデプロイする際は、事前にotto infraコマンドと、otto buildコマンドを実行する必要があります。内部的には前者はTerraform、後者はPackerが使われています。今回の本題ではないので、詳細は割愛。

  • otto infra: 複数アプリケーションの共通基盤となるインスタンスを、AWSにデプロイ
  • otto build: デプロイ対象のアプリを含むAMIを作成(将来的にはコンテナ作成なども対応予定とのこと)

以下は、otto buildeのログ出力の例です。よく見てみると、otto devのときと違い、ログにdockerやmongodb関係のメッセージが出ていないのがわかります。つまり、AMIの作成時点で、dependencyの情報が無視されてしまっているようです。

happyturn% otto build
==> Detecting infrastructure credentials for: otto-getting-started (aws)
    Cached and encrypted infrastructure credentials found.
    Otto will now ask you for the password to decrypt these
    credentials.

Encrypted Credentials Password
  Infrastructure credentials are required for this operation. Otto found
  saved credentials that are password protected. Please enter the password
  to decrypt these credentials. You may also just hit <enter> and leave
  the password blank to force Otto to ask for the credentials again.

  Enter a value:
==> Querying infrastructure data for build...
==> Building deployment archive...
==> Building deployment artifact with Packer...
    Raw Packer output will begin streaming in below. Otto
    does not create this output. It is mirrored directly from
    Packer while the build is being run.

otto output will be in this color.

==> otto: Prevalidating AMI Name...
==> otto: Inspecting the source AMI...
==> otto: Creating temporary keypair: packer 563377c3-5671-2d2f-a472-67471e05270c
==> otto: Creating temporary security group for this instance...
==> otto: Authorizing access to port 22 the temporary security group...
==> otto: Launching a source AWS instance...
    otto: Instance ID: i-665669d9
==> otto: Waiting for instance (i-665669d9) to become ready...
==> otto: Waiting for SSH to become available...
==> otto: Connected to SSH!
==> otto: Provisioning with shell script: /var/folders/4s/c_r4lqx92wnd6ml3tz8q6mm80000gn/T/packer-shell148828306
==> otto: Uploading /Users/myoshiz/Otto/otto-getting-started/.otto/compiled/app/foundation-consul/app-build/ => /tmp/otto/foundation-1
==> otto: Provisioning with shell script: /var/folders/4s/c_r4lqx92wnd6ml3tz8q6mm80000gn/T/packer-shell567335967
    otto: [otto] Installing Consul...
    otto: [otto] Installing dnsmasq for Consul...
    otto: [otto] Configuring consul service: otto-getting-started
==> otto: Uploading /var/folders/4s/c_r4lqx92wnd6ml3tz8q6mm80000gn/T/otto-slug-758424784 => /tmp/otto-app.tgz
==> otto: Provisioning with shell script: build-ruby.sh
    otto: [otto] Waiting for cloud-config to complete...
    otto: [otto] Adding apt repositories and updating...
    otto: [otto] Installing Ruby, Passenger, Nginx, and other packages...
    otto: [otto] Installing Bundler...
    otto: [otto] Extracting app...
    otto: [otto] Adding application user...
    otto: [otto] Setting permissions...
    otto: [otto] Configuring nginx...
    otto: [otto] Bundle installing the app...
    otto: Fetching gem metadata from https://rubygems.org/..........
    otto: Fetching version metadata from https://rubygems.org/..
    otto: Installing rack 1.6.4
    otto: Installing rack-protection 1.5.3
    otto: Installing tilt 2.0.1
    otto: Installing sinatra 1.4.6
    otto: Using bundler 1.10.6
    otto: Bundle complete! 1 Gemfile dependency, 5 gems now installed.
    otto: Gems in the groups development and test were not installed.
    otto: Bundled gems are installed into ./vendor/bundle.
    otto: [otto] ...done!
==> otto: Stopping the source instance...
==> otto: Waiting for the instance to stop...
==> otto: Creating the AMI: otto-getting-started 1446213571
    otto: AMI: ami-c03e4daa
==> otto: Waiting for AMI to become ready...
==> otto: Terminating the source AWS instance...
==> otto: Cleaning up any extra volumes...
==> otto: No volumes to clean up, skipping
==> otto: Deleting temporary security group...
==> otto: Deleting temporary keypair...
Build 'otto' finished.

==> Builds finished. The artifacts of successful builds are:
--> otto: AMIs were created:

us-east-1: ami-c03e4daa
==> Storing build data in directory...
==> Build success!
    The build was completed successfully and stored within
    the directory service, meaning other members of your team
    don't need to rebuild this same version and can deploy it
    immediately.

最後に、otto deployコマンドを実行すると、ここで作ったAMIをもとに、インスタンスが作成されます。

happyturn% otto deploy
==> Detecting infrastructure credentials for: otto-getting-started (aws)
    Cached and encrypted infrastructure credentials found.
    Otto will now ask you for the password to decrypt these
    credentials.

Encrypted Credentials Password
  Infrastructure credentials are required for this operation. Otto found
  saved credentials that are password protected. Please enter the password
  to decrypt these credentials. You may also just hit <enter> and leave
  the password blank to force Otto to ask for the credentials again.

  Enter a value:
aws_security_group.app: Creating...
  description:                         "" => "Managed by Terraform"
  egress.#:                            "" => "1"
  egress.482069346.cidr_blocks.#:      "" => "1"
  egress.482069346.cidr_blocks.0:      "" => "0.0.0.0/0"
  egress.482069346.from_port:          "" => "0"
  egress.482069346.protocol:           "" => "-1"
  egress.482069346.security_groups.#:  "" => "0"
  egress.482069346.self:               "" => "0"
  egress.482069346.to_port:            "" => "0"
  ingress.#:                           "" => "1"
  ingress.482069346.cidr_blocks.#:     "" => "1"
  ingress.482069346.cidr_blocks.0:     "" => "0.0.0.0/0"
  ingress.482069346.from_port:         "" => "0"
  ingress.482069346.protocol:          "" => "-1"
  ingress.482069346.security_groups.#: "" => "0"
  ingress.482069346.self:              "" => "0"
  ingress.482069346.to_port:           "" => "0"
  name:                                "" => "otto-getting-started-571aac33"
  owner_id:                            "" => "<computed>"
  vpc_id:                              "" => "vpc-571aac33"
aws_security_group.app: Creation complete
aws_instance.app: Creating...
  ami:                               "" => "ami-c03e4daa"
  availability_zone:                 "" => "<computed>"
  ebs_block_device.#:                "" => "<computed>"
  ephemeral_block_device.#:          "" => "<computed>"
  instance_type:                     "" => "t2.micro"
  key_name:                          "" => "otto-571aac33"
  placement_group:                   "" => "<computed>"
  private_dns:                       "" => "<computed>"
  private_ip:                        "" => "<computed>"
  public_dns:                        "" => "<computed>"
  public_ip:                         "" => "<computed>"
  root_block_device.#:               "" => "<computed>"
  security_groups.#:                 "" => "<computed>"
  source_dest_check:                 "" => "1"
  subnet_id:                         "" => "subnet-9b6163c2"
  tags.#:                            "" => "1"
  tags.Name:                         "" => "otto-getting-started"
  tenancy:                           "" => "<computed>"
  vpc_security_group_ids.#:          "" => "1"
  vpc_security_group_ids.1716715879: "" => "sg-fd872d9b"
aws_instance.app: Creation complete

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: /var/folders/4s/c_r4lqx92wnd6ml3tz8q6mm80000gn/T/otto-tf083753006/state

Outputs:

  url = http://ec2-52-91-29-204.compute-1.amazonaws.com/

AWSへのデプロイ結果の確認

起動したインスタンスへは、ubuntuユーザでSSH接続できます。AWSへの最初の接続時に、Ottoが、SSH接続に使う証明書を尋ねてくるので、そこで正しく設定していれば以下のコマンドで接続できるはずです。

happyturn% ssh ubuntu@52.91.29.204
Welcome to Ubuntu 14.04.3 LTS (GNU/Linux 3.13.0-63-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

  System information as of Fri Oct 30 14:15:28 UTC 2015

  System load: 0.39              Memory usage: 5%   Processes:       80
  Usage of /:  15.9% of 7.74GB   Swap usage:   0%   Users logged in: 0

  Graph this data and manage this system at:
    https://landscape.canonical.com/

  Get cloud support with Ubuntu Advantage Cloud Guest:
    http://www.ubuntu.com/business/services/cloud

ローカル環境の場合と違い、dockerコマンドはインストールされていません。

ubuntu@ip-10-0-2-222:~$ which docker
ubuntu@ip-10-0-2-222:~$

Consulはインストールされています。ただし、依存関係にあるアプリケーションが動作していないので、現時点ではあまり意味はありません。

ubuntu@ip-10-0-2-222:~$ consul members
Node           Address          Status  Type    Build  Protocol  DC
ip-10-0-2-222  10.0.2.222:8301  alive   client  0.5.2  2         dc1
ip-10-0-2-6    10.0.2.6:8301    alive   server  0.5.2  2         dc1

余談ですが、Rubyアプリケーションは /srv/otto-app 以下にインストールされていました。

ubuntu@ip-10-0-2-222:~$ ls -la /srv/otto-app/
total 40
drwxr-xr-x 5 otto-app otto-app 4096 Oct 30 14:03 .
drwxr-xr-x 3 root     root     4096 Oct 30 14:03 ..
-rw-r--r-- 1 otto-app otto-app  307 Oct 30 13:24 app.rb
drwxr-xr-x 2 otto-app otto-app 4096 Oct 30 14:03 .bundle
-rw-r--r-- 1 otto-app otto-app   42 Oct 30 13:24 config.ru
-rw-r--r-- 1 otto-app otto-app   57 Oct 30 13:24 Gemfile
-rw-r--r-- 1 otto-app otto-app  267 Oct 30 13:24 Gemfile.lock
-rw-r--r-- 1 otto-app otto-app  160 Oct 30 13:24 README.md
drwxr-xr-x 3 otto-app otto-app 4096 Oct 30 14:03 vendor
drwxr-xr-x 2 otto-app otto-app 4096 Oct 30 13:24 views

最後に

OttoのDependencyの機能はまだまだ開発途上であることが、今回の解説でわかって頂けたかと思います。ただ、Ottoの概念自体は、一度理解してしまえばわかりやすいですし、開発環境と本番環境を一元管理できれば確かに理想的ではあります。個人的には、今後しばらくOttoに注目していきたいと思っています。