Cloud-native applications with Java and Kubernetes
Hello, ${username}! ● Senior Java developer @ DataArt ● Working with Java for 5+ years ● Background in networking and game development, mostly C/C++ ● Interested in everything DevOps related since started working with Java ● https://www.facebook.com/yegor.volkov.x64
Evolution of Environments
Evolution of Environments Bare metal Virtualization (HW) Virtualization (SW) OS Container Engine JVM Your app 1. Bare metal, VM software-based, VM hardware-based 2. IaaS, PaaS, SaaS 3. Amazon Services, Amazon EC2, Amazon ECS 4. Google Services, Google App Engine, Google Kubernetes Engine 5. CloudFoundry, Heroku, etc.
Evolution of Environments my-project-0.0.1-SNAPSHOT.jar app-build-2541.war ./exploded jar/ ./exploded war/ Java developers only care about these!
Evolution of Environments … and sometimes about these ... java version "10.0.2" 2018-07-17 Java(TM) SE Runtime Environment 18.3 (build 10.0.2+13) Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.2+13, mixed mode) openjdk version "9-internal" OpenJDK Runtime Environment (build 9-internal+0-2016-04-14-195246.buildd.src) OpenJDK 64-Bit Server VM (build 9-internal+0-2016-04-14-195246.buildd.src, mixed mode)
Evolution of Environments 1. As long as the JRE is the same, that we develop against - everything should be fine 2. As long as the Servlet container version is the same, that we develop against - everything should be fine 3. As long as the RDBMS version is the same, that we develop against - everything should be fine 4. …
Evolution of Environments ● We need to solidify versions of everything (JVM, JDK/JRE, servers, servlet containers, databases, etc) ● There’s no good way to limit Ops people from screwing everything up ● This should be done by software, not people ● We already have solutions for similar problems in Java world - dependency managers!
Dockerization: starting small ● Docker to the rescue! ● Make a snapshot of your environment ● Redistribute and run anywhere “©” ● Bake internal custom service versions and builds
Dockerization: starting small
Dockerization: starting small FROM openjdk:10-jre VOLUME /tmp COPY target/project1-0.0.1-SNAPSHOT.jar app.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] gradlew clean build && docker build -t starships . docker run -p 3306:3306 -v C:/dev/docker/mysql-project1:/var/lib/mysql --env MYSQL_ROOT_PASSWORD=s3cur1ty@maks --env MYSQL_DATABASE=space --name mysql-project1 --network space-net mysql:5.6 docker run -p 8443:8443 --name starships --network space-net starships:latest Dockerfile: Run with:
Dockerization: starting small
Dockerization: starting small
Dockerization: starting small
Dockerization: next obvious step version: "3" services: starships: build: context: . dockerfile: Dockerfile image: "starships:latest" ports: - "8443:8443" networks: - space-net depends_on: - mysql mysql: hostname: "mysql" image: "mysql:5.6" ports: - "3306:3306" volumes: - mysql_volume:/var/lib/mysql environment: - MYSQL_ROOT_PASSWORD=s3cur1ty@maks - MYSQL_DATABASE=space networks: space-net: networks: space-net: volumes: mysql_volume:
Dockerization: next obvious step gradlew clean build docker build -t starships . docker-compose up Run with:
Dockerization: next obvious step
Dockerization: next obvious step
Diving into Kubernetes
Diving into Kubernetes
Diving into Kubernetes kompose convert -f docker-compose.yaml docker-compose.yaml mysql-deployment.yaml mysql-service.yaml mysql-pvc.yaml starships-deployment.yaml starships-service.yaml
Diving into Kubernetes $ kubectl get all NAME READY STATUS RESTARTS AGE pod/mysql-646fdcd6b9-8x9f7 1/1 Running 0 3h pod/starships-68585d5f5b-v5qtr 1/1 Running 0 2m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12d service/mysql ClusterIP 10.102.90.77 <none> 3306/TCP 3h service/starships ClusterIP 10.98.205.45 <none> 8443/TCP 2m NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deployment.apps/mysql 1 1 1 1 3h deployment.apps/starships 1 1 1 1 2m
Diving into Kubernetes Take advantage of Kubernetes features: secrets and configuration! kubectl create secret generic spacekey --from-file=src/main/resources/spacekey.p12 Update app configuration: server.ssl.key-store=/app/spacekey.p12 server.ssl.key-store-password=spac3ke1 server.ssl.key-alias=spacecert
Diving into Kubernetes Adjust YAML file to mount the secret: spec: containers: ... volumeMounts: - mountPath: "/app" name: spacekey readOnly: true ... volumes: - name: spacekey secret: secretName: spacekey
Diving into Kubernetes So far we have: ● MySQL service ● MySQL persistent volume claim ● MySQL deployment ● Application service ● Application deployment ● SSL certificate secret
Playing with Kubernetes Expose the application: kubectl expose deployment starships --port=8443 --protocol=TCP --target-port=8443 --type=LoadBalancer --name=starships-lb Examine new service: kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12d mysql ClusterIP 10.102.90.77 <none> 3306/TCP 3h starships-lb LoadBalancer 10.100.175.25 localhost 8443:32699/TCP 3h
Playing with Kubernetes Access the application: https://localhost:8443/
Playing with Kubernetes Inject config from ConfigMap: kubectl create configmap starship-config --from-file=src/main/resources/application-k8s-cm.properties Mount it in the pod: volumeMounts: - mountPath: /app/application-k8s.properties name: app-config subPath: application-k8s-cm.properties volumes: - name: spacekey secret: secretName: spacekey - name: app-config configMap: name: starship-config
Playing with Kubernetes Restart pods: kubectl scale --replicas=0 deployment.apps/starships kubectl scale --replicas=1 deployment.apps/starships Access the application: https://localhost:8443/
Playing with Kubernetes Scale it up: kubectl scale --replicas=2 deployment.apps/starships
Playing with Kubernetes wollf@WOLLF-TYPHOON MINGW64 ~/IdeaProjects/project1 (master-february-kubernetes) $ kubectl get all NAME READY STATUS RESTARTS AGE pod/mysql-646fdcd6b9-8x9f7 1/1 Running 0 2h pod/starships-68585d5f5b-pcdcg 1/1 Running 0 2m pod/starships-68585d5f5b-v5qtr 1/1 Running 0 3m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12d service/mysql ClusterIP 10.102.90.77 <none> 3306/TCP 2h service/starships-lb LoadBalancer 10.100.175.25 localhost 8443:32699/TCP 2h ...
Playing with Kubernetes ... NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deployment.apps/mysql 1 1 1 1 2h deployment.apps/starships 2 2 2 2 2h NAME DESIRED CURRENT READY AGE replicaset.apps/mysql-646fdcd6b9 1 1 1 2h replicaset.apps/starships-68585d5f5b 2 2 2 15m
Back to the surface Things we added: ● App config via ConfigMap ● Environment variable populated with current pod name ● Load balanced service
Back to the surface
Stateful services in Kubernetes Current database configuration: ● Regular ReplicaSet (as for the app) ● Will lose data if re-scheduled ● Credentials are insecure ● Configuration is hard-coded
Stateful services in Kubernetes Moving MySQL configuration and credentials outside of deployment YAML: apiVersion: v1 kind: Secret metadata: name: mysql-credentials data: username: dW5pdmVyc2U= password: ZDRrZlg4QndBQ3F4Rkt0Mw== root-password: YWRtaW4=
Stateful services in Kubernetes MySQL master and slave config: apiVersion: v1 kind: ConfigMap metadata: name: mysql-config data: mysql_database: space master.cnf: | # Apply this config only on the master. [mysqld] log-bin slave.cnf: | # Apply this config only on slaves. [mysqld] super-read-only
Stateful services in Kubernetes Use DB configuration from ConfigMap: containers: - name: mysql env: - name: MYSQL_DATABASE valueFrom: configMapKeyRef: name: mysql-config key: mysql_database - name: MYSQL_ALLOW_EMPTY_PASSWORD value: "1" Use credentials configuration from ConfigMap: containers: - name: mysql env: - name: MYSQL_PASSWORD valueFrom: secretKeyRef: name: mysql-credentials key: password - name: MYSQL_USER valueFrom: secretKeyRef: name: mysql-credentials key: username
Stateful services in Kubernetes Use DB credentials for the application deployment: spec: containers: - image: starships:latest name: starships Env: ... - name: MYSQL_PASSWORD valueFrom: secretKeyRef: name: mysql-credentials key: password - name: MYSQL_USERNAME valueFrom: secretKeyRef: name: mysql-credentials key: username
Stateful services in Kubernetes Use DB master/slave endpoint and credentials in the app config: spring.datasource.url=jdbc:mysql://mysql:3306,mysql-read:3306/space spring.datasource.username=${MYSQL_USERNAME} spring.datasource.password=${MYSQL_PASSWORD}
Stateful services in Kubernetes # Headless service for stable # DNS entries of StatefulSet members. apiVersion: v1 kind: Service metadata: name: mysql labels: app: mysql spec: ports: - name: mysql port: 3306 clusterIP: None selector: app: mysql # Client service for connecting to any MySQL # instance for reads. For writes, you must instead connect to the master: mysql-0.mysql. apiVersion: v1 kind: Service metadata: name: mysql-read labels: app: mysql spec: ports: - name: mysql port: 3306 selector: app: mysql
Stateful services in Kubernetes
Road ahead 1. Get rid of HTTPS in the application and use a web server for that 2. Configure replica set to support dynamic scaling 3. Deploy multiple types of the same instances (/app,/api,/admin) 4. Utilize namespaces to limit visibility of environments 5. Start using K8S metric API to get more info about your cluster 6. Try deploying somewhere else (AWS, Azure, GCE, OpenStack, CloudStack) 7. Log aggregation 8. Rolling updates and blue/green deployments
Road ahead https://github.com/wollfxp/project1/tree/master-february-kubernetes
Questions and Answers
Thank you! Good luck! https://github.com/wollfxp/project1/tree/master-february-kubernetes https://www.facebook.com/yegor.volkov.x64

Cloud-native applications with Java and Kubernetes - Yehor Volkov

  • 1.
    Cloud-native applications withJava and Kubernetes
  • 2.
    Hello, ${username}! ● SeniorJava developer @ DataArt ● Working with Java for 5+ years ● Background in networking and game development, mostly C/C++ ● Interested in everything DevOps related since started working with Java ● https://www.facebook.com/yegor.volkov.x64
  • 3.
  • 4.
    Evolution of Environments Baremetal Virtualization (HW) Virtualization (SW) OS Container Engine JVM Your app 1. Bare metal, VM software-based, VM hardware-based 2. IaaS, PaaS, SaaS 3. Amazon Services, Amazon EC2, Amazon ECS 4. Google Services, Google App Engine, Google Kubernetes Engine 5. CloudFoundry, Heroku, etc.
  • 5.
    Evolution of Environments my-project-0.0.1-SNAPSHOT.jarapp-build-2541.war ./exploded jar/ ./exploded war/ Java developers only care about these!
  • 6.
    Evolution of Environments …and sometimes about these ... java version "10.0.2" 2018-07-17 Java(TM) SE Runtime Environment 18.3 (build 10.0.2+13) Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.2+13, mixed mode) openjdk version "9-internal" OpenJDK Runtime Environment (build 9-internal+0-2016-04-14-195246.buildd.src) OpenJDK 64-Bit Server VM (build 9-internal+0-2016-04-14-195246.buildd.src, mixed mode)
  • 7.
    Evolution of Environments 1.As long as the JRE is the same, that we develop against - everything should be fine 2. As long as the Servlet container version is the same, that we develop against - everything should be fine 3. As long as the RDBMS version is the same, that we develop against - everything should be fine 4. …
  • 8.
    Evolution of Environments ●We need to solidify versions of everything (JVM, JDK/JRE, servers, servlet containers, databases, etc) ● There’s no good way to limit Ops people from screwing everything up ● This should be done by software, not people ● We already have solutions for similar problems in Java world - dependency managers!
  • 9.
    Dockerization: starting small ●Docker to the rescue! ● Make a snapshot of your environment ● Redistribute and run anywhere “©” ● Bake internal custom service versions and builds
  • 10.
  • 11.
    Dockerization: starting small FROMopenjdk:10-jre VOLUME /tmp COPY target/project1-0.0.1-SNAPSHOT.jar app.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] gradlew clean build && docker build -t starships . docker run -p 3306:3306 -v C:/dev/docker/mysql-project1:/var/lib/mysql --env MYSQL_ROOT_PASSWORD=s3cur1ty@maks --env MYSQL_DATABASE=space --name mysql-project1 --network space-net mysql:5.6 docker run -p 8443:8443 --name starships --network space-net starships:latest Dockerfile: Run with:
  • 12.
  • 13.
  • 14.
  • 15.
    Dockerization: next obviousstep version: "3" services: starships: build: context: . dockerfile: Dockerfile image: "starships:latest" ports: - "8443:8443" networks: - space-net depends_on: - mysql mysql: hostname: "mysql" image: "mysql:5.6" ports: - "3306:3306" volumes: - mysql_volume:/var/lib/mysql environment: - MYSQL_ROOT_PASSWORD=s3cur1ty@maks - MYSQL_DATABASE=space networks: space-net: networks: space-net: volumes: mysql_volume:
  • 16.
    Dockerization: next obviousstep gradlew clean build docker build -t starships . docker-compose up Run with:
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
    Diving into Kubernetes komposeconvert -f docker-compose.yaml docker-compose.yaml mysql-deployment.yaml mysql-service.yaml mysql-pvc.yaml starships-deployment.yaml starships-service.yaml
  • 22.
    Diving into Kubernetes $kubectl get all NAME READY STATUS RESTARTS AGE pod/mysql-646fdcd6b9-8x9f7 1/1 Running 0 3h pod/starships-68585d5f5b-v5qtr 1/1 Running 0 2m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12d service/mysql ClusterIP 10.102.90.77 <none> 3306/TCP 3h service/starships ClusterIP 10.98.205.45 <none> 8443/TCP 2m NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deployment.apps/mysql 1 1 1 1 3h deployment.apps/starships 1 1 1 1 2m
  • 23.
    Diving into Kubernetes Takeadvantage of Kubernetes features: secrets and configuration! kubectl create secret generic spacekey --from-file=src/main/resources/spacekey.p12 Update app configuration: server.ssl.key-store=/app/spacekey.p12 server.ssl.key-store-password=spac3ke1 server.ssl.key-alias=spacecert
  • 24.
    Diving into Kubernetes AdjustYAML file to mount the secret: spec: containers: ... volumeMounts: - mountPath: "/app" name: spacekey readOnly: true ... volumes: - name: spacekey secret: secretName: spacekey
  • 25.
    Diving into Kubernetes Sofar we have: ● MySQL service ● MySQL persistent volume claim ● MySQL deployment ● Application service ● Application deployment ● SSL certificate secret
  • 26.
    Playing with Kubernetes Exposethe application: kubectl expose deployment starships --port=8443 --protocol=TCP --target-port=8443 --type=LoadBalancer --name=starships-lb Examine new service: kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12d mysql ClusterIP 10.102.90.77 <none> 3306/TCP 3h starships-lb LoadBalancer 10.100.175.25 localhost 8443:32699/TCP 3h
  • 27.
    Playing with Kubernetes Accessthe application: https://localhost:8443/
  • 28.
    Playing with Kubernetes Injectconfig from ConfigMap: kubectl create configmap starship-config --from-file=src/main/resources/application-k8s-cm.properties Mount it in the pod: volumeMounts: - mountPath: /app/application-k8s.properties name: app-config subPath: application-k8s-cm.properties volumes: - name: spacekey secret: secretName: spacekey - name: app-config configMap: name: starship-config
  • 29.
    Playing with Kubernetes Restartpods: kubectl scale --replicas=0 deployment.apps/starships kubectl scale --replicas=1 deployment.apps/starships Access the application: https://localhost:8443/
  • 30.
    Playing with Kubernetes Scaleit up: kubectl scale --replicas=2 deployment.apps/starships
  • 31.
    Playing with Kubernetes wollf@WOLLF-TYPHOONMINGW64 ~/IdeaProjects/project1 (master-february-kubernetes) $ kubectl get all NAME READY STATUS RESTARTS AGE pod/mysql-646fdcd6b9-8x9f7 1/1 Running 0 2h pod/starships-68585d5f5b-pcdcg 1/1 Running 0 2m pod/starships-68585d5f5b-v5qtr 1/1 Running 0 3m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12d service/mysql ClusterIP 10.102.90.77 <none> 3306/TCP 2h service/starships-lb LoadBalancer 10.100.175.25 localhost 8443:32699/TCP 2h ...
  • 32.
    Playing with Kubernetes ... NAMEDESIRED CURRENT UP-TO-DATE AVAILABLE AGE deployment.apps/mysql 1 1 1 1 2h deployment.apps/starships 2 2 2 2 2h NAME DESIRED CURRENT READY AGE replicaset.apps/mysql-646fdcd6b9 1 1 1 2h replicaset.apps/starships-68585d5f5b 2 2 2 15m
  • 33.
    Back to thesurface Things we added: ● App config via ConfigMap ● Environment variable populated with current pod name ● Load balanced service
  • 34.
    Back to thesurface
  • 35.
    Stateful services inKubernetes Current database configuration: ● Regular ReplicaSet (as for the app) ● Will lose data if re-scheduled ● Credentials are insecure ● Configuration is hard-coded
  • 36.
    Stateful services inKubernetes Moving MySQL configuration and credentials outside of deployment YAML: apiVersion: v1 kind: Secret metadata: name: mysql-credentials data: username: dW5pdmVyc2U= password: ZDRrZlg4QndBQ3F4Rkt0Mw== root-password: YWRtaW4=
  • 37.
    Stateful services inKubernetes MySQL master and slave config: apiVersion: v1 kind: ConfigMap metadata: name: mysql-config data: mysql_database: space master.cnf: | # Apply this config only on the master. [mysqld] log-bin slave.cnf: | # Apply this config only on slaves. [mysqld] super-read-only
  • 38.
    Stateful services inKubernetes Use DB configuration from ConfigMap: containers: - name: mysql env: - name: MYSQL_DATABASE valueFrom: configMapKeyRef: name: mysql-config key: mysql_database - name: MYSQL_ALLOW_EMPTY_PASSWORD value: "1" Use credentials configuration from ConfigMap: containers: - name: mysql env: - name: MYSQL_PASSWORD valueFrom: secretKeyRef: name: mysql-credentials key: password - name: MYSQL_USER valueFrom: secretKeyRef: name: mysql-credentials key: username
  • 39.
    Stateful services inKubernetes Use DB credentials for the application deployment: spec: containers: - image: starships:latest name: starships Env: ... - name: MYSQL_PASSWORD valueFrom: secretKeyRef: name: mysql-credentials key: password - name: MYSQL_USERNAME valueFrom: secretKeyRef: name: mysql-credentials key: username
  • 40.
    Stateful services inKubernetes Use DB master/slave endpoint and credentials in the app config: spring.datasource.url=jdbc:mysql://mysql:3306,mysql-read:3306/space spring.datasource.username=${MYSQL_USERNAME} spring.datasource.password=${MYSQL_PASSWORD}
  • 41.
    Stateful services inKubernetes # Headless service for stable # DNS entries of StatefulSet members. apiVersion: v1 kind: Service metadata: name: mysql labels: app: mysql spec: ports: - name: mysql port: 3306 clusterIP: None selector: app: mysql # Client service for connecting to any MySQL # instance for reads. For writes, you must instead connect to the master: mysql-0.mysql. apiVersion: v1 kind: Service metadata: name: mysql-read labels: app: mysql spec: ports: - name: mysql port: 3306 selector: app: mysql
  • 42.
  • 43.
    Road ahead 1. Getrid of HTTPS in the application and use a web server for that 2. Configure replica set to support dynamic scaling 3. Deploy multiple types of the same instances (/app,/api,/admin) 4. Utilize namespaces to limit visibility of environments 5. Start using K8S metric API to get more info about your cluster 6. Try deploying somewhere else (AWS, Azure, GCE, OpenStack, CloudStack) 7. Log aggregation 8. Rolling updates and blue/green deployments
  • 44.
  • 45.
  • 46.