Nginxのモジュールの作り方 C言語でHandlers編

概要

モジュールの種類

Nginxで開発できるmoduleの種類は、ざっくりとわけると以下の3種類があります。

  • Handlers・・・locationを指定して処理が可能
  • Load balancers・・・upstream moduleやload balancer moduleと連携して使う。load balancer moduleにはRound RobinとIP Hashがbuild-inされているけど、それ以外の分散方法として使うことが出来る
  • Filters・・・Handlerのレスポンスに対して処理を行う。例えばgzip filterはresponseをgzip圧縮する。

今回対象とするのはHandlersをC言語で実装することにします。

必要となるファイル

特定パスにアクセスしたら『hello world』と出力するようなモジュールを作ってみます。
stub_status moduleのイケてない版のようのモジュールです。
nginxのモジュールを実装するには、最低限2つのファイルが必要になります。

  • config
  • ngx_http_hello_module.c

configファイルにはモジュールの概要を書き、モジュール実態はngx_http_hello_module.cになります。

動作イメージ

configファイルのserverディテクティブの中に以下のように記載すると、http://localhost/hello/ にアクセスするとHello WorldをHTTP Responseとして返す。

location /hello/ {
   hello 'Hello World';
} 

実装

config

configファイルにはモジュールの概要や説明などを書きます。

ngx_addon_name=ngx_http_hello_module
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_module.c"

ngx_addon_nameにはモジュールの名前を書きます。
HTTP_MODULESには依存するモジュールの名前を書きます。
NGIX_ADDON_SRCSにはモジュールのパスを書きます。

HTTP_MODULESの他にどんなModuleがあるかは、auto/modulesのスクリプトを見るとわかります。
1.9.2では以下のように書かれています。

459     modules="$modules $HTTP_MODULES $HTTP_FILTER_MODULES \
460              $HTTP_HEADERS_FILTER_MODULE \
461              $HTTP_AUX_FILTER_MODULES \
462              $HTTP_COPY_FILTER_MODULE \
463              $HTTP_RANGE_BODY_FILTER_MODULE \
464              $HTTP_NOT_MODIFIED_FILTER_MODULE"

module.c

コンフィグファイルの定義

NginxのConfigファイルのどのディテクティブ(main, server, location)で、このモジュールの設定が定義出来るか決めます。
ngx_http<モジュール名>(main|srv|loc)_conf_t という構造体で、ディレクティブの設定値を保存する構造体を定義します。

その他のngx_http_core_loc_conf_tなどの定義は、src/core/ngx_config.h にある。
ngx_str_t のような型は、src/core/nginx_config.h にある。

typedef struct {
  ngx_str_t   name;
} ngx_http_hello_loc_conf_t;

ディテクティブに具体的にどんな内容を書くかは以下のように定義します。

static ngx_conf_post_handler_pt ngx_http_hello_p = ngx_http_hello;

static ngx_command_t ngx_http_hello_commands[] = {
  { 
    ngx_string("hello"),
    NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
    ngx_conf_set_str_slot,
    NGX_HTTP_LOC_CONF_OFFSET,
    offsetof(ngx_http_hello_loc_conf_t, name),
    &ngx_http_hello_p 
  },
  ngx_null_command
};

このモジュールを利用するには、コンフィグファイル内にhelloというKeyを定義するのが一番最初の『ngx_string("hello")』です。
次の『NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1』は、HTTPのLocationディテクティブで利用し、"hello"が引数を1つ取るということです。
引数の個数についてはsrc/core/ngx_conf_file.h に、コンフィグファイルのどのディテクティブに定義を表するかは src/http/ngx_http_config.h に定義されています。
3番目の『ngx_conf_set_str_slot』引数を取る場合の型を定義するお作法として、関数ポインタを記載します。ここで記載出来る関数名はsrc/core/ngx_conf_file.h の328行目から340行目までに定義されています。
4番目の 『NGX_HTTP_LOC_CONF_OFFSET』はLocationディテクティブの時にはこれを設定します。src/http/ngx_http_config.hの50〜52にsrv、mainの時に設定する関数が定義されています。
5番目では、ngx_http_hello_loc_conf_t->nameに設定
ngx_null_commandは、配列の最後を表します。モジュールとしてhello以外Keyが存在する場合は、ngx_null_commandの前に同様の定義をします。

module contextの設定

ngx_httpmodule_ctxという名前で、下記のコメントのようなコンフィグに対する処理を定義します。
ngx_http_hello_create_loc_conf関数で、コンフィグの初期化をしていますが、複数のlocationに対応するような場合にはngx_httpmerge_loc_confというような名前にする必要があります。

static ngx_http_module_t ngx_http_hello_module_ctx = {
  NULL,                          /* pre configuration */
  NULL,                          /* post configuration */
  NULL,                          /* create main configuration */
  NULL,                          /* init main configuration */
  NULL,                          /* create server configuration */
  NULL,                          /* merge server configuration */
  ngx_http_hello_create_loc_conf, /* create location configuration */
  NULL                           /* merge location configuration */
};

/* The function which initializes memory for the module configuration structure
*/
static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
{
  ngx_http_hello_loc_conf_t  *conf;
  conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
  if (conf == NULL) {
   return NULL;
  }
  return conf;
}

moduleの定義

NGX_MODULE_V1とNGX_MODULE_V1_PADDINGで挟んだ内容がモジュールの定義(ngx_module_t)になります。
それぞれの処理に合わせてコールバック処理登録を登録します。

/*
 * The module which binds the context and commands
 *
 */
ngx_module_t ngx_http_hello_module = {
  NGX_MODULE_V1,
     &ngx_http_hello_module_ctx,    /* module context */
     ngx_http_hello_commands,       /* module directives */
     NGX_HTTP_MODULE,               /* module type(CORE, MAIL, EVENT, ...) */
     NULL,                          /* init master */
     NULL,                          /* init module */
     NULL,                          /* init process */
     NULL,                          /* init thread */
     NULL,                          /* exit thread */
     NULL,                          /* exit process */
     NULL,                          /* exit master */
     NGX_MODULE_V1_PADDING
};

Handler

Handlerでは、リクエストを受けてレスポンスか返すという実質的な処理を実装します。

/*
 * Main handler function of the module.
 */
static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r)
{
  ngx_int_t    rc;
  ngx_buf_t   *b;
  ngx_chain_t  out;
  /* we response to 'GET' and 'HEAD' requests only */
  if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
    return NGX_HTTP_NOT_ALLOWED;
  }

  /* discard request body, since we don't need it here */
  rc = ngx_http_discard_request_body(r);
  if (rc != NGX_OK) {
    return rc;
  }

  /* set the 'Content-type' header */
  r->headers_out.content_type_len = sizeof("text/html") - 1;
  r->headers_out.content_type.data = (u_char *) "text/html";
  /* send the header only, if the request type is http 'HEAD' */
  if (r->method == NGX_HTTP_HEAD) {
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = hello_string.len;

    return ngx_http_send_header(r);
  }
  /* allocate a buffer for your response body */
  b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
  if (b == NULL) {
    return NGX_HTTP_INTERNAL_SERVER_ERROR;
  }
  /* attach this buffer to the buffer chain */
  out.buf = b;
  out.next = NULL;

  /* adjust the pointers of the buffer */
  b->pos = hello_string.data;
  b->last = hello_string.data + hello_string.len;
  b->memory = 1;    /* this buffer is in memory */
  b->last_buf = 1;  /* this is the last buffer in the buffer chain */
  /* set the status line */
  r->headers_out.status = NGX_HTTP_OK;
  r->headers_out.content_length_n = hello_string.len;
  /* send the headers of your response */
  rc = ngx_http_send_header(r);
  if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
    return rc;
  }

  /* send the buffer chain of your response */
  return ngx_http_output_filter(r, &out);
}

ビルドしてみる

モジュールのソースコードを配備します。

~# mkdir /usr/local/src/ngx_http_hello_module
~# ls /usr/local/src/ngx_http_hello_module
config  ngx_http_hello_module.c

nginx-buildの0.4.0以降を使ってビルドします。

~# useradd --shell /sbin/nologin nginx
~# apt-get update
~# apt-get install gcc make
~# wget https://github.com/cubicdaiya/nginx-build/releases/download/v0.4.1/nginx-build-linux-amd64-0.4.1.tar.gz
~# tar xvfz nginx-build-linux-amd64-0.4.1.tar.gz
~# chmod +x nginx-build 
~# ./nginx-build -d work -pcre -zlib -openssl -verbose --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --pid-path=/var/run/nginx.pid --lock-path=/var/lock/nginx.lock --http-log-path=/var/log/nginx/access.log --with-http_stub_status_module --with-debug --add-module=/usr/local/src/ngx_http_hello_module
~# cd work/1.9.2/nginx-1.9.2/
~/work/1.9.2/nginx-1.9.2# make install
~#  /usr/sbin/nginx -c /etc/nginx/nginx.conf

ソースコードhideji/ngx_http_hello_module · GitHubにあります。

起動スクリプトを利用る場合はJasonGiedymin/nginx-init-ubuntu · GitHubを参照してください。

参考資料

簡単なHandlerの作り方は以下が参考になります。

TerraformをDigital Oceanで試してみる3 - provisionerにchefを使う

Terraformのprovisionerとしてchefを使うにはChefサーバが必要になります。
Chefサーバの準備が面倒な場合は、Hosted Chefで代用できます。Hosted Chefの設定については前回の記事が参考にしてください。

Chef Clientのインストール

provisionerにChefを指定したときに、skip_installをtrueにしなければ(デフォルトではfalse)Chef Clientをインストールできますが、remote-execを利用してChef Clientをインストールします。
inlineに実行したいコマンドを指定するので、ここではOmnibus Installerを実行します。
remote-execではConnectionを指定してSSHで接続出来るようにします。

    provisioner "remote-exec" {
        inline = [
        "curl -L https://www.chef.io/chef/install.sh | sudo bash"
        ]

        connection {
            user = "root"
        type = "ssh"
            key_file = "${var.ssh_key_file}"
    }
    }

key_fileで指定する鍵ファイルはdecryptされている必要があります。decryptしていない場合は以下の様なエラーがでます。

digitalocean_droplet.node-1: Provisioning with 'remote-exec'...
digitalocean_droplet.node-1: Error: 1 error(s) occurred:

* Failed to read key '/Users/username/.ssh/id_rsa': password protected keys are
not supported. Please decrypt the key prior to use.
Error applying plan:

1 error(s) occurred:

* 1 error(s) occurred:

* 1 error(s) occurred:

* Failed to read key '/Users/username/.ssh/id_rsa': password protected keys are
not supported. Please decrypt the key prior to use.

Terraform does not automatically rollback in the face of errors.
Instead, your Terraform state file has been partially updated with
any resources that successfully completed. Please address the error
above and apply again to incrementally change your infrastructure.

その場合は以下のコマンドでdecryptします。

openssl rsa -in <Encrypted key filename>  -out < desired output file name>

Chefの実行

provisionerとしてchefを利用します。
Chef Serverを使う必要が有るため事前にChef Serverを利用できるようにする必要があります。
ここでは、CookBookとしてgetting-startedを指定しています。
getting-startedを使う場合はattributesは利用しないので設定不要ですが、サンプルとして記述しています。 また、Terraform v0.5.2ではchef provisionerはterraform.tfvarsに定義した値を使えない※ようなので直接値を書きます。 ※変数名が定義した文字列にならず、変数名のままchef-clientに渡っているようなエラーがでます。

    provisioner "chef"  {
        attributes {
            "key" = "value"
            "app" {
                "cluster1" {
                    "nodes" = ["webserver1", "webserver2"]
                }
            }
        }
        environment = "_default"
        run_list = ["getting-started"]
        node_name = "node-1"
        server_url = "https://api.opscode.com/organizations/"
        validation_client_name = "Organazation名-validator"
        validation_key_path = "./Organazation名-validator.pem"
        skip_install = true
 
        connection {
            user = "root"
            type = "ssh"
            key_file = "${var.ssh_key_file}"
        }
    }

注意すべき点としては、destroyしてもChef Server側に登録されたnode(node_name)は削除されないので別途削除する必要があります。
作成されたnodeを削除しないでterraform applyを再度実行した場合は、以下の様なエラーが表示されます。

ocean_droplet.node-1 (chef): Creating a new client identity for node-1 using the validator key.
digitalocean_droplet.node-1 (chef): 
digitalocean_droplet.node-1 (chef): ================================================================================
digitalocean_droplet.node-1 (chef): Chef encountered an error attempting to create the client "node-1"
digitalocean_droplet.node-1 (chef): ================================================================================

digitalocean_droplet.node-1 (chef): Authorization Error:
digitalocean_droplet.node-1 (chef): --------------------
digitalocean_droplet.node-1 (chef): Your validation client is not authorized to create the client for this node (HTTP 403).
digitalocean_droplet.node-1 (chef): 
digitalocean_droplet.node-1 (chef): Possible Causes:
digitalocean_droplet.node-1 (chef): ----------------
digitalocean_droplet.node-1 (chef): * There may already be a client named "node-1"
digitalocean_droplet.node-1 (chef): * Your validation client (Organization名-validator) may have misconfigured authorization permissions.
digitalocean_droplet.node-1 (chef): 
digitalocean_droplet.node-1 (chef): [2015-06-13T07:26:44-04:00] FATAL: Stacktrace dumped to /var/chef/cache/chef-stacktrace.out
digitalocean_droplet.node-1 (chef): Chef Client failed. 0 resources updated in 3.789611695 seconds
digitalocean_droplet.node-1 (chef): [2015-06-13T07:26:44-04:00] ERROR: 403 "Forbidden"
digitalocean_droplet.node-1 (chef): [2015-06-13T07:26:44-04:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)
digitalocean_droplet.node-1: Error: 1 error(s) occurred:

* Command "chef-client -j \"/etc/chef/first-boot.json\" -E \"_default\"" exited with non-zero exit status: 1
Error applying plan:

1 error(s) occurred:

* 1 error(s) occurred:

* 1 error(s) occurred:

* Command "chef-client -j \"/etc/chef/first-boot.json\" -E \"_default\"" exited with non-zero exit status: 1

Terraform does not automatically rollback in the face of errors.
Instead, your Terraform state file has been partially updated with
any resources that successfully completed. Please address the error
above and apply again to incrementally change your infrastructure.

do.tfファイル、variables.tf

最終的な do.tfは以下のようになります

provider "digitalocean" {
      token = "${var.digitalocean_token}"
}

resource "digitalocean_droplet" "node-1" {
     image = "ubuntu-14-04-x64"
     name = "node-1"
     region = "sgp1"
     size = "512mb"
     ssh_keys = [${var.ssh_key_id}]

    provisioner "remote-exec" {
        inline = [
        "curl -L https://www.chef.io/chef/install.sh | sudo bash"
        ]

        connection {
            user = "root"
            type = "ssh"
            key_file = "${var.ssh_key_file}"
        }
    }

    provisioner "chef"  {
        attributes {
            "key" = "value"
            "app" {
                "cluster1" {
                    "nodes" = ["webserver1", "webserver2"]
                }
            }
        }

        environment = "_default"
        run_list = ["getting-started"]
        node_name = "node-1"
        server_url = "https://api.opscode.com/organizations/"
        validation_client_name = "Organazation名-validator"
        validation_key_path = "./Organazation名-validator.pem"
        skip_install = true
 
        connection {
            user = "root"
            type = "ssh"
            key_file = "${var.ssh_key_file}"
        }
    }
}

variables.tfとterraform.tfvarsは前回から変更は無いです。

Hosted Chefを使ってみる

サーバがインターネットに接続可能でChefサーバを建てるまではないけど、少しChefサーバを使ってみたいというようなときにHosted Chefが便利なので使ってみました。
Chef 12のオープンソース版と25ノード制限について を見ると、5ノードまでは問題なく使えるようなのでちょっとした確認には使うことが出来ます。
アカウントが無い場合はStart your free trial of hosted Chef から作ることが出来ます。
以降はログインが完了している前提で記載しています。

HOSTED CHEFでの設定

Administration -> Organazationを選択します。

f:id:clavier:20150611231433p:plain

knife.rbの作成

Generate Knife Configを選択すると、kinife.rbが ダウンロード出来ます。

validation_keyのダウンロード

Reset Validation Keyを選択すると、kinife.rbのvalidation_keyにOrganazation名書かれているpemファイル(Organazation名-validator.pem)がダウンロード出来ます。

client_keyのダウンロード

Account Management画面でPassword and Keyのタブを選択して、 Get a New Keyを選択するとpemファイル(ユーザ名.pem)がダウンロード出来ます。

WORKSTATIONでの作業

Starter Kitのダウンロード

CookBookのアプロードやRoleの作成などをWorkStationとして実施するための環境を整えるためにStarter Kitをダウンロードします。 左側のメニューに有るStarter Kitを選択するとダウンロード出来ます。 settings ディレクトリを作成して、kinife.rbとvalidation_key、client_keyを移動します。

これらを実行すると以下の様な構成になります。

.
├── README.md
├── settings
│   ├── Organazation名-validator.pem
│   ├── ユーザ名.pem
│   └── knife.rb
├── cookbooks
│   ├── chefignore
│   └── starter
│       ├── attributes
│       │   └── default.rb
│       ├── files
│       │   └── default
│       │       └── sample.txt
│       ├── metadata.rb
│       ├── recipes
│       │   └── default.rb
│       └── templates
│           └── default
│               └── sample.erb
└── roles
    └── starter.rb

コミュニティCookBookの追加

この状態でsettingsディレクトリでkinife listコマンドを実行する以下のように表示されます。

$ knife client list

Organazation名-validator

chef-repoをgitの管理下にします。

chef-repo$ git init .
chef-repo/.git/

chef-repo$ git add .
chef-repo$ git commit -m "Initial commit"
 11 files changed, 230 insertions(+)
 create mode 100644 README.md
 create mode 100644 settings/Organazation名-validator.pem
 create mode 100644 settings/ユーザ名.pem
 create mode 100644 settings/knife.rb
 create mode 100644 cookbooks/chefignore
 create mode 100644 cookbooks/starter/attributes/default.rb
 create mode 100644 cookbooks/starter/files/default/sample.txt
 create mode 100644 cookbooks/starter/metadata.rb
 create mode 100644 cookbooks/starter/recipes/default.rb
 create mode 100644 cookbooks/starter/templates/default/sample.erb
 create mode 100644 roles/starter.rb

コミュニティCookbookのgetting-startedを追加します。

chef-repo$cd settings
chef-repo/settings$ knife cookbook site install getting-started
  ...省略...
Cookbook getting-started version 0.4.0 successfully installed

Cookbookのアップロード

Cookbookをアップロードします。

chef-repo/settings$ knife cookbook upload --all
ERROR: Chef::Exceptions::MetadataNotValid: Cookbook loaded at path(s) [/home/clavier/Dev/chef/chef-repo/cookbooks/getting-started] has invalid metadata: The `name' attribute is required in cookbook metadata

meatadata.rbやmetadata.jsonにnameの項目がないので、以下の内容を追加します。

chef-repo/settings$ vim ../cookbooks/getting-started/metadata.rb

name 'getting-started'

再度、Cookbookをアップロードします。

chef-repo/settings$ knife cookbook upload --all
Uploading getting-started [0.4.0]
Uploading starter      [1.0.0]
Uploaded all cookbooks.

アップロードが完了したCookbookは Policy -> Cookbooks を選択することで確認出来ます。

f:id:clavier:20150611231458p:plain

TerraformをDigital Oceanで試してみる2 環境情報の外出し

前回では、do.tfファイルの中に全ての情報を書き込みました。
それではtoken情報やssh keyの値などはgitなどで管理しづらいので、ssh_keysとtokenを別のファイルで管理します。
ファイルを外出しするようになると、以下の様な構成になります。

.
├── do.tf
├── terraform.tfvars
└── variables.tf

この構成になるとterraform.tfvarsにtoken情報やssh keyの値を書くことで、このファイルのみをコミット対象外するとこでgitなどで問題無く扱えるようになります。

variables.tf

terraform.tfvarsで宣言する変数を宣言するためのファイルです。
do.tfの中で宣言しても、問題無いですが別ファイルで管理します。ファイルの内容はInput Variablesにあるように、書いていきます。

variable digitalocean_token {}
variable ssh_key_id {}

terraform.tfvars

このファイルでは、変数と値を定義します。
terraform.tfvarsファイルという名前にしておくと、上記のような構成にしておくと-var-fileオプションを付けなくても自動的にTerraformが読み込んでくれます。

dgitalocean_token = "前回作成したtoken"
ssh_key_id = "512189"

do.tf

前回作成したdo.tfのtokenとssh_keysの値を、variables.tfで定義した変数に書き換えます。

provider "digitalocean" {
      token = "${var.digitalocean_token}"
}

resource "digitalocean_droplet" "node-1" {
     image = "ubuntu-14-04-x64"
     name = "node-1"
     region = "sgp1"
     size = "512mb"
     ssh_keys = [${var.ssh_key_id}]
 }

これで、terraform plan や terraform applyの引数は変更なく実行出来ます。

TerraformをDigital Oceanで試してみる1

Digital Oceanの情報取得

Terraform を Digital Ocean で触ってみた (初級編) を参考にDigital Ocean のtokenを作成します。

Degital OceanのAPI V2を利用して、curlからSSH鍵の情報を取得します。
参考:https://developers.digitalocean.com/documentation/v2/#ssh-keys

# curl -X GET -H 'Content-Type: application/json' -H 'Authorization: Bearer 先ほど作成したtokenを記載' "https://api.digitalocean.com/v2/account/keys"

{
  "ssh_keys": [
    {
      "id": 512189,
      "fingerprint": "3b:16:bf:e4:8b:00:8b:b8:59:8c:a9:d3:f0:19:45:fa",
      "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDDHr/jh2Jy4yALcK4JyWbVkPRaWmhck3IgCoeOO3z1e2dBowLh64QAM+Qb72pxekALga2oi4GvT+TlWNhzPH4V example",
      "name": "My SSH Public Key"
    }
  ],
  "links": {
  },
  "meta": {
    "total": 1
  }
}

ここで返ってきたidとDegital Oceanのtokenを次で作成する.tfファイルで利用します。

.tfファイルの作成

terraformでは、.tfファイルを作成しそこから環境を作成出来ます。
ここで、今まで行った作業を元にDegital Ocean用のdo.tfファイルを作成してみます。

provider "digitalocean" {
      token = "作成したtokenを記載"
}

resource "digitalocean_droplet" "node-1" {
     image = "ubuntu-14-04-x64"
     name = "node-1"
     region = "sgp1"
     size = "512mb"
     ssh_keys = [512189]
 }

dropletの作成

do.tfを利用して、dropletを作成します。 planのみでは実際にdropletは作成されず、applyまで実行することでdropletが作成されます。

$ terraform plan
Refreshing Terraform state prior to plan...


The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ digitalocean_droplet.node-1
    image:                "" => "ubuntu-14-04-x64"
    ipv4_address:         "" => "<computed>"
    ipv4_address_private: "" => "<computed>"
    ipv6_address:         "" => "<computed>"
    ipv6_address_private: "" => "<computed>"
    locked:               "" => "<computed>"
    name:                 "" => "node-1"
    region:               "" => "sgp1"
    size:                 "" => "512mb"
    ssh_keys.#:           "" => "1"
    ssh_keys.0:           "" => "512189"
    status:               "" => "<computed>"

$ terraform apply
digitalocean_droplet.node-1: Creating...
  image:                "" => "ubuntu-14-04-x64"
  ipv4_address:         "" => "<computed>"
  ipv4_address_private: "" => "<computed>"
  ipv6_address:         "" => "<computed>"
  ipv6_address_private: "" => "<computed>"
  locked:               "" => "<computed>"
  name:                 "" => "node-1"
  region:               "" => "sgp1"
  size:                 "" => "512mb"
  ssh_keys.#:           "" => "1"
  ssh_keys.0:           "" => "512189"
  status:               "" => "<computed>"
digitalocean_droplet.node-1: Creation complete

status: "" => "”になっていば、作成が完了しています。

dropletにログイン

作成したdropletの情報を確認してログインします。

$ terraform show
digitalocean_droplet.node-1:
  id = 5556133
  image = ubuntu-14-04-x64
  ipv4_address = 128.199.64.45
  locked = false
  name = node-1
  region = sgp1
  size = 512mb
  ssh_keys.# = 1
  ssh_keys.0 = 512189
  status = active

IPアドレスがわかったので、実際にログインしてみます。
秘密鍵は~/.ssh/id_rsaにあるものとしています。

ssh -i ~/.ssh/id_rsa root@128.199.64.45

dropletの破棄

作成したdropletを破棄します。

$ terraform destroy
Do you really want to destroy?
  Terraform will delete all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

digitalocean_droplet.node-1: Refreshing state... (ID: 5556133)
digitalocean_droplet.node-1: Destroying...
digitalocean_droplet.node-1: Destruction complete

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