<p>I was looking at step-by-step tutorial on how to run my spring boot, mysql-backed app using AWS EKS (Elastic Container service for Kubernetes) using the existing SSL wildcard certificate and wasn't able to find a complete solution. </p>

<p>The app is a standard Spring boot self-contained application backed by MySQL database, running on port 8080. I need to run it with high availability, high redundancy including MySQL db that needs to handle large number of writes as well as reads. </p>

<p>I decided to go with the EKS-hosted cluster, saving a custom Docker image to AWS-own ECR private Docker repo going against EKS-hosted MySQL cluster. And using AWS issued SSL certificate to communicate over HTTPS. Below is my solution but I'll be very curious to see how it can be done differently</p>

<p>This a step-by-step tutorial. Please don't proceed forward until the previous step is complete. </p>

<p><strong>CREATE EKS CLUSTER</strong></p>

<p>Follow <a href="https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html" rel="nofollow noreferrer">the standard tutorial</a> to create EKS cluster. Don't do step 4. When you done you should have a working EKS cluster and you must be able to use <code>kubectl</code> utility to communicate with the cluster. When executed from the command line you should see the working nodes and other cluster elements using
<code>kubectl get all --all-namespaces</code> command</p>

<p><strong>INSTALL MYSQL CLUSTER</strong></p>

<p>I used <code>helm</code> to install MySQL cluster following steps from <a href="https://github.com/helm/helm#install" rel="nofollow noreferrer">this tutorial</a>. Here are the steps</p>

<p><strong>Install helm</strong></p>

<p>Since I'm using Macbook Pro with <code>homebrew</code> I used <code>brew install kubernetes-helm</code> command</p>

<p><strong>Deploy MySQL cluster</strong></p>

<p>Note that in <em>MySQL cluster</em> and <em>Kubernetes (EKS) cluster</em>, word "cluster" refers to 2 different things. Basically you are installing cluster into cluster, just like a Russian Matryoshka doll so your MySQL cluster ends up running on EKS cluster nodes.</p>

<p>I used a 2nd part of <a href="https://www.presslabs.com/code/kubernetes-mysql-operator-aws-kops/" rel="nofollow noreferrer">this tutorial</a> (ignore kops part) to prepare the <code>helm</code> chart and install MySQL cluster. Quoting helm configuration:</p>

<pre><code>$ kubectl create serviceaccount -n kube-system tiller

serviceaccount "tiller" created

$ kubectl create clusterrolebinding tiller-crule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller

clusterrolebinding.rbac.authorization.k8s.io "tiller-crule" created

$ helm init --service-account tiller --wait

$HELM_HOME has been configured at /home/presslabs/.helm.
Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.
Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!

$ helm repo add presslabs https://presslabs.github.io/charts

"presslabs" has been added to your repositories

$ helm install presslabs/mysql-operator --name mysql-operator

NAME:   mysql-operator
LAST DEPLOYED: Tue Aug 14 15:50:42 2018
NAMESPACE: default
STATUS: DEPLOYED
</code></pre>

<p>I run all commands exactly as quoted above.</p>

<p>Before creating a cluster, you need a secret that contains the ROOT_PASSWORD key.</p>

<p>Create a file named <code>example-cluster-secret.yaml</code> and copy into it the following YAML code</p>

<pre><code>apiVersion: v1
kind: Secret
metadata:
  name: my-secret
type: Opaque
data:
  # root password is required to be specified
  ROOT_PASSWORD: Zm9vYmFy
</code></pre>

<p>But what is that <code>ROOT_PASSWORD</code>? Turns out this is base64 encoded password that you planning to use with your MySQL root user. Say you want <code>root/foobar</code> (please don't actually use <code>foobar</code>). The easiest way to encode the password is to use one of the websites such as <a href="https://www.base64encode.org/" rel="nofollow noreferrer">https://www.base64encode.org/</a> which encodes <code>foobar</code> into <code>Zm9vYmFy</code></p>

<p>When ready execute <code>kubectl apply -f example-cluster-secret.yaml</code> which will create a new secret</p>

<p>Then you need to create a file named <code>example-cluster.yaml</code> and copy into it the following YAML code:</p>

<pre><code>apiVersion: mysql.presslabs.org/v1alpha1
kind: MysqlCluster
metadata:
  name: my-cluster
spec:
  replicas: 2
  secretName: my-secret
</code></pre>

<p>Note how the <code>secretName</code> matches the secret name you just created. You can change it to something more meaningful as long as it matches in both files. Now run <code>kubectl apply -f example-cluster.yaml</code> to finally create a MySQL cluster. Test it with</p>

<pre><code>$ kubectl get mysql
NAME        AGE
my-cluster  1m
</code></pre>

<p>Note that I did not configure a backup as described in the rest of the article. You don't need to do it for the database to operate. But how to access your db? At this point the mysql service is there but it doesn't have external IP. In my case I don't even want that as long as my app that will run on the same EKS cluster can access it. </p>

<p>However you can use <code>kubectl</code> port forwarding to access the db from your dev box that runs <code>kubectl</code>. Type in this command: <code>kubectl port-forward services/my-cluster-mysql 8806:3306</code>. Now you can access your db from <code>127.0.0.1:8806</code> using user <code>root</code> and the non-encoded password (<code>foobar</code>). Type this into separate command prompt: <code>mysql -u root -h 127.0.0.1 -P 8806 -p</code>. With this you can also use MySQL Workbench to manage your database just don't forget to run <code>port-forward</code>. And of course you can change 8806 to other port of your choosing</p>

<p><strong>PACKAGE YOUR APP AS A DOCKER IMAGE AND DEPLOY</strong></p>

<p>To deploy your Spring boot app into EKS cluster you need to package it into a Docker image and deploy it into the Docker repo. Let's start with a Docker image. There are plenty tutorials on this <a href="https://spring.io/guides/gs/spring-boot-docker/" rel="nofollow noreferrer">like this one</a> but the steps are simple:</p>

<p>Put your generated, self-contained, spring boot jar file into a directory and create a text file with this exact name: <code>Dockerfile</code> in the same directory and add the following content to it:</p>

<pre><code>FROM openjdk:8-jdk-alpine
MAINTAINER me@mydomain.com
LABEL name="My Awesome Docker Image" 
# Add spring boot jar
VOLUME /tmp
ADD myapp-0.1.8.jar app.jar
EXPOSE 8080
# Database settings (maybe different in your app)
ENV RDS_USERNAME="my_user"
ENV RDS_PASSWORD="foobar"
# Other options
ENV JAVA_OPTS="-Dverknow.pypath=/"
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
</code></pre>

<p>Now simply run a Docker command from the same folder to create an image. Of course that requires Docker client installed on your dev box.</p>

<p><code>$ docker build -t myapp:0.1.8 --force-rm=true --no-cache=true .</code></p>

<p>If all goes well you should see your image listed with <code>docker ps</code> command</p>

<p><strong>Deploy to the private ECR repo</strong></p>

<p>Deploying your new image to ECR repo is easy and ECR works with EKS right out of the box. Log into AWS console and navigate to <a href="https://us-west-2.console.aws.amazon.com/ecr/get-started?region=us-west-2" rel="nofollow noreferrer">the ECR section</a>. I found it confusing that apparently you need to have one repository per image but when you click "Create repository" button put your image name (e.g. <code>myapp</code>) into the text field. Now you need to copy the ugly URL for your image and go back to the command prompt</p>

<p>Tag and push your image. I'm using a fake URL as example: <code>901237695701.dkr.ecr.us-west-2.amazonaws.com</code> you need to copy your own from the previous step</p>

<pre><code>$ docker tag myapp:0.1.8 901237695701.dkr.ecr.us-west-2.amazonaws.com/myapp:latest
$ docker push 901237695701.dkr.ecr.us-west-2.amazonaws.com/myapp:latest
</code></pre>

<p>At this point the image should show up at ECR repository you created</p>

<p><strong>Deploy your app to EKS cluster</strong></p>

<p>Now you need to create a Kubernetes deployment for your app's Docker image. Create a <code>myapp-deployment.yaml</code> file with the following content</p>

<pre><code>apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  selector:
    matchLabels:
      app: myapp
  replicas: 2
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - image: 901237695701.dkr.ecr.us-west-2.amazonaws.com/myapp:latest
        name: myapp
        ports:
        - containerPort: 8080
          name: server
        env:
        # optional
        - name: RDS_HOSTNAME
          value: "10.100.98.196"
        - name: RDS_PORT
          value: "3306"
        - name: RDS_DB_NAME
          value: "mydb"
      restartPolicy: Always
status: {}
</code></pre>

<p>Note how I'm using a full URL for the <code>image</code> parameter. I'm also using a private CLUSTER-IP of mysql cluster that you can get with <code>kubectl get svc my-cluster-mysql</code> command. This will differ for your app including any env names but you do have to provide this info to your app somehow. Then in your app you can set something like this in the <code>application.properties</code> file:</p>

<pre><code>spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://${RDS_HOSTNAME}:${RDS_PORT}/${RDS_DB_NAME}?autoReconnect=true&amp;amp;zeroDateTimeBehavior=convertToNull
spring.datasource.username=${RDS_USERNAME}
spring.datasource.password=${RDS_PASSWORD}
</code></pre>

<p>Once you save the <code>myapp-deployment.yaml</code> you need to run this command</p>

<p><code>kubectl apply -f myapp-deployment.yaml</code></p>

<p>Which will deploy your app into EKS cluster. This will create 2 pods in the cluster that you can see with <code>kubectl get pods</code> command</p>

<p>And rather than try to access one of the pods directly we can create a service to front the app pods. Create a <code>myapp-service.yaml</code> with this content:</p>

<pre><code>apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  ports:
  - port: 443
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: myapp
  type: LoadBalancer
</code></pre>

<p>That's where the magic happens! Just by setting the port to 443 and type to <code>LoadBalancer</code> the system will create a Classic Load Balancer to front your app.</p>

<p>BTW if you don't need to run your app over HTTPS you can set port to 80 and you will be pretty much done!</p>

<p>After you run <code>kubectl apply -f myapp-service.yaml</code> the service in the cluster will be created and if you go to to the Load Balancers section in the EC2 section of AWS console you will see that a new balancer is created for you. You can also run <code>kubectl get svc myapp-service</code> command which will give you EXTERNAL-IP value, something like <code>bl3a3e072346011e98cac0a1468f945b-8158249.us-west-2.elb.amazonaws.com</code>. Copy that because we need to use it next.</p>

<p>It is worth to mention that if you are using port 80 then simply pasting that URL into the browser should display your app</p>

<p><strong>Access your app over HTTPS</strong></p>

<p>The following section assumes that you have AWS-issued SSL certificate. If you don't then go to AWS console "Certificate Manager" and create a wildcard certificate for your domain</p>

<p>Before your load balancer can work you need to access <code>AWS console -&gt; EC2 -&gt; Load Balancers -&gt; My new balancer -&gt; Listeners</code> and click on "Change" link in <code>SSL Certificate</code> column. Then in the pop up select the AWS-issued SSL certificate and save.</p>

<p>Go to Route-53 section in AWS console and select a hosted zone for your domain, say <code>myapp.com.</code>. Then click "Create Record Set" and create a <code>CNAME - Canonical name</code> record with <code>Name</code> set to whatever alias you want, say <code>cluster.myapp.com</code> and <code>Value</code> set to the EXTERNAL-IP from above. After you "Save Record Set" go to your browser and type in <a href="https://cluster.myapp.com" rel="nofollow noreferrer">https://cluster.myapp.com</a>. You should see your app running</p>
