2017年4月28日金曜日

go-swagger をアップデートしたときに「Your local changes to the following files would be overwritten by merge」が発生

概要

go get で go-swagger をアップデートしたときに最新版をうまくマージできない現象が発生しました
対象方法を紹介します

環境

  • Ubuntu 16.04
  • golang 1.8.1
  • go-swagger 0.8.0 -> 0.10.0

エラー詳細

アップデート時に使用したコマンドは以下の通り

  • go get -u github.com/go-swagger/go-swagger/cmd/swagger

そして以下のエラーが発生

go get -u github.com/go-swagger/go-swagger/cmd/swagger
# cd /root/go/src/github.com/go-swagger/go-swagger; git pull --ff-only
error: Your local changes to the following files would be overwritten by merge:
        generator/bindata.go
        generator/client.go
        generator/shared.go
        scan/path.go
        scan/responses.go
Please, commit your changes or stash them before you can merge.
Aborting
Updating 00f4a1f..e62bb82
package github.com/go-swagger/go-swagger/cmd/swagger: exit status 1

単純にマージできないだけだったので、対象のディレクトリをごそっと削除して再度インストールしてあげます
その前に clean で swagger コマンド自体を削除します

  • go clean -i github.com/go-swagger/go-swagger/cmd/swagger
  • cd /root/go/src/github.com/go-swagger/go-swagger
  • cd ../ && rm -rf go-swagger
  • go get -u github.com/go-swagger/go-swagger/cmd/swagger

でエラーなく最新版をインストールすることができました

コードも実は修正する必要あり

go-swagger が生成してくれる main 的なソース「restapi/configure_your_project_name.go」ですがこいつも修正しないと最新版ではエラーが発生すると思います
configureServer の引数が 1 つ増えているので以下のように最後に一つ引数を追加してあげましょう

func configureServer(s *graceful.Server, scheme, addr string) {
...
}

最後に

go-swagger をバージョンアップした際に対応したことを紹介しました
オープンソースだしまだメジャーバージョンアップでもないので何とも言えませんが、後方互換性がないアップデートだとちょっと焦ります

2017年4月27日木曜日

vic-machine inspect で取得できる vch のエンドポイントの情報をコードから取得してみた

概要

前回 ls コマンドをコードから実行しました
今回は ls の追加として inspect を実装します
inspect は docker のエンドポイントを取得するためのコマンドです
ls で取得した vch を for ループで回して各 vch のエンドポイントを取得してみます

環境

  • Ubuntu 16.04
  • golang 1.8.1
  • vic 0.9.0

inspect のように vch からエンドポイントを取得するコード

  • vim my_vic_ls.go
github.com/docker/docker/opts

上記を import に追加します

for _, vch := range vchs {
        c, err := executor.GetVCHConfig(vch)
        if err != nil {
                return err
        }
        cip := c.ExecutorConfig.Networks["client"].Assigned.IP
        fmt.Println(cip)
        pip := c.ExecutorConfig.Networks["public"].Assigned.IP
        fmt.Println(pip)
        if !c.HostCertificate.IsNil() {
                fmt.Println(opts.DefaultTLSHTTPPort)
        } else {
                fmt.Println(opts.DefaultHTTPPort)
        }
        if c.IsCreating() {
                fmt.Println("ng")
        } else {
                fmt.Println("ok")
        }
}

これを Run メソッドの executor.SearchVCHs の後に追加すれば OK です
vch の config 情報を executor.GetVCHConfig で取得できるのでその config に対して c.ExecutorConfig.Networks["client"].Assigned.IP でエンドポイントを取得します
Networks はスライスになっているのでネットワーク名を指定することで必要な IP アドレスを取得できます
普通は client or public が docker のエンドポイント用の IP アドレスになっているのでどちらかを取得すれば OK かなと思います

また、c.HostCertificate を判定することで vch のエンドポイントが ssl 用のポートかそうでないかを表示してあげます

vch が作成できたかどうかは IsCreating というメソッドがあるのでこれで判定します

最後に

inspect で取得できるエンドポイントの情報をコードから取得してみました
ls のコードに追加するだけで inspect の情報も取得できるようにしました

参考サイト

2017年4月26日水曜日

vic-machine delete コマンドをコードからライブラリとして使う方法

概要

前回 vic-machine ls コマンドをコードから実行してみました
今回は vic-machine delete コマンドをコードから実行してみました

環境

  • Ubuntu 16.04
  • golang 1.8.1
  • vic 0.9.0

delete を実行するコードの作成

  • mkdir /path/to/work
  • cd /path/to/work
  • vim my_vic_delete.go
package main

import (
        "context"
        "fmt"
        "github.com/vmware/vic/lib/install/data"
        "github.com/vmware/vic/lib/install/management"
        "github.com/vmware/vic/lib/install/validate"
        "github.com/vmware/vic/pkg/vsphere/vm"
        "net/url"
)

type Delete struct {
        Data     *data.Data
        executor *management.Dispatcher
}

func NewDelete() (d *Delete, err error) {
        d = new(Delete)
        d.Data = data.NewData()
        u, err := url.Parse("https://192.168.100.101/dc")
        if err != nil {
                return d, err
        }
        d.Data.Target.URL = u
        l.Data.Target.User = "vcenter_username"
        p := "vcenter_password"
        d.Data.Target.Password = &p
        l.Data.Target.Thumbprint = "F9:xx:xx:..."
        d.Data.Force = true
        err = d.Data.Target.HasCredentials()
        if err != nil {
                return d, err
        }
        //d.Data.ID = "vm-100"
        d.Data.DisplayName = "vch001"
        d.Data.ComputeResourcePath = "rp-1"
        //d.Data.Force = true
        return d, nil
}

func (d *Delete) Run() (err error) {
        ctx, cancel := context.WithTimeout(context.Background(), d.Data.Timeout)
        defer cancel()
        validator, err := validate.NewValidator(ctx, d.Data)
        if err != nil {
                return err
        }
        validator.AllowEmptyDC()
        executor := management.NewDispatcher(validator.Context, validator.Session, nil, false)
        var vch *vm.VirtualMachine
        if d.Data.ID != "" {
                vch, err = executor.NewVCHFromID(d.Data.ID)
        } else {
                vch, err = executor.NewVCHFromComputePath(d.Data.ComputeResourcePath, d.Data.DisplayName, validator)
        }
        fmt.Println(vch)
        vchConfig, err := executor.GetVCHConfig(vch)
        if err != nil {
                return err
        }
        err = executor.DeleteVCH(vchConfig)
        if err != nil {
                executor.CollectDiagnosticLogs()
                return err
        }
        return nil
}

func main() {
        d, err := NewDelete()
        if err != nil {
                fmt.Println(err)
        }
        err = d.Run()
        if err != nil {
                fmt.Println(err)
        }
}
  • go fmt my_vic_delete.go
  • go run my_vic_delete.go

d.Data.ID で ID を指定するか d.Data.DisplayNamed.Data.ComputeResourcePath を vch までの名前とパスを使って指定します
どちらでも OK ですが個人的にはパスで指定する方法をオススメします
パスがわかっていれば govmomi を使って直接 VM に対しても操作できるからです

Force が true になっている場合は vch 上のコンテナが起動中でも強制的に削除してくれます

解説

config して実行の流れは前回と同じです
delete のときのほうが指定するパラメータが増えているので注意してください
ID や DisplayName は ls を実行したときに取得できます

まず executor.NewVCHFromID で vch の情報を取得します
そして、それを元に executor.DeleteVCH をコールすることで vch を削除してくれる他、付属のネットワークとストレージも削除してくれます

executor.CollectDiagnosticLogs で CLI 用のデバッグログを出力してくれます

最後に

vic-machine delete コマンドをコードから実行してみました
delete も結構簡単にできました
あとは inspect あたりができるなるといいかなという感じです

参考サイト

2017年4月25日火曜日

vic-machine ls コマンドをコードからライブラリとして使う方法

概要

VMware VIC は vic-machine というコマンドを提供しておりこれを使って vch を操作します
プログラムから vch を操作したい場合、コードから os.Exec などを使って CLI を実行する方法があります
が、CLI の場合結果をパースしたりエラーハンドリングをしたりするのが大変です
なので、vic-machine コマンドがやっていることをコードから同じように実行してみました

環境

  • Ubuntu 16.04
  • golang 1.8.1
  • vic 0.9.0

vic のインストール

ライブラリ形式で提供されていないため go get が使えません
なので、GOPATH に git clone して無理矢理ライブラリとして利用します

ls を実行するコードの作成

  • mkdir /path/to/work
  • cd /path/to/work
  • vim my_vic_ls.go
package main

import (
        "context"
        "fmt"
        "github.com/vmware/vic/lib/install/data"
        "github.com/vmware/vic/lib/install/management"
        "github.com/vmware/vic/lib/install/validate"
        "net/url"
)

type List struct {
        Data     *data.Data
        executor *management.Dispatcher
}

func NewList() (l *List, err error) {
        l = new(List)
        l.Data = data.NewData()
        u, err := url.Parse("https://192.168.100.101/dc")
        if err != nil {
                return l, err
        }
        l.Data.Target.URL = u
        l.Data.Target.User = "vcenter_username"
        p := "vcenter_password"
        l.Data.Target.Password = &p
        l.Data.Target.Thumbprint = "F9:xx:xx:..."
        l.Data.Force = true
        err = l.Data.Target.HasCredentials()
        if err != nil {
                return l, err
        }
        return l, nil
}

func (l *List) Run() (err error) {
        ctx, cancel := context.WithTimeout(context.Background(), l.Data.Timeout)
        defer cancel()
        validator, err := validate.NewValidator(ctx, l.Data)
        if err != nil {
                return err
        }
        validator.AllowEmptyDC()
        executor := management.NewDispatcher(validator.Context, validator.Session, nil, false)
        vchs, err := executor.SearchVCHs(validator.ClusterPath)
        if err != nil {
                return err
        }
        fmt.Println(vchs)
        return nil
}

func main() {
        l, err := NewList()
        if err != nil {
                fmt.Println(err)
        }
        err = l.Run()
        if err != nil {
                fmt.Println(err)
        }
}
  • go fmt my_vic_ls.go
  • go run my_vic_ls.go

でとりあえず vch の一覧を取得できると思います
vCenter の情報は適宜書き換えて使用してください

解説

流れ的には config して実行しているだけです

まず、NewList 関数で vCenter の認証情報を設定します
ここで一つポイントなのが HasCredentials を呼ばないと vCenter の認証でエラーとなります
なので、必ず呼んでください
vCenter の URL を設定するときにデータセンター名まで指定するとそのデータセンター配下にある vch を取得してくれます

実際に取得している関数は executor.SearchVCHs になります
これが VIC 内で定義されている関数になりライブラリとしてコールしている感じになります
あとは executor.SearchVCHs をコールするために必要な変数などを定義しているだけです

実際に参考したコードは vic-machine の list.go になります

最後に

VMware VIC の CLI 用のコードをライブラリっぽく使ってみました
そもそも godoc にすらなっていないのでコードを見ながら理解しなければいけないのが辛いです

CLI が実装していることと同じようなことをしてるので車輪の再発明的な感じがありますが、ライブラリを使ってクライアントコードを実装していると思えば割りと当たり前のことかなと言う気がします

本来であれば vmware/vic のコードをライブラリ用のコードとして書き換えて go get で取得できるようにして使うのが定石だとは思います
現状はそうなっていないので、無理矢理使ってみました

他のコマンドに関しては同じような実装をしていけば同様にコードから使えるようになると思います
ただ、あくまでも内部では CLI 用のコードとして書かれているのでロギングなどの出力は CLI ベースになってしまいます

参考サイト

2017年4月23日日曜日

gradle で Jersey 入門

概要

Jersey は RESTful な API を開発するための Java のフレームワークです
今回は Hello World として gradle を使って Jersey を動作させてみました
また、テストも作成しています

環境

  • Ubuntu 16.04
  • gradle 3.5
  • OpenJDK 8u121-b13
  • Jersey 2.14

事前作業

gradle のインストールはこちら

  • apt -y install openjdk-8-jdk

プロジェクト作成

  • mkdir gradle-jersey
  • cd gradle-jersey
  • vim build.gradle
apply plugin: 'java'
apply plugin: 'jetty'

repositories {
  mavenCentral()
}

dependencies {
  testCompile 'junit:junit:4.11'
  testCompile 'org.hamcrest:hamcrest-all:1.3'
}

test {
  exclude '**/*IntegrationTest*'
}

task integrationTest(type: Test) {
  include '**/IntegrationTest*'
  doFirst {
    jettyRun.httpPort = 8080
    jettyRun.daemon = true
    jettyRun.execute()
  }
  doLast {
    jettyStop.stopPort = 8091
    jettyStop.stopKey = 'stopkey'
    jettyStop.execute()
  }
}
  • gradle build

でとりあえずビルドが成功することを確認しましょう

テスト作成

  • mkdir -p src/test/java/com/hawksnowlog/hello/webapp
  • vim src/test/java/com/hawksnowlog/hello/webapp/HelloIntegrationTest.java
package com.hawksnowlog.hello.webapp;

import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;

import org.junit.Test;

public class HelloIntegrationTest {

    private static String HELLO_URL = "http://localhost:8080/hello";

    @Test
    public void testHello() throws Exception {
        Client client = ClientBuilder.newClient();
        WebTarget wt = client.target(HELLO_URL);
        String r = wt.request().get(String.class);
        assertThat(r, is("Hello World!"));
    }

}
  • vim build.gradle

一部抜粋

dependencies {
  testCompile 'junit:junit:4.11'
  testCompile 'org.hamcrest:hamcrest-all:1.3'
  compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.14'
}
  • gradle integrationTest

と実行すると specified for property 'webAppSourceDirectory' does not exist. と言われるのでとりあえずプロパティを追加する

  • vim build.gradle

一部抜粋

task integrationTest(type: Test) {
  include '**/*IntegrationTest*'
  doFirst {
    jettyRun.webAppSourceDirectory = file("src/main/java/com/hawksnowlog/hello/webapp")
    jettyRun.contextPath = '/'
    jettyRun.httpPort = 8080
    jettyRun.daemon = true
    jettyRun.execute()
  }
  doLast {
    jettyStop.stopPort = 8091
    jettyStop.stopKey = 'stopkey'
    jettyStop.execute()
  }
}

で再度実行して 404 のエラーとなりテストがこけることを確認する
javax.ws.rs.NotFoundException at HelloIntegrationTest.java:20

web アプリのコード作成

404 にならないように本体のアプリを作成する

  • mkdir -p src/main/java/com/hawksnowlog/hello/webapp
  • vim src/main/java/com/hawksnowlog/hello/webapp/HelloWebapp.java
package com.hawksnowlog.hello.webapp;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("/hello")
public class HelloWebapp {

    @GET()
    public String hello() {
        return "Hello World!";
    }
}
  • mkdir -p src/main/webapp/WEB-INF/
  • vim src/main/webapp/WEB-INF/web.xml
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Jetty Gradle Hello World</display-name>
  <servlet>
    <servlet-name>HelloWorldServlet</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>jersey.config.server.provider.packages</param-name>
      <param-value>com.hawksnowlog.hello.webapp</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>HelloWorldServlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

web.xml の内容は正直自分もよくわかっていないのでおまじないです
一番のポイントは jersey.config.server.provider.packages でアプリのパッケージを指定しているところだと思います

  • gradle integrationTest

でビルドが成功すると思います
この状態で curl localhost:8080/hello を実行してみると「Hello World!」が返ってくることも確認できると思います
(本当は doLast で jettyStop しているので止まるはずなんですが、止まらない、、、)

ちなみに web.xml を作成後は build.grdle で定義している jettyRun.webAppSourceDirectory = file("src/main/java/com/hawksnowlog/hello/webapp") は削除しても動作します

最後に

gradle で Jersey を試してみました
あとは Path を追加するなり DAO 的なのを作ったりしていけば OK じゃないかなと思います

Java の場合 war ファイルを作成する必要もあるので、そのあたりのビルドフローも build.gradle に追加すればいんじゃないかなと思います
久しぶりに Java を使ってみましたが、やはり Java は辛いイメージです

「Hello World」を表示するだけなのにこれだけコードを記述する必要があるのが辛い点です

参考サイト

2017年4月22日土曜日

Gradle 入門

概要

Gradle はビルドツールの一つで主に Java で使用されるビルドツールです
いろいろな API が提供されており、ant や maven より強力な機能を提供してます
また、groovy という言語が使えるのでビルド定義をプログラマブルに記載することができます

環境

  • Ubuntu 16.04
  • gradle 3.5

gradle のインストール

  • curl -s “https://get.sdkman.io” | bash
  • source “/root/.sdkman/bin/sdkman-init.sh”
  • sdk help
  • sdk install gradle

学習

いろいろなタスクを定義して実行しながら学習していきます

  • vim gradle.build

タスク作成

task hello {
    doLast {
        println 'hello'
    }
}
  • gradle -q hello

-q は quiet で出力省略

別タスク作成

task hello2 << {
  println 'hello2'
}
  • gradle -q hello2

groovy 関数を使う

task upper << {
  String s = 'hoge'
  println "Original: " + s
  println "Upper: " + s.toUpperCase()
}
  • gradle upper

times でループさせる

task c << {
  int sum = 0
  4.times {
    sum += it
    println sum
  }
}
  • gradle c

タスクに依存関係を持たせる

task world(dependsOn: hello) << {
  println 'world'
}
  • gradle world

先に (ファイルの先頭に) hello タスクを定義しておかないとエラーとなる

動的タスクを作成する

4.times { count ->
  task "task$count" << {
    println count
  }
}
  • gradle task1 task2 task3 task4

タスク API を使って依存関係を持たせる

4.times { count ->
  task "task$count" << {
    println count
  }
}
task0.dependsOn task1 task2

宣言したタスクには gradle が準備する API がいくつか準備されている
その中の dependsOn という API を使えば依存関係を持たせることができる

  • gradle task0

タスクに前後の処理を追加する

hello2.doFirst {
  println 'doFirst'
}

hello2.doLast {
  println 'doLast'
}

今まででてきた << は doLast のエイリアスになっている

  • gradle hello2

空のタスクを定義して API で処理を記載する

task hello3
hello3 << {
  println 'hello3'
}
hello3 {
  println 'hello3.1'
}

最後のようにタスクを定義したあとで処理を記載することができます
この場合は「hello3.1」->「hello3」と表示されます
(<< が doLast のエイリアスのため)

  • gradle hello3

タスクにプロパティを持たせる

task extv {
  ext.p = 'value1'
}
task m << {
  println extv.p
}

extv タスクに p プロパティを定義して値を持たせています
タスク内のプロパティにアクセスするには「タスク名.プロパティ名」でアクセスすることができます
task の後に doFirst も doLast も設定していない場合は何かタスク実行を実行した時に必ず実行されるタスクになります

  • gradle m

ant タスクを利用する

task tant << {
  ant.echo("tant")
}

ant.echo を使う場合 -q を付与すると出力されないので注意してください
他にもいろいろな ant API があるので詳しくはこちらを参照してください

  • gradle tant

関数を定義する

int sum(int a, int b) {
  a + b
}
task m1 << {
  println sum(1, 2)
}
  • gradle m1

デフォルトタスク

defaultTasks 'pre1', 'pre2'

task pre1 << {
  println 'pre1'
}
task pre2 << {
  println 'pre2'
}

こうすることで gradle だけでタスクを実行することができます
gradle pre1 pre2 を実行するのと同義になります

  • gradle

有効非巡回グラフ (DAG) の利用

task deploy << {
  println "deployed version: $version"
}

task pdeploy(dependsOn: deploy) << {
  println 'production deploy'
}

gradle.taskGraph.whenReady {taskGraph ->
  if (taskGraph.hasTask(pdeploy)) {
    version = "production"
  } else {
    version = "develop"
  }
}

gradle.taskGraph.whenReady を利用することであるタスクによって条件分岐することができます
version は gradle でデフォルトで使える変数です (デフォルトは unspecified が入っている)

  • gradle deploy
  • gradle pdeploy

例えば version を使わず独自の変数を使う場合は以下のようにも書けます
(ちょっと前にやった ext の拡張タスクプロパティを使います)

task deploy << {
  println "deployed version: $version"
}

task pdeploy(dependsOn: deploy) << {
  println 'production deploy'
}

gradle.taskGraph.whenReady {taskGraph ->
  if (taskGraph.hasTask(pdeploy)) {
    version = "production"
  } else {
    version = "develop"
  }
}

変数を使う

def a = 'hoge'

task at << {
  print a
}
  • gradle at

クラスを定義して使う

class Letter {
  def msg = 'default'

  def send() {
    println msg
  }
}

ext.letter = new Letter()
ext.letter.setMsg('hello')
ext.letter.send()
ext.letter.msg = 'HELLO'
ext.letter.send()
ext {
  letter.msg = 'hello'
  letter.send()
}

クラス Letter を定義しオブジェクトを ext.letter として保存します
クラスで定義したメソッドおよびプロパティにアクセスすることができます
プロパティへのアクセスはドット区切りの他、スクリプトブロックでもプロパティにアクセスできます
スクリプトブロックは所謂クロージャで処理を記述することができるので、関数へのアクセス、呼び出しも可能です
なお、スクリプトブロックでオブジェクトへの参照はできないので注意してください

  • gradle

作成した task も含めて存在する全タスクを確認する方法

  • gradle tasks --all

--all があることで自分で作成したタスクも確認することができる

プロジェクト内で使用できるデフォルトプロパティの一覧を表示する

  • gradle -q properties

ずらっと設定、使用可能なプロパティの一覧が表示されます

最後に

基本的なタスクの学習は以上です
実際に簡単なタスクでも実際に書いて手で動かすことで得られる知見も多くなると思います
gradle は Java でよく使われるイメージがありますが元は依存性を解決するビルドツールです
なので、Java だけではなくシェルスクリプトや別の言語などでも使うことができます

デフォルトで Java で使えるプラグインやタスクが多く提供されているので Java で使われる機会が多いんだと思います

gradle 自体にはまだまだ様々な使い方があるので興味があれば学習してみるといいと思います
自分も何か他にトピックがあれば紹介していきたいと思います

参考サイト

2017年4月21日金曜日

VMware Admiral を使ってみた

概要

Admiral は VMware が提供する PhotonOS をクラスタリングするツールです
Docker Swarm のような機能を提供します
今回は VMware Integrated Container (VIC) と Admiral を組み合わせて使ってみました

環境

  • Ubuntu 16.04
    • docker 17.03
    • VMware Admiral 0.9.2
  • VMware Integrated Container 0.9.0

Admiral のインストール

Admiral は Dockerhub でイメージが公開されているのでそれを使います
Ubuntu 上の普通にインストールした docker 上で動作させています

  • docker pull vmware/admiral
  • docker run -d -p 8282:8282 --name admiral vmware/admiral

で起動できます
起動できたらブラウザで 8282 ポートにアクセスしてみましょう
Admiral の管理画面が表示されると思います
Admiral ではこの管理画面を使ってクラスタを管理します

VCH の作成

Virtual Container Host (VCH) を作成します
VCH は VIC が提供するコンテナホストの名称です
VCH の作成方法は過去に詳しく紹介しています

とりあえず今回は 2 台作成します
また作成後 VCH に SSH でログインできる必要があるので debug コマンドで SSH でのログインを有効にしておきましょう

  • 作成
./cli/vic-machine-linux create \
--target 192.168.100.101/dc \
--user vcenter-username \
--password vcenter-password \
--name ah001 \
--volume-store datastore01/data_volume:default \
--image-store datastore01 \
--compute-resource cluster01 \
--bridge-network vic-bridge-01 \
--public-network vm-net \
--thumbprint F9:... \
--no-tls --no-tlsverify --force
  • ログイン有効化
./vic-machine-linux debug \
--target 192.168.100.101/dc \
--user vcenter-username \
--password vcenter-password \
--name ah001 \
--compute-resource cluster01 \
--thumbprint F9:... \
--enable-ssh --rootpw "rootpassword123"

ネットワークやストレージ、クラスタなどは環境に合わせて名称を変更してください
同様に ah002 も作成しましょう

Admiral へ VCH を追加する

トップ画面から「Add Host」で VCH を追加していきます

admiral1.png

admiral2.png

  • Address・・・作成した VCH のエンドポイント
  • Placement zone・・・ゾーン、とりあえず「default-placement-zone」を選択
  • Login credential・・・ログイン情報 (New Credential から追加します、先程 debug コマンドで設定した rootpw を設定します、ユーザは root になります)
  • Tags・・・タグ (追加するホストにタグを付けられます、とりあえず「ah」とします)

入力できたら Verify し問題なければ Add を選択しホストを追加します

追加できると以下のように Hosts の一覧に表示されます
admiral3.png

同様に 2 台目も追加しましょう
この 2 台は同一クラスタ上に属するコンテナホストとして admiral では管理されます

コンテナを作成する

では Admiral 上にコンテナを作成してみましょう
左メニューの Resources から「Create Container」を選択します

Basic タグの Image で Dockerhub にあるイメージを選択することができます
Name では好きなコンテナ名を入力します
admiral4.png

Network タグの PortBindinds の Host Port にホストでバインドするポートを入力します
Container Port にはコンテナが EXPOSE するポートを入力します
admiral5.png

今回は nginx のイメージを使ってコンテナを立ち上げるのでそれにあった情報を入力しています

設定できたら「Provision」を選択しコンテナを立ち上げます

左メニューの Resources -> Containers にコンテナが表示されると思います
同様にもう一つコンテナを作成してみましょう
先ほどと同様に nginx のイメージを選択し、Host Port も同じもの (8080) を設定してみてください

1 台構成であれば同じホストポートが使われるとエラーとなりますが、クラスタには 2 台の VCH があるのでエラーとならずコンテナが 2 台起動します
admiral6.png

当然ですが、3 台目のコンテナを 8080 で起動しようとすると 8080 が開いている VCH がないのでエラーとなります

その他

基本的な機能は以上です
コンテナを作成しすぎてリソースが足りなくなったら新たに VCH を作成して追加すれば OK です
その他ちょろっと触ってみた感想です

  • コンテナの詳細画面で docker logs が見れる
  • Placement (クラスタ) はコンテナを作成する優先度は設定することができるが Placement を指定してコンテナ作成をすることはできなさそう
  • LB 的なやつはない
  • レジストリ連携できる (Templates -> Manage Registry から追加できる)
  • Network 機能を使えばホスト間で横断的に通信できるオーバレイネットワークを組めそう

最後に

Admiral + VCH で docker swarm 的なことを試してみました
UI からの操作ですがかなり直感的に使えました

VMware 環境で VCH でコンテナ管理をしている場合には Admiral を使えば少し幸せになれるかもしれません

参考サイト

2017年4月20日木曜日

LetsEncrypt で無料で SSL 証明書を取得してみた

概要

LetsEncrypt は無料で SSL 証明書が取得できるサービスです
基本的にはドメイン認証のみになるので企業認証 (EV) などはできません
今回は専用クライアントのインストールから実際に証明書を取得してサーバにインストールするところまで行ってみます

環境

  • CentOS 7.3
  • nginx 1.10.3
  • certbot 0.12.0
  • Python 2.7.5

事前作業

事前にドメインを取得おきましょう
そして取得したいドメイン or サブドメインを DNS の A レコードとして登録して名前から IP が引けるようにしておいてください
自分は Freenom で取得したドメインを Dozens に登録することで対応しました
また、今回 SSL 証明書を取得するドメインは www.hawksnowlog.cf とします
(このドメインはすでに A レコードから削除しているためアクセスはできません)

certbot のインストール

  • yum -y install epel-release
  • yum -y install certbot python-certbot-apache

で OK です
他のプラットフォームだとインストール方法が異なるので詳細は以下をご覧ください
https://letsencrypt.jp/usage/

nginx で一旦 443 を LISTEN できるようにする

証明書を作成するのにドメインを設定したサーバの保有者であることを確認するために一旦サーバを 443 で受け付ける必要があります
自己証明書で問題ないので一旦自己証明書で 443 LISTEN 出来るように設定します

まずは自己証明書を作成します

  • mkdir /etc/nginx/cert
  • cd /etc/nginx/cert
  • openssl req -new -days 365 -x509 -nodes -keyout cert.key -out cert.crt

次に作成した自己証明書を設定した nginx 用の ssl.conf を作成します

  • vim /etc/nginx/conf.d/ssl.conf
server {
    listen 443;

    ssl on;
    ssl_certificate      /etc/nginx/cert/cert.crt;
    ssl_certificate_key  /etc/nginx/cert/cert.key;
    ssl_session_timeout  5m;

    ssl_protocols                  SSLv2 SSLv3 TLSv1;
    ssl_ciphers                    HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers      on;

    location / {
        proxy_set_header Host             $host;
        proxy_set_header X-Real-IP        $remote_addr;
        proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-User $remote_user;

        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}

コンテンツは何でも OK です

  • vim /usr/share/nginx/html/index.html
<html>
<head>
<title>
</title>
</head>
<body>
<p>Welcome www.hawksnowlog.cf !</p>
</body>
</html>

これで一旦 nginx を起動して 443 で LISTEN できるか確認してください

-k オプションがないと証明書で怒られると思います

certbot コマンドを使って証明書を取得する

LetsEncrypt は専用のコマンドを使って証明書を取得します
先程インストールした certbot コマンドを使います

  • certbot certonly --standalone -d www.hawksnowlog.cf

-d オプションの後は SSL 証明書を取得したいドメインを指定します
certonly サブコマンドは証明書の取得だけを行うサブコマンドです

まず実行するとメールアドレスを登録するように言われます
何でも OK ですが緊急時の連絡などが来るのでドメインの管理者のメールアドレスを登録するのが良いと思います
(以下はダミーのメールアドレスです)

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel):your-name@dummy.com
Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org

利用規約に同意します

-------------------------------------------------------------------------------
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf. You must agree
in order to register with the ACME server at
https://acme-v01.api.letsencrypt.org/directory
-------------------------------------------------------------------------------
(A)gree/(C)ancel:A

メールアドレスを EFF に共有しても良いですか?という確認です
EFF は certbot を管理している組織です
とりあえず今回はテストなので No にしました

-------------------------------------------------------------------------------
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about EFF and
our work to encrypt the web, protect its users and defend digital rights.
-------------------------------------------------------------------------------
(Y)es/(N)o: N

証明書の作成が始まります
ここでドメインの 443 へのアクセスがあります
外部からアクセスできる必要があるのでファイアウォールなどの設定で 443 はオープンにしておいてください
また、ngxin も起動状態にしておきます

Obtaining a new certificate
Performing the following challenges:
tls-sni-01 challenge for hawksnowlog.cf
tls-sni-01 challenge for www.hawksnowlog.cf
Waiting for verification...

-------------------------------------------------------------------------------
Could not bind TCP port 443 because it is already in use by another process on
this system (such as a web server). Please stop the program in question and then
try again.
-------------------------------------------------------------------------------
(R)etry/(C)ancel:R

アクセスできるとすでに 443 ポートが使われいるという旨が表示されるので一旦停止します
停止したあとで、そして Retry を入力します

すると証明書の作成が進み最終的に作成が完了します

Waiting for verification...
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/0000_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0000_csr-certbot.pem

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/www.hawksnowlog.cf/fullchain.pem. Your cert
   will expire on 2017-07-18. To obtain a new or tweaked version of
   this certificate in the future, simply run certbot again. To
   non-interactively renew *all* of your certificates, run "certbot
   renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

作成される主な証明書は以下の通りです

  • サーバ証明書
    • /etc/letsencrypt/archive/www.hawksnowlog.cf/cert1.pem
  • 中間証明書
    • /etc/letsencrypt/archive/www.hawksnowlog.cf/chain1.pem
  • サーバ証明書+中間証明書
    • /etc/letsencrypt/archive/www.hawksnowlog.cf/fullchain1.pem
  • 秘密鍵
    • /etc/letsencrypt/archive/www.hawksnowlog.cf/privkey1.pem

また、最新版の証明書を管理するシンボリックされます
サーバから使用する場合は基本的に以下を参照しておけば常に最新版を使うことになるので以下を参照するようにしましょう

  • ls /etc/letsencrypt/live/www.hawksnowlog.cf
cert.pem  chain.pem  fullchain.pem  privkey.pem  README

nginx の設定ファイルの書き換え

作成した LetsEncrypt の証明書を nginx に設定します

  • vim /etc/nginx/conf.d/ssl.conf
server {
    listen 443;

    ssl on;
    ssl_certificate      /etc/letsencrypt/live/www.hawksnowlog.cf/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/www.hawksnowlog.cf/privkey.pem;
    ssl_session_timeout  5m;

    ssl_protocols                  SSLv2 SSLv3 TLSv1;
    ssl_ciphers                    HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers      on;

    location / {
        proxy_set_header Host             $host;
        proxy_set_header X-Real-IP        $remote_addr;
        proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-User $remote_user;

        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}

書き換えている箇所は ssl_certificatessl_certificate_key の部分です
nginx の場合、中間証明書を指定するディレクティブがないので証明書には合体版の証明書を使います

指定できたら再起動します

  • nginx -s stop
  • nginx

そして再度ドメインにアクセスしてみましょう
今度は証明書の警告が出ないでアクセスできるようになっていると思います

ブラウザでもアクセスしてみましょう
左上ボタンで証明書の情報を確認することができます
設定したドメインの証明書であることと LetsEncrypt が認証局になっていることを確認してください
letsencrypt1.png

また、LetsEncrypt の場合証明書の期限が 3 ヶ月になっているので更新チェックはこまめに行うようにしましょう
(自動で証明書の更新を行う方法もあるようなので、それを使うのが簡単かなと思います)
letsencrypt2.png

最後に

LetsEncrypt を使って無料で SSL 証明書を取得してみました
コマンドだけなのでハードルは高いですが、逆にエンジニア的にはコマンドだけで証明書の管理から作成、更新までできるのでかなり楽です

cerbot コマンドには他にもいろいろなオプションがあるので興味ある方はサイトで調べてみるといいと思います
日本語のドキュメントサイトもかなり丁寧に書かれているのでわかりやすいです

参考サイト

Tips

取得した証明書の確認方法

  • certbot certificates

取得した証明書の削除方法

  • certbot delete
Saving debug log to /var/log/letsencrypt/letsencrypt.log

Which certificate would you like to delete?
-------------------------------------------------------------------------------
1: www.hawksnowlog.cf
-------------------------------------------------------------------------------
Press 1 [enter] to confirm the selection (press 'c' to cancel): 1

-------------------------------------------------------------------------------
Deleted all files relating to certificate www.hawksnowlog.cf.
-------------------------------------------------------------------------------

現在保持している証明書の一覧が表示されるので番号を入力すれば OK です
証明書を管理しているディレクトリ (/etc/letsencrypt/archive) ごとなくなっているのが確認できると思います

LetsEncrypt はワイルドカード SSL 証明書には対応していないようです
コードサイニングの証明書としても使えないようです
その他、詳しい情報は FAQ のページに詳しく書かれています

2017年4月19日水曜日

はじめての Jenkinsfile (git 連携)

概要

Jenkinsfile を改めて試してみました
Github 上で Jenkinsfile を管理してそれを取得してビルドを実行します

環境

  • CentOS 7.3
  • Jenkins 2.46.1

Jenkinsfile 作成

あらかじめ Github 上にリポジトリを作成しておきましょう

node {
  echo 'My first Jenkinsfile'
}
  • git add .
  • git commit -m “first”
  • git push -u origin master

でとりあえず OK です

Pipeline ジョブを作成する

Jenkins 上で Pipeline ジョブを作成します
Pipeline ジョブでないと Jenkinsfile が使えないのでご注意ください

ジョブを作成したら Pipeline の設定箇所で「Pipeline script from SCM」を選択します
SCM を git に変更し先程 push した Github リポジトリの URL を記載します
first_jenkinsfile1.png

これでビルドするとビルド結果に echo の結果が表示されると思います

Jenkinsfile を書き換えてみる

  • vim Jenkinsfile
node {
  stage('Echo') {
    echo 'My first Jenkinsfile'
  }
}
  • git add .
  • git commit -m “second”
  • git push -u origin master

書き換えて再 push 後ジョブをビルドし直すとビルド結果が変わっていることがわかると思います
ステージを追加することでステージごとにビルド結果が表示されるので結果も見やすくなります
first_jenkinsfile2.png

最後に

Jenkinsfile を Github で管理して Jenkins でビルドしてみました
Webhook などでジョブをキックできるようにすると更に便利かもしれません

Pipeline ジョブにすればジョブの内容をコードに落とせるので便利です

参考サイト

2017年4月18日火曜日

govmomi で VM を削除する方法

概要

govmomi で指定の VM を 1 台削除する方法を紹介します

環境

  • Ubuntu 16.04
  • golang 1.8
  • govmomi 0.14.0

ソースコード

  • vim destroy_vm.go
package main

import (
    "context"
    "flag"
    "fmt"
    "net/url"
    "os"

    "github.com/vmware/govmomi"
    "github.com/vmware/govmomi/find"
    "github.com/vmware/govmomi/object"
)

type VCenterAPI struct {
    URL           string
    User          string
    Pass          string
    Insecure      bool
    URLFlag       *string
    InsecureFlag  *bool
    Network       string
    GovmomiClient *govmomi.Client
    Finder        *find.Finder
    DC            *object.Datacenter
}

func NewVCenterAPI() (api *VCenterAPI) {
    api = new(VCenterAPI)
    api.URL = "https://192.168.100.101/sdk"
    api.User = "vcenter-username"
    api.Pass = "vcenter-password"
    api.Insecure = true
    api.URLFlag = flag.String("url", api.URL, "")
    api.InsecureFlag = flag.Bool("insecure", api.Insecure, "")
    api.Network = "VM Network"
    flag.Parse()
    return api
}

func (api *VCenterAPI) Init() (err error) {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    u, err := url.Parse(*api.URLFlag)
    if err != nil {
        return err
    }
    u.User = url.UserPassword(api.User, api.Pass)
    c, err := govmomi.NewClient(ctx, u, *api.InsecureFlag)
    if err != nil {
        return err
    }
    f := find.NewFinder(c.Client, true)
    dc, err := f.DefaultDatacenter(ctx)
    if err != nil {
        return err
    }
    f.SetDatacenter(dc)
    api.GovmomiClient = c
    api.Finder = f
    api.DC = dc
    return nil
}

func (api *VCenterAPI) DestroyVM(path string) (err error) {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    vm, err := api.Finder.VirtualMachine(ctx, path)
    if err != nil {
        return err
    }
    t, err := vm.Destroy(ctx)
    if err != nil {
        return err
    }
    err = t.Wait(ctx)
    if err != nil {
        return err
    }
    return nil
}

func (api *VCenterAPI) PowerOffVM(path string) (err error) {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    vm, err := api.Finder.VirtualMachine(ctx, path)
    if err != nil {
        return err
    }
    t, err := vm.PowerOff(ctx)
    if err != nil {
        return err
    }
    err = t.Wait(ctx)
    if err != nil {
        return err
    }
    return nil
}

func main() {
    api := NewVCenterAPI()
    err := api.Init()
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    path := "/dc/vm/folder/vm1"
    err = api.PowerOffVM(path)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    err = api.DestroyVM(path)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}
  • go fmt destroy_vm.go
  • go run destroy_vm.go

解説

削除する前に VM が停止していなければいけません
なので、PowerOffVM という関数を用意しています

DestroyVM も PowerOffVM もロジックはだいたい同じです
ポイントは find.VirtualMachine を使って単一の VM だけ取得している点です
find.Finder.VirtualMachineList を使って VM のインベントリパスを指定しても問題ないのですが、例えばアスタリスクなどを使って複数の VM を指定した場合複数の VM が取得できてしまいます
自分で VM の名前などでフィルタを掛けてもいいですが、そんなことをしないでも良いように準備されている find.VirtualMachine を使っています

あとはそこで取得できた object.VirtualMachine の構造体が用意している Destroy(context) を呼び出すだけで OK です
関数の戻り値にタスクが戻ってくるので Wait することでパワーオフと削除を待つことができます

実行する際に VM へのインベントリパスを指定しています
パスが存在しない場合にはエラーになります
主な発生するエラーは以下の通りです

  • find.VirtualMachine で複数指定すると以下のエラーになる
    • path '/dc/vm/folder/*' resolves to multiple vms
  • Destroy 対象の VM がパワー ON だと以下のエラーになる
    • The attempted operation cannot be performed in the current state (Powered on).
  • 指定した VM のインベントリパスがない場合
    • vm ‘/dc/vm/folder/non-vm’ not found

最後に

govmomi で VM を削除する方法を紹介しました
サンプルコードが見つからなかったのでリファレンスとソースコードを見ながら実装しましたが、慣れれば簡単にできるようになると思います

参考サイト

2017年4月17日月曜日

govmomi で指定のフォルダ配下の VM を取得する方法

概要

vSphpere 上のあるデータセンター配下に作成されたフォルダ配下に存在する VM の情報を取得してみました
今回は VM の名前と IP アドレスを取得するサンプルを紹介します

環境

  • Ubuntu 16.04
  • golang 1.8
  • govmomi 0.14.0

ソースコード

  • vim find_vm_sample.go
package main

import (
        "context"
        "flag"
        "fmt"
        "net/url"
        "os"
        "github.com/vmware/govmomi"
        "github.com/vmware/govmomi/find"
        "github.com/vmware/govmomi/object"
        "github.com/vmware/govmomi/property"
        "github.com/vmware/govmomi/vim25/mo"
        "github.com/vmware/govmomi/vim25/types"
)

type MyVCenter struct {
        URL           string
        User          string
        Pass          string
        Insecure      bool
        URLFlag       *string
        InsecureFlag  *bool
        Network       string
        GovmomiClient *govmomi.Client
        Finder        *find.Finder
        DC            *object.Datacenter
}

func NewMyVC() (myvc *MyVCenter) {
        myvc = new(MyVCenter)
        myvc.URL = "https://192.168.100.101/sdk"
        myvc.User = "vcenter-username"
        myvc.Pass = "vcenter-password"
        myvc.Insecure = true
        myvc.URLFlag = flag.String("url", myvc.URL, "")
        myvc.InsecureFlag = flag.Bool("insecure", myvc.Insecure, "")
        myvc.Network = "VM Network"
        flag.Parse()
        return myvc
}

func (myvc *MyVCenter) Init() (err error) {
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()
        u, err := url.Parse(*myvc.URLFlag)
        if err != nil {
                return err
        }
        u.User = url.UserPassword(myvc.User, myvc.Pass)
        c, err := govmomi.NewClient(ctx, u, *myvc.InsecureFlag)
        if err != nil {
                return err
        }
        f := find.NewFinder(c.Client, true) // true にするとディレクトリの場合も再帰的に検索してくれる
        dc, err := f.DefaultDatacenter(ctx)
        if err != nil {
                return err
        }
        f.SetDatacenter(dc)
        myvc.GovmomiClient = c
        myvc.Finder = f
        myvc.DC = dc
        return nil
}

func (myvc *MyVCenter) FindVMs(folder string) (err error) {
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()
        dci := myvc.DC.Common.InventoryPath
        path := fmt.Sprintf("%s/vm/%s/*", dci, folder) // フォルダは /dc/vm/folder という構造になっている
        vss, err := myvc.Finder.VirtualMachineList(ctx, path)
        if err != nil {
                return err
        }
        refs := []types.ManagedObjectReference{}
        for _, vs := range vss {
                refs = append(refs, vs.Reference())
        }
        vdst := []mo.VirtualMachine{}
        pc := property.DefaultCollector(myvc.GovmomiClient.Client)
        err = pc.Retrieve(ctx, refs, nil, &vdst)
        if err != nil {
                return err
        }
        for _, vs := range vdst {
                fmt.Println(vs.Summary.Config.Name)
                for _, n := range vs.Guest.Net {
                        if n.Network == myvc.Network {
                                fmt.Println(n.IpAddress)
                        }
                }
        }
        return nil
}

func main() {
        myvc := NewMyVC()
        err := myvc.Init()
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        err = myvc.FindVMs("your-folder-name")
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
}
  • go fmt find_vm_sample.go
  • go run find_vm_sample.go

解説

ちょっと長いですが、流れは簡単です
まず NewMyVC で必要なデータをセットします
変更する箇所は VCenter の IP とユーザ名/パスワードかなと思います

次に Init してます
Init では VCenter への接続確認と検索するための必要な変数を MyVCenter 構造体に保存しています

そして取得したい VM の一覧があるフォルダを指定して FindVM を実行します
今回は VM 名と VM が接続しているネットワーク (VM Network) に割り振られた IP アドレスを表示するだけになります
ネットワークに接続されていない VM は VM だけ表示されると思います

最後に

govmomi を使ってフォルダ指定のフォルダ配下の VM を取得してみました
pager や limit のような機能が現状ないので、必要に応じて実装する必要があります
詳しく調べないと何ともですが、VirtualMachineList の引数にそういった指定ができなさそうなので、基本は全件取得かもしれません

VirtualMachine から取得できるプロパティは govmomi の godoc と types.go のコードを直接見るのがわかりやすいと思います

2017年4月14日金曜日

golang で CLI ツールを作成してみた

概要

golang では flag パッケージなどを使うと CLI ツールを簡単に作成できます
サードパーティー製のパッケージもいろいろと用意されていますが今回は flag パッケージだけを使って作成してみました

環境

  • Mac OS X 10.12.4
  • golang 1.8

CLI ツールのコード

構造体を使ってそれっぽく書いてみました

package main

import (
    "flag"
    "fmt"
)

type User struct {
    Name   *string
    Age    *int
    Gender *bool
}

func NewUser() (u User) {
    return User{}
}

// 引数の値を構造体にセットする
func (u *User) Init() {
    u.Name = flag.String("n", "", "Your name")
    u.Age = flag.Int("a", -1, "Your age")
    u.Gender = flag.Bool("g", false, "Your gender. True is male")
    flag.Parse()
}

// ここにメインの実装をしていく
func (u *User) Run() {
    u.print()
}

func (u *User) print() {
    if *u.Name != "" {
        fmt.Printf("Your name is %s\n", *u.Name)
    }
    if *u.Age >= 0 {
        fmt.Printf("Your name is %d\n", *u.Age)
    }
    if *u.Gender {
        fmt.Println("Your gender is male")
    } else {
        fmt.Println("Your gender is female")
    }
}

func main() {
    u := NewUser()
    u.Init()
    u.Run()
}
  • go fmt main.go && go build main.go
  • ./main -n hawksnowlog -a 10 -g

とすると実行できます
流れとしては引数として受け取った値は構造体に設定しておきます
Run メソッド内にメイン処理を書く想定です
設定した構造体の値を使っていろいろな処理をここに書いていきます

DB の値を取得したり、他の API をコールしてもいいと思います

最後に

golang の標準パッケージ flag を使って簡単な cli ツールを作成してみました
公開する場合などはバイナリを配布するだけで完了なので簡単です

サードパーティー製のパッケージも試してみたいなと思っています

2017年4月13日木曜日

govmomi を試してみた

概要

vSphere の API を golang でコールするのに govmomi というライブラリがあります
今回はこれを使って vCenter から情報を取得したりしてみました

環境

  • Ubuntu 16.04
  • golang 1.8
  • govmomi 0.14.0

ライブラリインストール

  • go get github.com/vmware/govmomi

とりあえず vCenter に接続する

  • vim connect.go
package main

import (
        "context"
        "flag"
        "fmt"
        "net/url"
        "os"

        "github.com/vmware/govmomi"
)

var envURL = "https://192.168.100.101/sdk"
var urlDescription = fmt.Sprintf("ESX or vCenter URL [%s]", envURL)
var urlFlag = flag.String("url", envURL, urlDescription)

var envInsecure = true
var insecureDescription = fmt.Sprintf("Don't verify the server's certificate chain [%s]", envInsecure)
var insecureFlag = flag.Bool("insecure", envInsecure, insecureDescription)

func main() {
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()
        flag.Parse()
        u, err := url.Parse(*urlFlag)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        u.User = url.UserPassword("vcenter_user", "vcenter_pass")
        c, err := govmomi.NewClient(ctx, u, *insecureFlag)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        fmt.Println(c.Client.Client.Version)
}

設定する箇所は envURL の部分と url.UserPassword の部分です
それぞれ vCenter の IP とユーザ名、パスワードを設定してください

とりあえずこれでエラーにならなければ vCenter には接続できています

データセンターの情報を取得する

import に以下を追加してください

"github.com/vmware/govmomi/find"

main の部分に以下を追記してください

f := find.NewFinder(c.Client, true)
dc, err := f.DefaultDatacenter(ctx)
if err != nil {
        fmt.Println(err)
        os.Exit(1)
}
f.SetDatacenter(dc)
fmt.Println(dc)

これでデータセンターの情報を取得できます

データストアのリストを取得する

import に以下を追加してください

"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types'

main の部分に以下を追記します

dss, err := f.DatastoreList(ctx, "*")
if err != nil {
        fmt.Println(err)
        os.Exit(1)
}
pc := property.DefaultCollector(c.Client)
var refs []types.ManagedObjectReference
for _, ds := range dss {
        fmt.Println(ds.Common.InventoryPath)
        refs = append(refs, ds.Reference())
}
var dst []mo.Datastore
err = pc.Retrieve(ctx, refs, nil, &dst)
if err != nil {
        fmt.Println(err)
        os.Exit(1)
}
for _, ds := range dst {
        fmt.Println(ds.Summary.Name)
        fmt.Println(ds.Summary.Type)
        fmt.Println(ds.Summary.Capacity)
        fmt.Println(ds.Summary.FreeSpace)
}

いきなりややこしくなった感があります
まず f.DatastoreList でデータストアのリストを取得します
このままだと InventoryPath しか取得できないので一旦ループさせて types.ManagedObjectReference の配列を作成します

さらにその配列をループさせて mo.Datastore の配列を作成します
この配列の中にデータストアのいろいろな情報が詰め込まれています
その詰め込むための関数が用意されていて property.DefaultCollector -> pc.Retrieve をコールします
これの 3 番目の引数に今回は nil を指定していますが、ここに ManagedObjectReference で設定したい値だけ設定することができます
例えば []string{"summary"} と指定すると summary の情報だけ設定することができます

設定した値はポインタで渡した dst に設定されるので、これを参照することでデータストアの詳細なデータにアクセスできます
dst から参照できるフィールドは mo の godoc を見ながらやるとわかりやすいと思います
更にその配下の構造体にさわる場合は godoc がなかったのどんなフィールドがあるかは直接ソースを見るしかなさそうです

VM のリストを取得する

main の部分に以下を追記します

refs = []types.ManagedObjectReference{}
vss, err := f.VirtualMachineList(ctx, "*")
if err != nil {
        fmt.Println(err)
        os.Exit(1)
}
for _, vs := range vss {
        fmt.Println(vs.Common.InventoryPath)
        refs = append(refs, vs.Reference())
}
var vdst []mo.VirtualMachine
err = pc.Retrieve(ctx, refs, nil, &vdst)
if err != nil {
        fmt.Println(err)
        os.Exit(1)
}
for _, vs := range vdst {
        fmt.Println(vs.Summary.Guest.GuestId)
        fmt.Println(vs.Summary.Guest.GuestFullName)
        fmt.Println(vs.Summary.Guest.HostName)
        fmt.Println(vs.Summary.Guest.IpAddress)
        fmt.Println(vs.Summary.Config.Name)
        fmt.Println(vs.Summary.Config.VmPathName)
        fmt.Println(vs.Summary.Config.MemorySizeMB)
        fmt.Println(vs.Summary.Config.NumCpu)
}

refs は設定し直します
f.VirtualMachineList を代わりにコールします
あとは pc.Retrieve を使って []mo.VirtualMachine に値を設定します

設定した dst のフィールドの値を参照することで VM の値を取得します

最後に

とりあえず取得系だけ試してみました
結構クセがある感じはしましたが、慣れればそれほど難しくはなさそうです

あとは update や post 系の処理がどんな感じかも試してみたいなと思います

参考サイト

2017年4月12日水曜日

Harbor の ova を govc でデプロイしてみた

概要

前回 ova から harbor をデプロイしてみました
前回やったときは Web Client から ova をデプロイしました
今回は golang 製の govc というツールを使って ova をデプロイしてみましあ

ova ダウンロード

govc インストール

  • go get github.com/vmware/govmomi/govc

govc 設定

  • export GOVC_INSECURE=1
  • export GOVC_URL=192.168.100.200
  • export GOVC_USERNAME=vcenter-user
  • export GOVC_PASSWORD=vcenter-pass
  • export GOVC_DATASTORE=datastore
  • export GOVC_NETWORK=”my-network”
  • export GOVC_RESOURCE_POOL=’*/Resources’

192.168.100.200 は vCenter の IP です
GOVC_INSECURE=1 とすることで証明書の警告を無視できます

  • govc about

で vCenter の情報が確認できれば OK です

json ファイル書き出し

govc を使って ova ファイルをデプロイする場合、設定を記載する json が必要になります
ova から雛形を作成できるので作成します

  • govc import.spec harbor_0.5.0-9e4c90e.ova > harbor.json
  • vim harbor.json
  • diff <(cat harbor.json | python -m json.tool) <(govc import.spec harbor_0.5.0-9e4c90e.ova | python -m json.tool)

以下のように編集しました
harbor 向けの設定なので、他の ova では設定が異なります

>     "Deployment": "small",
11c12
<             "Network": "my-network"
---
>             "Network": ""
18c19
<             "Value": "root_pwd"
---
>             "Value": ""
22c23
<             "Value": "harbor_admin_password"
---
>             "Value": ""
26c27
<             "Value": "db_password"
---
>             "Value": ""
30c31
<             "Value": "true"
---
>             "Value": "false"
42c43
<             "Value": "off"
---
>             "Value": "on"
130c131
<             "Value": ""
---
>             "Value": "Harbor"

上記の編集ポイントは以下の通り

  • govc: ServerFaultCode: A specified parameter was not correct: cisp.deploymentOption というエラーになるので Deployment という属性を削除
  • Network を指定 (my-network) 、何も指定しないと VM Network となる
  • root_pwd, harbor_admin_password, db_password を設定、harbor はこれが設定されていないとデプロイ時エラーとなる
  • permit_root_login を true に変更
  • self_registration を off に変更
  • govc: Property vm.vmname is not user configurable. というエラーになるので vm.vmname を空にする

変更が完了したらコマンドからデプロイします

  • govc import.ova -options=harbor.json -dump=true harbor_0.5.0-9e4c90e.ova

ホストに対して ova ファイルをアップロードするのでホストが FQDN の場合はホスト名が解決できないとエラーとなります
アップロードされたファイルは一時的に利用するだけなのでデプロイ後は削除されるようです

起動

デプロイが完了したら起動します

ここで一点ポイントですが起動しようとすると

  • プロパティ「Authentication Mode」は仮想マシンがパワーオンするように設定する必要があります

という謎のエラーが発生しました
設定の編集を開き vApp Options の設定を開いて OK とすると何故か電源を ON できるようになります
デプロイ後に何故か謎の reconfig_VM をしないと起動できない感じでした (もしかしたら環境依存かもしれません)

P.S. 20170411

ldap_searchdn, ldap_search_pwd を適当に設定してから ova をデプロイすると Authentication mode のエラーはでなくなるようです

動作確認

harbor の起動が完了し IP が取得できたら管理画面を開きましょう
ddmin ユーザでログインしてください

そしたら証明書をダウンロードします
左上から admin -> about と選択します
そして、Download リンクから harbor の自己証明書 (ca.crt) をダウンロードします

ダウンロードした証明書を docker コマンドを実行するサーバのアップロードします
そして以下のディレクトリに配置します

  • mkdir -p /etc/docker/certs.d/192.168.100.201

今回 harbor デプロイは https で行いました (前回は http にしていました)
なので、docker コマンドでアクセスするときに証明書が必要になるため配置した感じです

あとは harbor にログインして push までできることが確認できれば OK です

  • docker login 192.168.100.201
  • docker tag alpine 192.168.100.201/library/alpine
  • docker push 192.168.100.201/library/alpine

最後に

govc を使って ova をデプロイしてみました
かなりつまづきポイントが多かった印象です

CLI ツールではなく govmomi のライブラリだけを使って ova のデプロイもやってみたいです

参考サイト