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.

gorillaでAPIサーバを書く 1

gorillaを使ってAPIサーバを書く。

ディレクトリ構成としては、以下を想定。

.api
 │
 ├── handlers
 │   ├── core.go
 │   └── router.go
 └── api.go

それぞれのファイルは大まかに以下のような役割になります。

  • api.goはサーバの起動設定などを記載
  • router.goはファイル名の通りrouterなので、パスに合わせたハンドラの設定
  • core.goはハンドラーの実装

まずは必要なパッケージのインストール

go get github.com/gorilla/mux

api.goはgorilla特有の処理などは書かないので割愛。

router.go では、mux.Routerを使ったRouter関数を定義します。 エンドポイントを指定して、エンドポイントごとにHTTPメソッドに対応するHandler関数を設定します。

package handlers

import (
    "net/http"
    "github.com/gorilla/mux"
)

const (
    RegexMatchUserID = "[A-Za-z0-9]{3}"
)

func Router() *mux.Router {
    r := mux.NewRouter()

    s := r.PathPrefix("/api/").
        Subrouter().StrictSlash(false)

    usersPath := "/users/"
    subpath := usersPath + "{userId:" + RegexMatchUserID + "}"

    s.HandleFunc(usersPath, GetUsers).Methods("GET")

    s.HandleFunc(subpath, GetUserById).Methods("GET")

    s.HandleFunc(subpath, DeleteUserById).Methods("DELETE")

    s.HandleFunc(subpath, CreateUser).Methods("POST").
        Headers("Content-Type", "application/json")
    return r
}

PathPrefixでベースとなるURLを元にエンドポイントを組み立て行く方法の他に、下記のようにPathとPathPrefixを組み合わて同じように書くことが出来ます。

func Router() *mux.Router {
    r := mux.NewRouter()

    userPath := urlbase + "/users/"
    users := r.Path(userPath).Subrouter()

    users.Methods("GET").HandlerFunc(GetUsers)

    subpath := userPath + "{userId:" + RegexMatchUserID + "}"
    usr := r.PathPrefix(subpath).Subrouter()
    usr.Methods("GET").HandlerFunc(GetUserById)
    usr.Methods("DELETE").HandlerFunc(DeleteUserById)

    usr.Methods("POST").HandlerFunc(CreateUser)

    return r
}

Handlerメソッドについては、次回記載します。

SICPを読む会 1 問題1.3

読む会SICPを黙々と読む。

SICP Support for DrRacketのインストールを参考にDrRacketをインストールして実行環境を作成します。
DrRacketで言語宣言ペイン(上側のペイン)で、#lang planet neil/sicp と書いて実行しても調べたページとは違う挙動しましたが Schemeが実行出来るようなで練習問題を実施。

今日は問題1.3を実施。
今回は考え方としては3値のうち一番小さい値は何かという観点で手続きを定義します。
下記の一番最初のifの評価としては、『xはyよりも大きい、またはxはzよりも大きいのどちらもFalseになる = xは最も小さい』という周りくどい評価を実施しています。

> (define (square x) (* x x))
> (define (sum-of-squares x y)
    (+ (square x) (square y)))
> (define (f x y z)
    (if (or (>= x y) (>= x z)) 
        (if (or (>= y z) (>= y x)) 
            (sum-of-squares x y)
            (sum-of-squares z x)) 
        (sum-of-squares y z)))
> (f 3 4 5)
41
> (f 4 3 5)
41
> (f 4 5 3)
41

また、schemeではminという手続きが定義されており最小の値を求めることが出来るので、以下のように書き換えることが出来ます。

> (define (f x y z)
    (if (= (min x y z) x) (sum-of-squares y z)
        (if (= (min x y z) y) (sum-of-squares x z)
            (sum-of-squares x y))
        )
    )
> (f 3 4 5)
41
> (f 4 3 5)
41
> (f 5 4 3)
41

このような場合はifではなくcondを使ったほうが可読性が上がるので、condを使って書き換えます。

> (define (f x y z)
    (cond ((= (min x y z) x) (sum-of-squares y z))
          ((= (min x y z) y) (sum-of-squares x z))
          (else (sum-of-squares x y))))
> (f 3 4 5)
41
> (f 4 5 3)
41
> (f 5 3 4)
41

goのお勉強1

スコープ

変数や関数名で1文字目が小文字だとパッケージ内からのみ見える。大文字だとパッケージ外からのみ見える。

ポインタ

ポインタは、C言語と同様に利用できる。

package main

import "fmt"

func main() {
  a := 5
  var pa *int = &a // *intはint型のpointer
  fmt.Println(pa) // アドレスが表示される
  fmt.Println(*pa) // 値(5)が表示される

  *pa = 10
  fmt.Println(a) // 10が表示される
}

ただし、C言語と違いポインタは演算は出来ない。 c言語だと配列とかはポインタで表現され、以下のようにできるがgoでは出来ないことを確認する。 C言語では以下のように記述できる。

#include <stdio.h>

int main() {
    int  array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int* p = array;

    /* 配列の全要素の値を表示する */
    for (int i = 0; i < 10; ++i) {
        printf("*p = %d\n", *p);
        p = p + 1;  /* ポインタの値を 1 増やす */
    }

    return 0;
}

goで同じような処理をしようとしてみるとinvalid operationとコンパイルエラーになる。
スライスにしても同様にコンパイルエラーになる。

package main

import "fmt"

func main() {
  arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
  var p *[10]int = &arr
  for i := 0; i < 10; i++ {
    fmt.Println(*p) // [1 2 3 4 5 6 7 8 9 10]が出力される
    // fmt.Println(p[i]) とすると配列のi番目の値を出力できる
    p = p + 1    // invalid operation: p + 1 (mismatched types *[10]int and int) となりコンパイルエラーになる。
  }
}