逮捕で学ぶ認証認可

最近Twitterで認証認可を逮捕で例える話をしていて、結構しっくり来たので逮捕で例えながらざっくり認証認可について書いてみようと思って飽きたところまでおいておきます。

定義

認証=囚人が誰であるかを特定すること(囚人番号009はおれきゅーみたいなかんじ
認可=面会許可

面会フロー

面会をするためには、その囚人に面会できるかの確認をする必要がある。
ここでは模範囚のみ面会ができるとして、面会したい囚人が模範囚であれば面会ができる。これが認可にあたる。ただ、大体の場合は認証と認可はセットになっていることが多い。なぜならその人が模範囚であるかを確認するためには「面会したい囚人が誰であるか」を知る必要があるからだ。
とはいえ、模範囚が入ってるエリアとそうじゃないエリアが分かれていれば認証をしなくても面会はできそう。
現実にはそんなフローはダメそうだけどw記録残さないといけないだろうし。

このままoauthとかの説明書こうとしたけどあきた。
気が向いたら書く。

SpringCloudで遊ぶ 3日目

SpringBootAdminを入れる

SpringBootAdminを入れてみる。Eureka経由でサービスを見つけてくれるようになる。

project("admin") {
    dependencies {
        implementation 'de.codecentric:spring-boot-admin-starter-server'
        implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    }
}
@Configuration
@EnableAutoConfiguration
@EnableAdminServer
public class SpringBootAdminApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootAdminApplication.class, args);
    }
}
eureka:
  instance:
    leaseRenewalIntervalInSeconds: 10
    health-check-url-path: /actuator/health
    metadata-map:
      startup: ${random.int}    #needed to trigger info and endpoint update after restart
    prefer-ip-address: true
  client:
    registryFetchIntervalSeconds: 5
    serviceUrl:
      defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: ALWAYS
spring:
  application:
    name: admin
version: '3.7'

services:
  hello-app:
    image: net.orekyuu/hello:0.0.5
    ports:
      - 8080:8080
    deploy:
      replicas: 2
    environment:
      EUREKA_URI: http://eureka:8761/eureka
  eureka:
    image: net.orekyuu/eureka:0.0.5
    ports:
      - 8761:8761
    deploy:
      replicas: 1
  admin:
    image: net.orekyuu/admin:0.0.5
    ports:
      - 18080:8080
    environment:
      EUREKA_URI: http://eureka:8761/eureka

デプロイ…! f:id:orekyuu:20190912203857p:plain よし!動いてる!

API Gatewayを作ってみる

API Gatewayでhello-appを叩けるようにしてみる

project(':api-gateway') {
    dependencies {
        implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
        implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
       // actuatorはsubprojectsのdependenciesに移動した。どうせ全部のプロジェクトで必要だし
    }
}
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("hello_app_route", r -> r
                        .path("/hello")
                        .uri("http://hello-app:8080/hello"))
                .build();
    }
}

ガッっとアプリケーションコードを書く。
RouteLocatorBuilderでメソッドチェーンしながら組み立てていくっぽい。/helloにアクセスが来たらkubernetes内のhello-appiの/helloにプロキシするようにした。
hello-appの名前解決はeurekaでやってくれる。便利~!

spring:
  application:
    name: api-gateway
eureka:
  instance:
    leaseRenewalIntervalInSeconds: 10
    health-check-url-path: /actuator/health
    metadata-map:
      startup: ${random.int}    #needed to trigger info and endpoint update after restart
    prefer-ip-address: true
  client:
    registryFetchIntervalSeconds: 5
    serviceUrl:
      defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: ALWAYS

application.ymlはコピペ。spring.application.nameだけ変えてる。似たような設定が散らばるのやだなー。
一応ルーティングの設定はapplication.ymlでも書けるらしいけど、個人的にはJavaConfigのほうがコンパイルできるし補完も効くのですき。

version: '3.7'

services:
  hello-app:
    image: net.orekyuu/hello:0.0.5
    deploy:
      replicas: 2
    environment:
      EUREKA_URI: http://eureka:8761/eureka
  eureka:
    image: net.orekyuu/eureka:0.0.5
    ports:
      - 8761:8761
    deploy:
      replicas: 1
  admin:
    image: net.orekyuu/admin:0.0.5
    ports:
      - 18080:8080
    environment:
      EUREKA_URI: http://eureka:8761/eureka
  api-gateway:
    image: net.orekyuu/api-gateway:0.0.5
    environment:
      EUREKA_URI: http://eureka:8761/eureka
    ports:
      - 8080:8080

最後にapi-gatewayの設定を追加して8080で受けるようにする。もともとhello-appが使っていたけど、直接叩くことはなくなったのでportsは削除。
全部api-gatewayを通してリクエス

デプロイして8080を確認するとhello-appのときと変わらない画面が見える。
結局API Gatewayが使われているかわからん!
Adminを見てみる。

f:id:orekyuu:20190912210949p:plain ちゃんとリクエストきてる!やったー!

kubernetesに入門してみる 2日目

ImagePullBackOffで起動しない解決編

バージョンをlatestにしていると常にdocker registryにpullしてしまうみたい。
ということでjibでイメージを作るときに適当にタグを付けてあげる

    jib {
        to {
            image = "net.orekyuu/" + project.name
            tags = ["0.0.1"] // とりあえず固定値で渡してみる
        }
        from {
            image = "openjdk:12"
        }
    }

docker-compose.yml側にも変更

version: '3.7'

services:
  hello-app:
    image: net.orekyuu/hello:0.0.1
    ports:
      - 8080:8080

デプロイしてみる!

D:\repos\spring-illust-service>docker stack deploy --orchestrator=kubernetes -c docker-compose.yml hello
Waiting for the stack to be stable and running...
hello-app: Ready                [pod status: 1/1 ready, 0/1 pending, 0/1 failed]

Stack hello is stable and running


D:\repos\spring-illust-service>kubectl get all
NAME                             READY   STATUS    RESTARTS   AGE
pod/hello-app-6f985844fb-n8vrb   1/1     Running   0          16s

NAME                          TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
service/hello-app             ClusterIP      None           <none>        55555/TCP        16s
service/hello-app-published   LoadBalancer   10.97.45.121   localhost     8080:31557/TCP   15s
service/kubernetes            ClusterIP      10.96.0.1      <none>        443/TCP          21h

NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/hello-app   1/1     1            1           16s

NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/hello-app-6f985844fb   1         1         1       16s

なんだか動いてそう…!
localhost:8080で動いてるはずなのでアクセス

f:id:orekyuu:20190911200429p:plain やったー!

replicateしてみる

hello appを増やしてみる

version: '3.7'

services:
  hello-app:
    image: net.orekyuu/hello:0.0.1
    ports:
      - 8080:8080
    deploy:
      replicas: 2

もっかいデプロイ!

D:\repos\spring-illust-service>docker stack deploy --orchestrator=kubernetes -c docker-compose.yml hello
Waiting for the stack to be stable and running...
hello-app: Ready                [pod status: 1/1 ready, 0/1 pending, 0/1 failed]

Stack hello is stable and running


D:\repos\spring-illust-service>docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
958fbfb96101        f61f158e7339        "java -cp /app/resou…"   3 seconds ago       Up 1 second                             k8s_hello-app_hello-app-6f985844fb-8qrlz_default_8ad9e77c-d484-11e9-99f8-0
0155da07b15_0
e9165ba15192        f61f158e7339        "java -cp /app/resou…"   3 minutes ago       Up 3 minutes                            k8s_hello-app_hello-app-6f985844fb-n8vrb_default_2ad820d9-d483-11e9-99f8-0
0155da07b15_1

ちゃんと2台になってる。面白い~!

Eurekaを入れる

Spring Cloudを触ってみたいモチベーションがあったのでまずはEurekaから入れてみる
新しいプロジェクトを作ってピャッっと書く。アプリケーションのコードはこんな感じ

project(":eureka") {
    dependencies {
        implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
        // ↓JDK9以降だと自分でJAXB周り入れておかないとJigsawの影響とかで起動できない…
        implementation 'javax.xml.bind:jaxb-api:+'
        implementation 'com.sun.xml.bind:jaxb-impl:+'
        implementation 'org.glassfish.jaxb:jaxb-runtime:+'
        implementation 'javax.activation:activation:+'
    }
}
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryApplication {
    public static void main(String[] args) {
        SpringApplication.run(DiscoveryApplication.class, args);
    }
}
server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

自分自身を登録しないようにregister-with-eurekaとかはfalseにしておくと良いらしい。って書いてあった
jibでビルドしてdocker-compose.ymlに追記してあげる

version: '3.7'

services:
  hello-app:
    image: net.orekyuu/hello:0.0.1
    ports:
      - 8080:8080
    deploy:
      replicas: 2
  eureka:
    image: net.orekyuu/eureka:0.0.1
    ports:
      - 8761:8761
    deploy:
      replicas: 1

localhost:8761にアクセスするとダッシュボードが見れる f:id:orekyuu:20190911211027p:plain

ちょっと寄り道してJAXB周りでエラー出た話

一発目はJAXB周りに気付かず起動しないなー?になっていた。
kubectl get allを打つとコンテナの状態が見れるので叩いてみる。

D:\repos\spring-illust-service>kubectl get all
NAME                             READY   STATUS    RESTARTS   AGE
pod/eureka-586c7cbc48-mrb2h      0/1     Error     3          93s
pod/hello-app-6f985844fb-4stpb   1/1     Running   0          93s
pod/hello-app-6f985844fb-wl4zn   1/1     Running   0          93s

NAME                          TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
service/eureka                ClusterIP      None           <none>        55555/TCP        93s
service/eureka-published      LoadBalancer   10.109.74.59   localhost     8761:30876/TCP   92s
service/hello-app             ClusterIP      None           <none>        55555/TCP        92s
service/hello-app-published   LoadBalancer   10.96.5.72     localhost     8080:30280/TCP   93s
service/kubernetes            ClusterIP      10.96.0.1      <none>        443/TCP          22h

NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/eureka      0/1     1            0           93s
deployment.apps/hello-app   2/2     2            2           93s

NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/eureka-586c7cbc48      1         1         0       93s
replicaset.apps/hello-app-6f985844fb   2         2         2       93s

eurekaがエラーで再起動くりかえしてるぽい。原因は外からじゃわからないのでログをみたい。
kubectl logs [pod名]でログが取れるので打ってみる

D:\repos\spring-illust-service>kubectl logs pod/eureka-586c7cbc48-mrb2h
2019-09-11 11:52:44.502  INFO 1 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.auto
configure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$8ccca102] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.8.RELEASE)

2019-09-11 11:52:44.703  INFO 1 --- [           main] net.orekyuu.eureka.DiscoveryApplication  : No active profile set, falling back to default profiles: default
2019-09-11 11:52:45.492  WARN 1 --- [           main] o.s.boot.actuate.endpoint.EndpointId     : Endpoint ID 'service-registry' contains invalid characters, please migrate to a valid format.
2019-09-11 11:52:45.840  INFO 1 --- [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=10faa10b-835d-3f55-bd87-0a6398f3b9c7
...
java.lang.NoClassDefFoundError: com/sun/istack/FinalArrayList
        at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:249) ~[jaxb-impl-2.4.0-b180830.0438.jar:2.4.0-b180830.0438]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
        at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:208) ~[jaxb-api-2.4.0-b180830.0359.jar:2.3.0]
        at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:166) ~[jaxb-api-2.4.0-b180830.0359.jar:2.3.0]

みたいな感じでjaxb周りでコケてることがわかった。

hello-appにeureka-clientを入れる

eureka-clientを入れてeurekaに登録するようにする

project(":hello") {
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-web'
        // ↓新しく追加した
        implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    }
}
spring:
  application:
    name: hello-app
eureka:
  client:
    service-url:
      defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}

登録する先は環境変数で渡せるようにしておく。イメージ作り直すのいやだしね。

docker-composeを書いてあげる

version: '3.7'

services:
  hello-app:
    image: net.orekyuu/hello:0.0.3
    ports:
      - 8080:8080
    deploy:
      replicas: 2
    environment:
      EUREKA_URI: http://10.104.224.80:8761/eureka
  eureka:
    image: net.orekyuu/eureka:0.0.4
    ports:[f:id:orekyuu:20190911213036p:plain]
      - 8761:8761
    deploy:
      replicas: 1

ipアドレスはkubectl get allを叩いてservice/eureka-publishedのIPを入れた。いや、良くないのはわかっているけどとりあえず動かして楽しくなりたかった…!
あとで正しい方法は調べるとしてこれでdeployしてみる。 f:id:orekyuu:20190911213036p:plain ちゃんとインスタンスが2つ登録されていることが確認できたので満足!
明日はAPI Gatewayとか試してみよ~

kubernetesに入門してみる

spring cloudで遊んでみよーとおもったので、まずはkubernetesから触ってみることにした。
触ってきたメモ的な感じで残してく。

環境

Kubernetesを有効化

DockerのSettingsを開いてKubernetesからぽちっと有効化してあげる
f:id:orekyuu:20190910222346p:plain

Applyするとインストールが始まるので終わるまでしばらく待つ
f:id:orekyuu:20190910222452p:plain

終わったらとりあえずkubectlコマンドが使えるはずなので kubectl version あたりを叩いて確認するとよき

動かしてみる

とりあえず簡単なSpring Bootアプリケーションをデプロイしてみる。
こんなかんじのアプリケーションを作って

@SpringBootApplication
@Controller
public class HelloApplication {

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

    @GetMapping
    @ResponseBody
    public String hello() {
        return "hello " + LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME);
    }
}

Jibを使ってイメージを作る
デフォルトのベースイメージだとJDK12サポートしてないからベースイメージにはopenjdk:12を使うよ
あとたくさんアプリ作りそうなのでマルチプロジェクト構成にしておく

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.1.8.RELEASE' apply false
    id 'io.spring.dependency-management' version '1.0.8.RELEASE' apply false
    id 'com.google.cloud.tools.jib' version '1.5.1' apply false
}

repositories {
    jcenter()
}

subprojects {
    apply plugin: 'org.springframework.boot'
    apply plugin: 'io.spring.dependency-management'
    apply plugin: 'com.google.cloud.tools.jib'

    sourceCompatibility = 12

    def defaultEncoding = 'UTF-8'
    [AbstractCompile, Javadoc].each {
        tasks.withType(it).each { it.options.encoding = defaultEncoding }
    }

    repositories {
        jcenter()
    }

    jib {
        to {
            image = "net.orekyuu/" + project.name
        }
        from {
            image = "openjdk:12" // JDK12を使いたい場合は明示的にベースイメージ設定しておこう
        }
    }
}

gradlew :hello:jibDockerBuild を叩いてイメージを作る。Jib便利~。

D:\repos\spring-illust-service>gradlew :hello:jibDockerBuild

Containerizing application to Docker daemon as net.orekyuu/hello...
The base image requires auth. Trying again for openjdk:12...

Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, net.orekyuu.hello.HelloApplication]

Built image to Docker daemon as net.orekyuu/hello
Executing tasks:
[==============================] 100.0% complete


BUILD SUCCESSFUL in 5m 52s
2 actionable tasks: 1 executed, 1 up-to-date

ビルドできたっぽいのでコンテナを動かしてみて動くことを確認
docker run -p 8080:8080 net.orekyuu/hello
ブラウザでlocalhost:8080を見るとちゃんとメッセージが見れたのでヨシ!

docker-compose.ymlでデプロイする

compose-on-kubernetesとやらでkubernetesにdocker-compose.ymlの定義に従ってコンテナをデプロイできるっぽいので使ってみる。とりあえず動かしたいしね。
とりあえずhelloアプリだけ起動するようにdocker-compose.ymlを用意。

version: '3.7'

services:
  hello-app:
    image: net.orekyuu/hello:latest
    ports:
      - 8080:8080

つぎにデプロイはdocker stack deployでできるらしい。docker-swarmと一緒だけどorchestratorオプションでkubernetesを指定できるらしい。なるほど。
stack名はhelloでデプロイ

D:\repos\spring-illust-service>docker stack deploy --orchestrator=kubernetes -c docker-compose.yml hello
Waiting for the stack to be stable and running...
hello-app: Pending              [pod status: 0/1 ready, 1/1 pending, 0/1 failed]

起動待ちがやけに長い…?

D:\repos\spring-illust-service>kubectl get all
NAME                             READY   STATUS             RESTARTS   AGE
pod/hello-app-557fc5fb78-b28md   0/1     ImagePullBackOff   0          5m33s

NAME                          TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
service/hello-app             ClusterIP      None           <none>        55555/TCP        5m33s
service/hello-app-published   LoadBalancer   10.99.53.170   localhost     8080:32717/TCP   5m32s
service/kubernetes            ClusterIP      10.96.0.1      <none>        443/TCP          71m

NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/hello-app   0/1     1            0           5m33s

NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/hello-app-557fc5fb78   1         1         0       5m33s

pod/hello-app-...のSTATUSがImagePullBackOffのままずっと動いてないっぽい。
おそらくローカルのイメージが見えてなくてpullをずっと待ってるのかな。明日は適当なレジストリ立てて試してみよ~