Think Twice

Memorandum

マルチホストでdocker−swarm (GCP)

マルチホストでdocker swarm を試す

参考サイト

docs.docker.com

blog.docker.com

kvs の作成

  • docker-machine でswarm−kvsを作成する
$ docker-machine create \
    --driver google \
    --google-project myproject-xxxx \
    --google-zone asia-east1-a \
    --google-machine-type f1-micro \
    swarm-kvs

instanceが作成されたのを確認。

$ docker-machine ls
NAME        ACTIVE   DRIVER       STATE     URL                         SWARM
swarm-kvs   -        google       Running   tcp://130.211.246.63:2376

環境変数を整えて

$ eval "$(docker-machine env swarm-kvs)"

作成したswarm−kvs 上にconsul をサーバ・モードで実行する。

$ docker $(docker-machine config swarm-kvs) run -d \
    -p "8500:8500" \
    -h "consul" \
    progrium/consul -server -bootstrap

swarm cluster を作成する

マルチホストで通信するためにはクラスタ設定が必要。

  • swarm−master の作成
$ docker-machine create \
    --driver google \
    --google-project myproject-xxxx \
    --google-zone asia-east1-a \
    --google-machine-type f1-micro \
    --swarm \
    --swarm-master \
    --swarm-discovery="consul://$(docker-machine ip swarm-kvs):8500" \
    --engine-opt="cluster-store=consul://$(docker-machine ip swarm-kvs):8500" \
    --engine-opt="cluster-advertise=eth0:2376" \
    swarm-master
  • swarm−agent の作成
$ docker-machine create \
    --driver google \
    --google-project myproject-xxxx \
    --google-zone asia-east1-a \
    --google-machine-type f1-micro \
    --swarm \
    --swarm-discovery="consul://$(docker-machine ip swarm-kvs):8500" \
    --engine-opt="cluster-store=consul://$(docker-machine ip swarm-kvs):8500" \
    --engine-opt="cluster-advertise=eth0:2376" \
    swarm-agent

docker クライアントの環境変数の切り替え --swarm をつけるとswarmクラスタの設定になる。

$ eval $(docker-machine env --swarm swarm-master)

docker infoクラスタの状態を確認できる。

$ docker info
Containers: 3
Images: 2
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 2
 swarm-agent1: 104.155.201.164:2376
  └ Status: Healthy
  └ Containers: 1
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 618.7 MiB
  └ Labels: executiondriver=native-0.2, kernelversion=3.19.0-28-generic, operatingsystem=Ubuntu 14.04.3 LTS, provider=google, storagedriver=aufs
 swarm-master: 104.199.156.16:2376
  └ Status: Healthy
  └ Containers: 2
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 618.7 MiB
  └ Labels: executiondriver=native-0.2, kernelversion=3.19.0-28-generic, operatingsystem=Ubuntu 14.04.3 LTS, provider=google, storagedriver=aufs
CPUs: 2
Total Memory: 1.208 GiB
Name: bedd6437dbb4

compute engine のinstanceを確認。

$ gcloud compute instances list
NAME         ZONE         MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP     STATUS
swarm-agent1 asia-east1-a f1-micro                 10.240.0.4  104.155.201.164 RUNNING
swarm-kvs    asia-east1-a f1-micro                 10.240.0.2  130.211.246.63  RUNNING
swarm-master asia-east1-a f1-micro                 10.240.0.3  104.199.156.16  RUNNING
$ docker-machine ls
NAME           ACTIVE   DRIVER       STATE     URL                          SWARM
swarm-agent1   -        google       Running   tcp://104.155.201.164:2376   swarm-master
swarm-kvs      -        google       Running   tcp://130.211.246.63:2376
swarm-master   *        google       Running   tcp://104.199.156.16:2376    swarm-master (master)

オーバーレイ ネットワークの作成

  • ネットワークの作成
$ docker network create --driver overlay my-network
9e37172e132c50e8bc89e06000cf2ef6774a0fb5305e87ce5c4599383f49096e
  • 作成したネットワークの確認
$ docker network ls
NETWORK ID          NAME                  DRIVER
bfa7f2d3ea25        swarm-master/none     null
a656e617f5b0        swarm-master/host     host
9e37172e132c        my-network            overlay
fd6eaa122101        swarm-master/bridge   bridge
66f847f22da0        swarm-agent1/none     null
1e48ec001aaa        swarm-agent1/host     host
0902af9fc7d3        swarm-agent1/bridge   bridge

アプリケーションの起動(作成したネットワーク上での)

  • DB(postgres) をswarm上に起動
$ docker build -t postgres ./postgres
$ docker run -d --net=my-network --name mydb postgres
  • AP(spring-boot) をswarm上に起動 起動時の引数を以下のように設定 ↑で設定した名前(mydb)を使用できる。(自然な感じで接続できる)
FROM ubuntu

# Setup
RUN apt-get install -y software-properties-common debconf-utils
RUN add-apt-repository -y ppa:webupd8team/java
RUN apt-get update

# Java8
RUN echo "oracle-java8-installer shared/accepted-oracle-license-v1-1 boolean true" | debconf-set-selections
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get install -y oracle-java8-installer

CMD ["java", "-version"]

EXPOSE 8080

ADD ./my-application-0.1.0.jar /my-application-0.1.0.jar

CMD ["java", "-jar", "/my-application-0.1.0.jar",\
"--spring.datasource.initialize=false",\
"--spring.datasource.driverClassName=org.postgresql.Driver",\
"--spring.datasource.url=jdbc:postgresql://mydb:5432/myschema",\
"--spring.datasource.username=scott",\
"--spring.datasource.password=tiger"]
$ docker build -t web ./web
$ docker run --rm -p 80:8080 --net=my-network --name myapp web

ホスト間の接続の確認

  • docker ps で別々のnodeで起動していることを確認
docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                         NAMES
b033d2069995        web                 "java -jar /isolating"   9 seconds ago       Up 6 seconds        104.199.156.16:80->8080/tcp   swarm-master/myapp
87ec3da3b615        postgres            "/usr/lib/postgresql/"   22 seconds ago      Up 21 seconds       5432/tcp                      swarm-agent1/mydb
$ docker exec -it b033d2069995 bash
root@b033d2069995:/#
root@b033d2069995:/# cat /etc/hosts
10.0.0.3    b033d2069995
127.0.0.1   localhost
::1    localhost ip6-localhost ip6-loopback
fe00::0    ip6-localnet
ff00::0    ip6-mcastprefix
ff02::1    ip6-allnodes
ff02::2    ip6-allrouters
10.0.0.2    mydb
10.0.0.2    mydb.my-network
root@b033d2069995:/#
root@b033d2069995:/# ping mydb
PING mydb (10.0.0.2) 56(84) bytes of data.
64 bytes from mydb (10.0.0.2): icmp_seq=1 ttl=64 time=1.35 ms
64 bytes from mydb (10.0.0.2): icmp_seq=2 ttl=64 time=0.631 ms
64 bytes from mydb (10.0.0.2): icmp_seq=3 ttl=64 time=0.680 ms
64 bytes from mydb (10.0.0.2): icmp_seq=4 ttl=64 time=0.590 ms
64 bytes from mydb (10.0.0.2): icmp_seq=5 ttl=64 time=0.589 ms
64 bytes from mydb (10.0.0.2): icmp_seq=6 ttl=64 time=0.620 ms
^C
--- mydb ping statistics ---
6 packets transmitted, 6 received, 0% packet loss, time 4998ms
docker exec -it 87ec3da3b615 bash
postgres@87ec3da3b615:/$
postgres@87ec3da3b615:/$ cat /etc/hosts
10.0.0.2    87ec3da3b615
127.0.0.1   localhost
::1    localhost ip6-localhost ip6-loopback
fe00::0    ip6-localnet
ff00::0    ip6-mcastprefix
ff02::1    ip6-allnodes
ff02::2    ip6-allrouters
10.0.0.3    myapp
10.0.0.3    myapp.my-network
postgres@87ec3da3b615:/$
postgres@87ec3da3b615:/$ ping myapp
PING myapp (10.0.0.3) 56(84) bytes of data.
64 bytes from myapp (10.0.0.3): icmp_seq=1 ttl=64 time=1.44 ms
64 bytes from myapp (10.0.0.3): icmp_seq=2 ttl=64 time=0.820 ms
64 bytes from myapp (10.0.0.3): icmp_seq=3 ttl=64 time=0.801 ms
64 bytes from myapp (10.0.0.3): icmp_seq=4 ttl=64 time=0.814 ms
^C
--- myapp ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3000ms
rtt min/avg/max/mdev = 0.801/0.970/1.446/0.275 ms

画面からもDBにアクセスするのを確認できた。*1 めでたし。めでたし。


途中、すんなり行かなかったところ。

compute engine のinstance を作成するときに、途中で止まってうまく作れない(Configuring swarm に失敗する)場合、 firewall−ruleでtcp:3386;tcp:2376を許可するルールを作成する必要がある。

Configuring swarm...
WARNING >>> Error attempting heartbeat call to plugin server: unexpected EOF

と出力された場合、以下のコマンドでfirewall−ruleを作成する

$ gcloud compute firewall-rules create swarm-firewall \
    --target-tags docker-machine --allow tcp:2376,tcp:3376

Created [https://www.googleapis.com/compute/v1/projects/myproject-xxxx/global/firewalls/swarm-firewall].
NAME           NETWORK SRC_RANGES RULES             SRC_TAGS TARGET_TAGS
swarm-firewall default 0.0.0.0/0  tcp:2379,tcp:3389          docker-machine

firewall-rulesの確認

$ gcloud compute firewall-rules list

NAME                   NETWORK SRC_RANGES    RULES                             SRC_TAGS TARGET_TAGS
default-allow-icmp     default 0.0.0.0/0     icmp
default-allow-internal default 10.240.0.0/16 tcp:0-65535,udp:0-65535,icmp
default-ssh            default 0.0.0.0/0     tcp:22
docker-machines        default 0.0.0.0/0     tcp:2376,tcp:3376,tcp:8500,tcp:80          docker-machine

docker−machine create で失敗した時の出力

$ docker-machine create     --driver google     --google-project myproject-xxxx     --google-zone asia-east1-a     --google-machine-type f1-micro     --swarm     --swarm-master     --swarm-discovery="consul://$(docker-machine ip swarm-kvs):8500"     --engine-opt="cluster-store=consul://$(docker-machine ip swarm-kvs):8500"     --engine-opt="cluster-advertise=eth0:2376"     swarm-master
Running pre-create checks...
(swarm-master) OUT | Check that the project exists
(swarm-master) OUT | Check if the instance already exists
Creating machine...
(swarm-master) OUT | Generating SSH Key
(swarm-master) OUT | Creating host...
(swarm-master) OUT | Creating instance.
(swarm-master) OUT | Waiting for Instance...
(swarm-master) OUT | Uploading SSH Key
(swarm-master) OUT | Waiting for SSH Key
Waiting for machine to be running, this may take a few minutes...
Machine is running, waiting for SSH to be available...
Detecting operating system of created instance...
Detecting the provisioner...
Provisioning created instance...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Configuring swarm...
WARNING >>> Error attempting heartbeat call to plugin server: unexpected EOF
Error creating machine: Error running provisioning: connection is shut down

*1:screenshotは撮り忘れた。

docker swarm を(今さら)試す

docker swarm

docs.docker.com

  • cruster-id 作成
$ docker run --rm swarm create
9b9bc80f30bfbf9d16142fcc031ad7a7
  • master (swarm-master) 作成
$ docker-machine create \
--driver google \
--google-project myproject-1198 \
--google-zone asia-east1-a \
--google-machine-type f1-micro \
--swarm \
--swarm-master \ 
--swarm-discovery token://9b9bc80f30bfbf9d16142fcc031ad7a7 \
swarm-master
  • node (swarm-agent) 作成
$ docker-machine create \
--driver google \
--google-project myproject-1198 \
--google-zone asia-east1-a \
--google-machine-type f1-micro \
--swarm \
--swarm-discovery token://9b9bc80f30bfbf9d16142fcc031ad7a7 \
swarm-agent
  • firewall-rule作成 docker-machine createでinstanceを作成するとdocker-machine tag が付与されるので、http通信できるようにfirewall-ruleを作成する。
$ gcloud compute firewall-rules create docker-http \
       --source-ranges 0.0.0.0/0 \
       --target-tags docker-machine \
       --allow tcp:80
$ docker-machine ls
NAME           ACTIVE   DRIVER       STATE     URL                          SWARM
swarm-agent    -        google       Running   tcp://104.155.224.60:2376    swarm-master
swarm-master   *        google       Running   tcp://104.155.223.102:2376   swarm-master (master)
$ docker-machine env --swarm swarm-master
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://104.155.223.102:3376"
export DOCKER_CERT_PATH="/Users/setoguchi/.docker/machine/machines/swarm-master"
export DOCKER_MACHINE_NAME="swarm-master"
# Run this command to configure your shell:
# eval "$(docker-machine env --swarm swarm-master)"
$ eval "$(docker-machine env --swarm swarm-master)"
  • docker info でclusterの状態を確認できる
$ docker info
Containers: 6
Images: 7
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 2
 swarm-agent: 104.155.224.60:2376
  └ Status: Healthy
  └ Containers: 2
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 618.7 MiB
  └ Labels: executiondriver=native-0.2, kernelversion=3.19.0-28-generic, operatingsystem=Ubuntu 14.04.3 LTS, provider=google, storagedriver=aufs
 swarm-master: 104.155.223.102:2376
  └ Status: Healthy
  └ Containers: 4
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 618.7 MiB
  └ Labels: executiondriver=native-0.2, kernelversion=3.19.0-28-generic, operatingsystem=Ubuntu 14.04.3 LTS, provider=google, storagedriver=aufs
CPUs: 2
Total Memory: 1.208 GiB
Name: 16a45c9e82db
  • databaseを作成
$ docker build -t postgres ./postgres
  • databaseを起動
$ docker run --rm --name mydb postgres
  • web app 作成
$ docker build -t web ./web
  • web app 起動(コンテナリンクを使う)
$ docker run --rm -p 80:8080 --name myapp --link mydb:db web
$ docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                          NAMES
0c6fd7fe6288        web                 "java -jar /admission"   13 minutes ago      Up 13 minutes       104.155.223.102:80->8080/tcp   swarm-master/myapp
364cca700777        postgres            "/usr/lib/postgresql/"   15 minutes ago      Up 15 minutes       5432/tcp                       swarm-master/myapp/db,swarm-master/mydb

同じノードで動いてる。 コンテナリンクを使うとclusterを組んでいても同じノードで実行される。

linkをやめて再度トライ。

$ docker run --rm -p 80:8080 --name myapp web
$ docker run --rm --name mydb postgres
docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                          NAMES
890d7dbe56e5        postgres            "/docker-entrypoint.s"   20 seconds ago      Up 20 seconds       5432/tcp                       swarm-agent/mydb
b412bd500dc6        web                 "java -jar /admission"   48 seconds ago      Up 47 seconds       104.155.223.102:80->8080/tcp   swarm-master/myapp

別のノードで動いている。 が、これだと、コンテナ間の通信はできない。

マルチホストのコンテナ間での通信は

link ではなく networkを使えば可能。

docs.docker.com

blog.docker.com

Docker Swarm Networking

Spring Cloud

Josh Longのセッションをなぞってみた。

www.youtube.com

Config-Server

プロパティをアプリケーションの外に設定する。 gitリポジトリーGitHub,Gitbucket...)にyml、もしくは propertiesファイルで設定できる。

git リポジトリーの作成

Josh のリポジトリーをcloneして使う。

$ git clone git@github.com:joshlong/bootiful-microservices-config.git
Cloning into 'bootiful-microservices-config'...
remote: Counting objects: 158, done.
remote: Compressing objects: 100% (9/9), done.

config-server を作成

http://start.spring.io/で、Config-Serverにチェックして、Download。

DemoApplication.java@EnableConfigServerを追加

@EnableConfigServer
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

application.propertiesにconfig−serverがlistenするportと変数を定義するgitリポジトリを定義する

server.port=8888
spring.cloud.config.server.git.uri=${CONFIG_REPO:${HOME}/bootiful/bootiful-microservices-config}

起動してConfig-Serverはできあがり。

http://localhost:8888/reservation-service/masterにアクセスすると、gitに登録してある情報を参照できる。 f:id:mix-juice001:20150923151438p:plain

http://localhost:8888/${file_name}/${branch_name} で他のファイルやブランチにアクセスできる。

$ ls
application-cloud.properties        eureka-service.properties       reservation-client.properties
application.properties          hystrix-dashboard.properties        reservation-service.properties
auth-service.properties         reservation-client-github.properties

今回は8つのファイルを登録しているので、下記のURLでアクセスできる。 http://localhost:8888/application-cloud/master http://localhost:8888/eureka-service/master http://localhost:8888/reservation-client/master http://localhost:8888/application/master http://localhost:8888/hystrix-dashboard/master http://localhost:8888/reservation-service/master http://localhost:8888/auth-service/master http://localhost:8888/reservation-client-github/master

Config-Client

Reservation-ServiceをConfig-Serverにする。

pom.xmlに依存関係を定義

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

MessageRestControllerを作成する。

gitにコミットした値をすぐにInjectionして反映させるため、 @RefreshScopeアノテーションをつける。

@RefreshScope
@RestController
class MessageRestController {
    @Value("${message}")
    private String message;

    @RequestMapping("/message")
    String msg() {
        return this.message;
    }
}

applicationが起動する前に設定を定義するため bootstrap.propertiesにconfig-server の場所と、アプリケーションの名前を定義する。

spring.cloud.config.uri=${CONFIG_SERVER:http://localhost:8888}
spring.application.name=reservation-service

config-server (gitリポジトリー)には下記のように登録されているので、portは8000で起動し、 messageに"konnichiwa JSUG!!!"がインジェクションされる。

{
name: "/Users/setoguchi/bootiful/bootiful-microservices-config/reservation-service.properties",
  source: {
    server.port: "8000",
    spring.cloud.stream.bindings.input: "reservations",
    message: "konnichiwa JSUG!!!"
  }
}

Reservation-service (config-client)を起動してアクセス。

f:id:mix-juice001:20150923153234p:plain

reservation-service.propertiesのmessageを変更

$ vi reservation-service.properties
server.port=8000
message = Hello Config-Server!

$ git commit -a -m update\ message
[master dfe712c] update message
 1 file changed, 1 insertion(+), 1 deletion(-)

config-serverにアクセスすると、変更が反映されているのがわかる。 f:id:mix-juice001:20150923153902p:plain

Reservation-serviceにアクセスすると

f:id:mix-juice001:20150923154027p:plain

変わっていない!?

アプリケーションに以下のコマンドで変更を知らせる必要がある。

$ curl -d {} http://localhost:8000/refresh
["message"]

f:id:mix-juice001:20150923154321p:plain

無事変更が反映された。

変更を通知するアプリケーションがたくさんある場合、 /bus/refresh とすればすべてに通知されるらしいけど、まだ試していない。

Eureka

サービス名でURLを検索できるようにするサービス。

eureka-server を作成

http://start.spring.io/で、Eureka-server,Config-Clientにチェックして、Download。 Config-ClientをチェックするのはEureka-Serverのportをconfig−server経由で設定するため。

bootstrap.propertiesにconfig-serverの場所と、application名を定義

spring.cloud.config.uri=${CONFIG_SERVER:http://localhost:8888}
spring.application.name=eureka-service

eureka-serverを起動して、http://localhost:8761/ にアクセスすると何も登録されていないのがわかる。 f:id:mix-juice001:20150923155819p:plain

eureka-server に登録する。

pom.xml   に依存関係を定義する。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

DemoApplication.java に @EnableDiscoveryClientをつける

@EnableDiscoveryClient
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

これで起動する。起動後、30秒してから http://localhost:8761 にアクセスすると、 f:id:mix-juice001:20150923161037p:plain

reservation-service という名前で登録されているのがわかる。

reservation-clientの作成

reservation-serviceを利用するreservation-clientを作成する。

pom.xml に依存関係を追加。 feign,zuul,hystrixはあとから使うので、追記しておく。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>

DiscoveryClient

DemoApplication.javaアノテーションと、reservation-serviceにアクセスする処理を追加

@EnableDiscoveryClient
@SpringBootApplication
public class DemoApplication {

    @Bean
    CommandLineRunner dc(DiscoveryClient dc) {
        return args -> dc.getInstances("reservation-service")
                .forEach(si -> System.out.println(String.format("%s %s:%s", si.getServiceId(), si.getHost(), si.getPort())));
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

起動すると、DiscoveryClient が取得したinstanceが表示される。 f:id:mix-juice001:20150923162213p:plain

RestTemplate

    @Bean
    CommandLineRunner rt(RestTemplate rt) {
        return args -> {

            ParameterizedTypeReference<List<Reservation>> ptr =
                    new ParameterizedTypeReference<List<Reservation>>() {};

            ResponseEntity<List<Reservation>> response =
                    rt.exchange("http://reservation-service/reservations",
                        HttpMethod.GET,
                        null,
                        ptr);

            response.getBody().forEach(System.out::println);
        };
    }

http://localhost:8000/reservations にアクセスして取得できるjsonRestTemplatereservation-serviceという名前で解決して取得した値を表示する。

f:id:mix-juice001:20150923162509p:plain f:id:mix-juice001:20150923162257p:plain

Feign

@EnableFeignClientsを付与する。 @FeignClient("reservation-service")をつけることで、Reservation-clientが、reservation-serviceの振りをする(feign)ことができるようになる。 /reservations にアクセスすると、reservatin-serviceの/reservations にdispach される。

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class DemoApplication {

    @Bean
    CommandLineRunner fc(ReservationClient rc) {
        return args -> rc.getReservations().forEach(System.out::println);
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

@FeignClient("reservation-service")
interface ReservationClient {
    @RequestMapping(method = RequestMethod.GET, value="/reservations")
    Collection<Reservation> getReservations();

Hystrix

Circuit Breaker Pattern を実装している。 www.sawanoboly.net martinfowler.com

簡単に言うと、障害が発生したときにその障害を拡大させず、代替手段でサービスを提供し続けるようにする。

今回の例ではreservaation-serviceはeurekaで何台あってもロードバランスしてアクセスできるが、1台も動いていないときに例外*1が発生する。 そんなときに、代替メソッドを実行できるようになる。

DemoApplication.java に @EnableCircuitBreakerアノテーションをつける。

@EnableFeignClients
@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

ReservationIntegration を作成する。 @HystrixCommand(fallbackMethod = "reservationNamesFallback")メソッド実行失敗時の代替のメソッドを指定する。 fallbackMethod も もともとのメソッドpublicでないと正常に動かない。

@Component
class ReservationIntegration {
    @Autowired
    ReservationClient rc;

    public Collection<String> reservationNamesFallback() {
        return Collections.emptyList();
    }

    @HystrixCommand(fallbackMethod = "reservationNamesFallback")
    public Collection<String> reservationNames() {
        return rc.getReservations().stream()
                .map(Reservation::getReservationName)
                .collect(Collectors.toList());
    }
}

reservation-client を起動する。reservation-serviceが動いている時は正常に値を返却する。 f:id:mix-juice001:20150923205856p:plain

reservation-serviceを停止してアクセスすると、

f:id:mix-juice001:20150923205419p:plain

fallbackMethodの結果のemptyList()が表示される。

初回失敗時は少し時間がかかるが、2回目以降はすぐに結果が返却される。reservation-serviceの状態を記憶していて、すぐにfallbackMethodがよばれるからである。 (circuit が open 状態になる。)

reservation-serviceを再起動してしばらくすると、正常にもどる。 (circuit が close 状態にもどる。)

HystrixDashboard

http://start.spring.io/でhystrix-dashboard,eureka,config-clientにチェックをいれてダウンロード。

pom.xml はこんな感じ。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
        <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
        

DemoApplication.java@EnableHystrixDashboardをつける。

@EnableHystrixDashboard
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

bootstrap.propertiesを同じように作成。

spring.cloud.config.uri=${CONFIG_SERVER:http://localhost:8888}
spring.application.name=hystrix-dashboard

Hystrix-dashboard アプリケーションを起動する。 f:id:mix-juice001:20150923211310p:plain

http://localhost:9999/hystrix.stream にアクセスするとエンドレスで流れる。 f:id:mix-juice001:20150923211519p:plain

http://localhost:9999/hystrix.streamモニタリングできる。 f:id:mix-juice001:20150923211718p:plain

連続してアクセスするとこんな感じ。 f:id:mix-juice001:20150923212007p:plain

Zuul

バックエンドサービスをUIから呼び出す際のproxy。 reservation-client の application.yml に設定

zuul:
  routes:
    reservation-service: /myservice/**

reservation-serviceeurekaに登録したapplication名で、/myservice/** というURL でアクセスしてきたリクエストをreservation-serviceに転送する。

DemoApplication.java@EnableZuulProxyアノテーションをつける。

@EnableFeignClients
@EnableCircuitBreaker
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

起動する。

2015-09-24 10:27:18.741  INFO 7345 --- [           main] o.s.c.n.zuul.web.ZuulHandlerMapping      : Mapped URL path [/myservice/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
2015-09-24 10:27:18.741  INFO 7345 --- [           main] o.s.c.n.zuul.web.ZuulHandlerMapping      : Mapped URL path [/reservation-service/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]

/myservice/** をマッピングしたログが出力される。

http://localhost:9999/myservice/reservationsにアクセスするとreservation-service(バックエンド)のproxyとして機能していることがわかる。

f:id:mix-juice001:20150924103934p:plain

*1:cannot connect to reservation-service

Spring Boot

Josh のセッションをなぞる。

www.youtube.com

http://start.spring.io/でWeb,JPA,Thymeleaf,H2,Actuator,Remote ShellにチェックをいれてDownload

pom.xmlはこんな感じ。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-remote-shell</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

Reservationクラスを追加

@Entity
class Reservation {
    @Id
    @GeneratedValue
    private Long id;
    private String reservationName;

    @Override
    public String toString() {
        return "Reservation{" +
                "id=" + id +
                ", reservationName='" + reservationName + '\'' +
                '}';
    }

    Reservation() {
    }

    public Reservation(String reservationName) {
        this.reservationName = reservationName;
    }

    public Long getId() {
        return id;
    }

    public String getReservationName() {
        return reservationName;
    }
}

ReservationRepositoryインターフェースを追加

@RepositoryRestResource
interface ReservationRepository extends JpaRepository<Reservation, Long> {
    //select * from reservations where reservation_name = :rn
    Collection<Reservation> findByReservationName(@Param("rn") String rn);
}

DemoApplicationクラスにCommandLineRunner を追加。起動時に実行される。

    @Bean
    CommandLineRunner runner(ReservationRepository repository) {
        return args -> {
            Arrays.asList("Josh,Julie,Michael,Peter".split(","))
                    .forEach(n -> repository.save(new Reservation(n)));
            repository.findByReservationName("Julie").forEach(System.out::println);
            repository.findAll().forEach(System.out::println);
        };
    }

実行する。 f:id:mix-juice001:20150923220910p:plain H2に登録されていることがわかる。

RestControllerの追加

@RestController
class ReservationRestController {
    @Autowired
    private ReservationRepository reservationRepository;

    @RequestMapping("/reservations")
    Collection<Reservation> reservations() {
        return reservationRepository.findAll();
    }
}

実行する。

f:id:mix-juice001:20150923221300p:plain f:id:mix-juice001:20150923221428p:plain f:id:mix-juice001:20150923221529p:plain

MvcControllerの追加

@Controller
class ReservationMvcController {
    @Autowired
    ReservationRepository reservationRepository;

    @RequestMapping("/reservations.php")
    String page(Model model) {
        model.addAttribute("reservations", reservationRepository.findAll());
        return "reservations";
    }
}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org" >
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
    <title>Bootiful Reservations</title>
</head>
<body>

<h1>Reservations</h1>

<div th:each="r : ${reservations}" >
    <b th:text="${r.id}">ID</b>
    <span th:text="${r.reservationName}">ReservationName</span>
</div>
</body>
</html>

実行する。

f:id:mix-juice001:20150923221851p:plain

Actuator

http://localhost:8080/metrics にアクセスすると、何回アクセスがあったのか等の情報を得ることができる。 f:id:mix-juice001:20150923222329p:plain

http://localhost:8080/traceにアクセス。 f:id:mix-juice001:20150923222244p:plain

http://localhost:8080/beans f:id:mix-juice001:20150923222438p:plain

http://localhost:8080/env f:id:mix-juice001:20150923222535p:plain

http://localhost:8080/dump f:id:mix-juice001:20150923222730p:plain

http://localhost:8080/mappings f:id:mix-juice001:20150923222736p:plain

jconsole

jconsole で見ることもできる

$ jconsole

f:id:mix-juice001:20150923223010p:plain "insecure connect"をクリックして f:id:mix-juice001:20150923223242p:plain f:id:mix-juice001:20150923223346p:plain

jmc

$ jmc

f:id:mix-juice001:20150924092013p:plain

Remote Shell

コンソールにShellでアクセスする場合のpasswordが出力されている。 f:id:mix-juice001:20150923223545p:plain

$ $ ssh -p 2000 user@127.0.0.1
Password authentication
Password:
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v1.2.6.RELEASE) on phantom.local
> help
Try one of these commands with the -h or --help switch:

NAME       DESCRIPTION
autoconfig Display auto configuration report from ApplicationContext
beans      Display beans in ApplicationContext
cron       manages the cron plugin
dashboard  a monitoring dashboard
egrep      search file(s) for lines that match a pattern
endpoint   Invoke actuator endpoints
env        display the term env
filter     a filter for a stream of map
java       various java language commands
jmx        Java Management Extensions
jul        java.util.logging commands
jvm        JVM informations
less       opposite of more
mail       interact with emails
man        format and display the on-line manual pages
metrics    Display metrics provided by Spring Boot
shell      shell related command
sleep      sleep for some time
sort       sort a map
system     vm system properties commands
thread     JVM thread commands
help       provides basic help
repl       list the repl or change the current repl

>
> endpoint list
requestMappingEndpoint
environmentEndpoint
healthEndpoint
beansEndpoint
infoEndpoint
metricsEndpoint
traceEndpoint
dumpEndpoint
autoConfigurationAuditEndpoint
configurationPropertiesReportEndpoint

> endpoint invoke healthEndpoint
{status=UP, diskSpace={status=UP, free=39115100160, threshold=10485760}, db={status=UP, database=H2, hello=1}}

>
> metrics

f:id:mix-juice001:20150923224709p:plain

> dashboard

f:id:mix-juice001:20150923224723p:plain

HealthIndicator を追加

    @Bean
    HealthIndicator gotoHealthIndecator() {
        return () -> Health.status("I <3 Chicago!").build();
    }

application.propertiesに設定を追加

server.port=8000
management.context-path=/admin
management.port=9000

http://localhost:8000/reservations

f:id:mix-juice001:20150923225045p:plain

http://localhost:9000/admin/health

f:id:mix-juice001:20150923225123p:plain

f:id:mix-juice001:20150923225322p:plain

make jar not war

$ mvn clean install
$ java -jar demo-0.0.1-SNAPSHOT.jar
$ export SERVER_PORT=8010
$ java -Dserver.port=8020 -jar demo-0.0.1.SNAPSHOT.jar 

書籍の電子化(断捨離)

f:id:mix-juice001:20150912181508j:plain


最近、本をカバンに入れて持ち歩くのが辛くなってきたので、
Kindleを買ったついでに電子化してみた。

気になったときにいつでも読めるようにするのが最終目標。

スキャン業者選び

スキャン業者を検索していると、どこも早くて1ヶ月〜長いところだと3ヶ月納品まで時間がかかることがわかる。
あと、多くの業者が350ページで1冊分として、何冊分になるか数えて、注文するスタイル。

数えるのがとってもメンドくさいので、何ページでも1冊150円の電天というところに注文。
電天 スキャン代行サービス

注文

オーダーサイトにしたがって入力し完了ボタンを押すだけ。今回は36冊注文した。
注文後、クレジットカードで決済し、ダンボールに本を詰めて指定された住所に送付する。
「本が届いた」というメールを受け取ってから1週間で「電子化が完了した」というメールを受け取った。

納品

サイトでは18営業日となっていたが、ずいぶん早かったなと思いつつダウンロードする。

PDF

ファイル名を入力して納品してくれるサービスをしてくれているのだけど、
Macで見ると見事に文字化けしていた。

中身はパソコンで見たときは読みやすくて満足!って思った。
f:id:mix-juice001:20150912183627p:plain

けど、Kindleでみると傾きが気になる & パソコンでみるより見にくい。
f:id:mix-juice001:20150912221208p:plain

O'reillyで買ったpdfが普通にKindleで読めたので、自炊本も読めると思っていたけど、
思っていたのと違う出来栄え。

あとから
ググるKindleで自炊pdfは読みにくいとの記事がたくさん見つかった。
部屋が片付いたのと、まぁ読めなくはないからよしとするか。

CentOS7 minimal で Kubernetes ~part 5~

GoogleCloudPlatformでGuestBookを動かしてみる

(もうCentOSじゃありません。)

gcp に container cluseter を作成
$ gcloud beta container clusters create guestbook --num-nodes 3
Creating cluster guestbook...done.
Created [https://container.googleapis.com/v1/projects/kube-1043/zones/asia-east1-b/clusters/guestbook].
kubeconfig entry generated for guestbook.
NAME       ZONE          MASTER_VERSION  MASTER_IP        MACHINE_TYPE   STATUS
guestbook  asia-east1-b  1.0.3           130.211.246.179  n1-standard-1  RUNNING
クラスタを確認する。
$ gcloud beta container clusters list
NAME       ZONE          MASTER_VERSION  MASTER_IP        MACHINE_TYPE   STATUS
guestbook  asia-east1-b  1.0.3           130.211.246.179  n1-standard-1  RUNNING
nodeの確認
$ kubectl get nodes
NAME                               LABELS                                                    STATUS
gke-guestbook-32b71c9c-node-2sd6   kubernetes.io/hostname=gke-guestbook-32b71c9c-node-2sd6   Ready
gke-guestbook-32b71c9c-node-88j1   kubernetes.io/hostname=gke-guestbook-32b71c9c-node-88j1   Ready
gke-guestbook-32b71c9c-node-kgjq   kubernetes.io/hostname=gke-guestbook-32b71c9c-node-kgjq   Ready
service はまだない。(kubernetesのサービスのみ)
$ kubectl get services
NAME         LABELS                                    SELECTOR   IP(S)          PORT(S)
kubernetes   component=apiserver,provider=kubernetes   <none>     10.215.240.1   443/TCP
redis のコントローラーを作成
$ kubectl create -f redis-master-controller.json
replicationcontrollers/redis-master
pod の確認
$ kubectl get -o wide pods
NAME                 READY     STATUS    RESTARTS   AGE       NODE
redis-master-x8dsk   1/1       Running   0          2m        gke-guestbook-32b71c9c-node-2sd6
redis のサービス作成
$ kubectl create -f redis-master-service.json
services/redis-master
サービス確認
$ kubectl get service -l name=redis-master
NAME           LABELS              SELECTOR            IP(S)            PORT(S)
redis-master   name=redis-master   name=redis-master   10.215.243.107   6379/TCP
コントローラー作成
$ kubectl create -f redis-worker-controller.json
replicationcontrollers/redis-slave
サービス作成
$ kubectl create -f redis-worker-service.json
services/redis-slave
frontend のコントローラー作成
$ kubectl create -f frontend-controller.json
replicationcontrollers/frontend
サービス作成
$ kubectl create -f frontend-service.json
services/frontend
サービス確認
$ kubectl get services
NAME           LABELS                                    SELECTOR            IP(S)            PORT(S)
frontend       name=frontend                             name=frontend       10.215.253.56    80/TCP
kubernetes     component=apiserver,provider=kubernetes   <none>              10.215.240.1     443/TCP
redis-master   name=redis-master                         name=redis-master   10.215.243.107   6379/TCP
redis-slave    name=redis-slave                          name=redis-slave    10.215.248.252   6379/TCP
ロードバランサーIPアドレス確認
$ kubectl describe services frontend | grep "LoadBalancer Ingress"
LoadBalancer Ingress:	130.211.249.217
サービスを確認してもロードバランサーIPアドレスが表示される
$ kubectl get services
NAME           LABELS                                    SELECTOR            IP(S)             PORT(S)
frontend       name=frontend                             name=frontend       10.215.253.56     80/TCP
                                                                             130.211.249.217
kubernetes     component=apiserver,provider=kubernetes                 10.215.240.1      443/TCP
redis-master   name=redis-master                         name=redis-master   10.215.243.107    6379/TCP
redis-slave    name=redis-slave                          name=redis-slave    10.215.248.252    6379/TCP


ブラウザでアクセス
http://130.211.249.217
f:id:mix-juice001:20150823085245p:plain

Kubernetes のweb ui にアクセスしてみる

Kubernetes User Interface

ID/PWが必要なので確認する。
f:id:mix-juice001:20150823090521p:plain

$ gcloud beta container clusters list --zone asia-east1-b
NAME       ZONE          MASTER_VERSION  MASTER_IP        MACHINE_TYPE   STATUS
guestbook  asia-east1-b  1.0.3           130.211.246.179  n1-standard-1  RUNNING
$ 
$ gcloud beta container clusters describe guestbook
clusterIpv4Cidr: 10.212.0.0/14
createTime: '2015-08-21T19:59:51+00:00'
currentMasterVersion: 1.0.3
currentNodeVersion: 1.0.3
endpoint: 130.211.246.179
initialClusterVersion: 1.0.3
initialNodeCount: 3
instanceGroupUrls:
- https://www.googleapis.com/replicapool/v1beta2/projects/kube-1043/zones/asia-east1-b/instanceGroupManagers/gke-guestbook-32b71c9c-group
loggingService: logging.googleapis.com
masterAuth:
  clientCertificate: LS0t…S0tLQo=
  clientKey: LS0tS…S0tLQo=
  clusterCaCertificate: LS0t…LS0K
  password: 'password'
  username: 'username'
monitoringService: monitoring.googleapis.com
name: guestbook
network: default
nodeConfig:
  diskSizeGb: 100
  machineType: n1-standard-1
  oauthScopes:
  - https://www.googleapis.com/auth/compute
  - https://www.googleapis.com/auth/devstorage.read_only
  - https://www.googleapis.com/auth/logging.write
  - https://www.googleapis.com/auth/monitoring
nodeIpv4CidrSize: 24
selfLink: https://container.googleapis.com/v1/projects/162560360030/zones/asia-east1-b/clusters/guestbook
servicesIpv4Cidr: 10.215.240.0/20
status: RUNNING
zone: asia-east1-b

https://130.211.246.179/ui
にアクセスすると
https://130.211.246.179/api/v1/proxy/namespaces/kube-system/services/kube-ui/#/dashboard/
にリダイレクトされて
f:id:mix-juice001:20150822071904p:plain

CentOS7 minimal で Kubernetes ~part 4~

GuestBookを試す。github.com


redis master を起動

redis-master-controller.yamlの作成
apiVersion: v1
kind: ReplicationController
metadata:
  name: redis-master
  labels:
    name: redis-master
spec:
  replicas: 1
  selector:
    name: redis-master
  template:
    metadata:
      labels:
        name: redis-master
    spec:
      containers:
      - name: master
        image: redis
        ports:
        - containerPort: 6379
controller 起動
$ kubectl create -f redis-master-controller.yaml
replication controller  の確認
$ kubectl get rc
CONTROLLER     CONTAINER(S)   IMAGE(S)                                    SELECTOR            REPLICAS
redis-master   master         redis                                       name=redis-master   1
pod の確認
$ kubectl get pods
NAME                 READY     STATUS    RESTARTS   AGE
redis-master-n2tok   1/1       Running   0          46m

redis slave の起動

read only のredisを作成する

redis-slave-controller.yaml の作成
apiVersion: v1
kind: ReplicationController
metadata:
  name: redis-slave
  labels:
    name: redis-slave
spec:
  replicas: 2
  selector:
    name: redis-slave
  template:
    metadata:
      labels:
        name: redis-slave
    spec:
      containers:
      - name: worker
        image: kubernetes/redis-slave:v2
        ports:
        - containerPort: 6379
controller 起動
$ kubectl create -f redis-slave-controller.yaml

redis master,slave のserviceを起動

redis-master-service.yaml の作成
apiVersion: v1
kind: Service
metadata:
  name: redis-master
  labels:
    name: redis-master
spec:
  ports:
    # the port that this service should serve on
  - port: 6379
    targetPort: 6379
  selector:
    name: redis-master
service起動
$ kubectl create -f redis-master-service.yaml
redis-slave-service.yaml の作成
apiVersion: v1
kind: Service
metadata:
  name: redis-slave
  labels:
    name: redis-slave
spec:
  ports:
    # the port that this service should serve on
  - port: 6379
  selector:
    name: redis-slave
service起動
$ kubectl create -f redis-slave-service.yaml

Frontend の pod 作成

frontend-controller.yaml の作成
apiVersion: v1
kind: ReplicationController
metadata:
  name: frontend
  labels:
    name: frontend
spec:
  replicas: 3
  selector:
    name: frontend
  template:
    metadata:
      labels:
        name: frontend
    spec:
      containers:
      - name: php-redis
        image: kubernetes/example-guestbook-php-redis:v2
        ports:
        - containerPort: 80
$ kubectl create -f frontend-controller.yaml
frontend-service.yaml の作成

今回はLoadBalancerがないので、
外部ネットワークに公開するために

type: NodePort

を指定
Services in Kubernetesを参考に。

apiVersion: v1
kind: Service
metadata:
  name: frontend
  labels:
    name: frontend
spec:
  # if your cluster supports it, uncomment the following to automatically create
  # an external load-balanced IP for the frontend service.
#  type: LoadBalancer
  type: NodePort
  ports:
    # the port that this service should serve on
    - port: 80
      targetport: 80
      protocol: TCP
  selector:
    name: frontend
Frontend Service の起動
$ kubectl create -f frontend-service.yaml
You have exposed your service on an external port on all nodes in your
cluster.  If you want to expose this service to the external internet, you may
need to set up firewall rules for the service port(s) (tcp:30621) to serve traffic.

See http://releases.k8s.io/HEAD/docs/user-guide/services-firewalls.md for more details.
services/frontend

http://192.168.1.18:30621
f:id:mix-juice001:20150817185543p:plain