Showing posts with label K8s. Show all posts
Showing posts with label K8s. Show all posts

1 May 2019

Running PostgreSQL in a Cloud on Oracle Containers Engine for Kubernetes

In this post I am going to show a few steps to deploy and run PostgreSQL database in a K8S cluster on OKE.

The deployment is going to be based on postgres:11.1 Docker image which requires a few environment variables to be configured: POSTGRES_DB (database name), POSTGRES_USER and POSTGRES_PASSWORD. I am going to store values of these variables in K8S ConfigMap and Secret:
apiVersion: v1
kind: ConfigMap
metadata:
  name: postgre-db-config
data:
  db-name: flexdeploy

apiVersion: v1
kind: Secret
metadata:
  name: postgre-db-secret
stringData:
  username: creator
  password: c67

These configuration K8S resources are referenced by the StatefulSet which actually takes care of the lifespan of a pod with postgres-db container:
apiVersion: apps/v1beta2
kind: StatefulSet
metadata:
  name: postgre-db
  labels:
    run: postgre-db
spec:
  selector:
      matchLabels:
        run: postgre-db
  serviceName: "postgre-db-svc"
  replicas: 1
  template:
    metadata:
      labels:
        run: postgre-db
    spec:
      containers:
      - image: postgres:11.1
        volumeMounts:
           - mountPath: /var/lib/postgresql/data
             name: db     
        env:
          - name: POSTGRES_DB
            valueFrom:
              configMapKeyRef:
                   name: postgre-db-config
                   key: db-name                  
          - name: POSTGRES_USER
            valueFrom:
              secretKeyRef:
                   name: postgre-db-secret
                   key: username                  
          - name: POSTGRES_PASSWORD
            valueFrom:
              secretKeyRef:
                   name: postgre-db-secret
                   key: password                  
          - name: PGDATA
            value: /var/lib/postgresql/data/pgdata           
        name: postgre-db
        ports:
        - containerPort: 5432
  volumeClaimTemplates:
   - metadata:
       name: db
     spec:
       accessModes: [ "ReadWriteOnce" ]
       resources:
         requests:
           storage: 3Gi  


StatefulSet K8S resource has been specially designed for stateful applications like database that save their data to a persistent storage. In order to define a persistent storage for our database we use another K8s resource Persistent Volume and here in the manifest file we are defining a claim to create a 3Gi Persistent Volume with name db. The volume is called persistent because its lifespan is not maintained by a container and not even by a pod, it’s maintained by a K8s cluster. So it can outlive any containers and pods and save the data. Meaning that if we kill or recreate a container or a pod or even the entire StatefulSet, the data will be still there. We are referring to this persistence volume in the container definition mounting a volume on path /var/lib/postgresql/data. This is where PostgreSQL container stores its data.

In order to access the database we are going to create a service:
apiVersion: v1
kind: Service
metadata:
  name: postgre-db-svc 
spec:
  selector:
    run: postgre-db
  ports:
    - port: 5432
      targetPort: 5432 
  type: LoadBalancer   

This is a LoadBalancer service which is accessible from outside of the K8S cluster:
$ kubectl get svc postgre-db-svc
NAME             TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)          AGE
postgre-db-svc   LoadBalancer   10.96.177.34   129.146.211.77   5432:32498/TCP   39m

Assuming that we have created a K8s cluster on OKE and our kubectl is configured to communicate with it we can apply the manifest files to the cluster:
kubectl apply -f postgre-db-config.yaml
kubectl apply -f postgre-db-secret.yaml
kubectl apply -f postgre-db-pv.yaml
kubectl apply -f postgre-db-service.yaml

Having done that, we can connect to the database from our favorite IDE on our laptop

The manifest files for this post are available on GitHub.

That's it!

20 Apr 2019

Deployment Strategies with Kubernetes and Istio

In this post I am going to discuss various deployment strategies and how they can be implemented with K8s and Istio. Basically the implementation of all strategies is based on the ability of K8s to run multiple versions of a microservice simultaneously and on the concept that consumers can access the microservice only through some entry point. At that entry point we can control what version of a microservice the consumer should be routed to.

The sample application for this post is going to be a simple Spring Boot application wrapped into a Docker image. So there are two images superapp:old and superapp:new representing an old and a new versions of the application respectively:

docker run -d --name old -p 9001:8080 eugeneflexagon/superapp:old
docker run -d --name new -p 9002:8080 eugeneflexagon/superapp:new


curl http://localhost:9001/version
{"id":1,"content":"old"}

curl http://localhost:9002/version
{"id":1,"content":"new"}


Let's assume the old version of the application is deployed to a K8s cluster running on Oracle Kubernetes Engine with the following manifest:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: superapp
spec:
  replicas: 3
  template:
    metadata:
       labels:
         app: superapp
    spec:
      containers:
        - name: superapp
          image: eugeneflexagon/superapp:old
          ports:
            - containerPort: 8080
So there are three replicas of a pod running the old version of the application. There is also a service routing the traffic to these pods:
apiVersion: v1
kind: Service
metadata:
  name: superapp
spec:
  selector:
    app: superapp   
  ports:
    - port: 8080
      targetPort: 8080      

Rolling Update
This deployment strategy updates pods in a rolling update way, changing them one by one.


This is a default strategy handled by a K8s cluster itself, so we just need to update the superapp deployment with a reference to the new image:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: superapp
spec:
  replicas: 3
  template:
    metadata:
       labels:
         app: superapp
    spec:
      containers:
        - name: superapp
          image: eugeneflexagon/superapp:new
          ports:
            - containerPort: 8080
However, we can fine-tune the rolling update algorithm by providing parameters for this deployment strategy in the manifest file:
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
       maxSurge: 30%
       maxUnavailable: 30%   
  template:
  ...

The maxSurge parameter defines the maximum number of pods that can be created over the desired number of pods. It can be either a percentage or an absolute number. Default value is 25%.
The maxUnavailable parameter defines the maximum number of pods that can be unavailable during the update process. It can be either a percentage or an absolute number. Default value is 25%.


Recreate
This deployment strategy kills all old pods and then creates the new ones.

spec:
  replicas: 3
  strategy:
    type: Recreate
  template:
  ...

Very simple.

Blue/Green
This strategy defines an old version of the application as a green one and a new version as a blue one. Users always have access only to the green version.


apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: superapp-01
spec:
  template:
    metadata:
       labels: 
         app: superapp
         version: "01"
...



apiVersion: v1
kind: Service
metadata:
  name: superapp
spec:
  selector:
    app: superapp 
    version: "01"
...

The service routes the traffic only to pods with label version: "01".

We deploy a blue version to a K8s cluster and make it available only for QAs or for a test automation tool (via a separate service or direct port-forwarding).



apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: superapp-02
spec:
  template:
    metadata:
       labels:
         app: superapp
         version: "02"
...


Once the new version is tested we switch the service to it and scale down the old version:



apiVersion: v1
kind: Service
metadata:
  name: superapp
spec:
  selector:
    app: superapp 
    version: "02"
...


kubectl scale deployment superapp-01 --replicas=0

Having done that,  all users work with the new version.

So there is no Istio so far. Everything is handled by a K8s cluster out-of-the box. Let's move on to the next strategy.


Canary
I love this deployment strategy as it lets the users test the new version of the application and they don't even know about that. The idea is that we deploy a new version of the application and route 10% of the traffic to it. The users have no idea about that.


If it works for a while, we can balance the traffic 70/30, then 50/50 and eventually 0/100.
Even though this strategy can be implemented with K8s resources only by playing with the number of old and new pods, it is way more convenient to implement it with Istio.
So the old and the new applications are defined as the following deployments:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: superapp-01
spec:
  template:
    metadata:
       labels: 
         app: superapp
         version: "01"
...

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: superapp-02
spec:
  template:
    metadata:
       labels:
         app: superapp
         version: "02"
...

The service routes the traffic to both of them:
apiVersion: v1
kind: Service
metadata:
  name: superapp
spec:
  selector:
    app: superapp 
...

On top of that we are going to use the following Istio resources: VirtualService and  DestinationRule.
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: superapp
spec:
  host: superapp
  subsets:
  - name: green
    labels:
      version: "01"
  - name: blue
    labels:
      version: "02"
---     
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: superapp
spec:
  hosts:
    - superapp   
  http:
  - match:
    - uri:
        prefix: /version
    route:
    - destination:
        port:
          number: 8080
        host: superapp
        subset: green
      weight: 90
    - destination:
        port:
          number: 8080
        host: superapp    
        subset: blue  
      weight: 10
The VirtualService will route all the traffic coming to the superapp service (hosts) to the green and blue pods according to the provided weights (90/10).

A/B Testing
With this strategy we can precisely control what users, from what devices, departments, etc. are routed to the new version of the application.

For example here we are going to analyze the request header and if its custom tag "end-user" equals "xammer" it will be routed to the new version of the application. The rest of the requests will be routed to the old one:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: superapp
spec:
  gateways:
    - superapp
  hosts:
    - superapp   
  http:
  - match:
    - headers:
        end-user:
          exact: xammer                 
    route:
    - destination:
        port:
          number: 8080
        host: superapp
        subset: blue
  - route:
    - destination:
        port:
          number: 8080
        host: superapp
        subset: green

All examples and manifest files for this post are available on GitHub so you can play with various strategies and sophisticated routing rules on your own. You just need a K8s cluster (e.g. Minikube on your laptop) with preinstalled Istio. Happy deploying!

That's it!

24 Nov 2018

Persistent Volumes for Database Containers running on a K8s cluster in the Cloud

In one of my previous posts I showed how we can run Oracle XE database on a K8s cluster. That approach works fine for the use-cases when we don't care about the data and we are fine with loosing it when the container is redeployed and the pod is restarted. But if we want to keep the data, if we want it to survive all rescheduling we'll want to reconsider K8s resources used to run the DB container on the cluster. That said, the yaml file defining the resources looks like this one:

apiVersion: apps/v1beta2
kind: StatefulSet
metadata:
  name: oraclexe
  labels:
    run: oraclexe
spec:
  selector:
      matchLabels:
        run: oraclexe
  serviceName: "oraclexe-svc"
  replicas: 1
  template:
    metadata:
      labels:
        run: oraclexe
    spec:
      volumes:
       - name: dshm
         emptyDir:
           medium: Memory  
      containers:
      - image: eugeneflexagon/database:11.2.0.2-xe
        volumeMounts:
           - mountPath: /dev/shm
             name: dshm
           - mountPath: /u01/app/oracle/oradata
             name: db
        imagePullPolicy: Always
        name: oraclexe
        ports:
        - containerPort: 1521
          protocol: TCP
  volumeClaimTemplates:
   - metadata:
       name: db
     spec:
       accessModes: [ "ReadWriteOnce" ]
       resources:
         requests:
           storage: 100M                   
---
apiVersion: v1
kind: Service
metadata:
  name: oraclexe-svc
  labels:
    run: oraclexe   
spec:
  selector:
    run: oraclexe
  ports:
    - port: 1521
      targetPort: 1521
  type: LoadBalancer

There are some interesting things here. First of all this is not a deployment. We are defining here another K8s resource which is called Stateful Set. Unlike a Deployment, a Stateful Set maintains a sticky identity for each of their Pods. These pods are created from the same specification, but they are not interchangeable: each has a persistent identifier that it maintains across any rescheduling.

This guy has been specially designed for stateful applications like database that save their data to a persistent storage. In order to define a persistent storage for our database we use a special K8s resource Persistent Volume and here in the yaml file we are defining a claim to create a 100mb Persistent Volume with name db. This volume provides read/write access mode for one assigned pod. The volume is called persistent because its lifespan is not maintained by a container and not even by a pod, it’s maintained by a K8s cluster. So it can outlive any containers and pods and save the data. We are referring to this persistence volume in the container definition mounting a volume on path /u01/app/oracle/oradata. This is where Oracle DB XE container stores its data.

That's it!

29 Sept 2018

Configuring a Datasource in a Docker Container

In this post I am going to show how to configure a datasource consumed by an ADF application running on Tomcat in a Docker container.


So, there is a Docker container sample-adf with a Tomcat application server preconfigured with ADF libraries and with an ADF application running on top of Tomcat. The ADF application requires a connection to an external database.
The application is implemented with ADF BC and it's application module is referring to a datasource jdbc/appDS.



This datasource is configured inside a container in Tomcat /conf/context.xml file. The JDBC url, username and password are provided by environment variables:

<Resource name="jdbc/appDS" auth="Container"
           type="oracle.jdbc.pool.OracleDataSource"
           factory="oracle.jdbc.pool.OracleDataSourceFactory"
           url="${DB_URL}"
           user="${DB_USERNAME}"
           password="${DB_PWD}"

           ...


These variables are propagated to the application server in Tomcat /bin/setenv.sh file:

CATALINA_OPTS='-DDB_URL=$DB_URL -DDB_USERNAME=$DB_USERNAME -DDB_PWD=DB_PWD ...'

Having these configurations set, we can run a container providing values of the variables:

docker run --name adf -e DB_URL="jdbc:oracle:thin:@myhost:1521:xe" -e DB_USERNAME=system -e DB_PWD=welcome1 sample-adf

If we are about to run a container in a K8s cluster we can provide variable values in a yaml file:

spec:
      containers:
      - image: sample-adf
        env:
        - name: DB_URL
           value: "jdbc:oracle:thin:@myhost:1521:xe"
        - name: DB_USERNAME
          value: "system"
        - name: DB_PWD
          value: "welcome1"


In order to make this yaml file portable we would avoid providing exact values and refer to K8s ConfigMaps and Secrets instead of that

A ConfigMap is a named K8s resource that allows us to decouple configuration artifacts from image content to keep containerized applications portable. This is just a simple set of key-value paires. And obviously those values in each K8s cluster, in each environment are different.

Similar approach is used when it comes to sensitive data like user names and passwords. Only in this case instead of configmaps we use a special resource which is called Secret. The data is encoded and it is only sent to a node if a pod on that node requires it. It is deleted once the pod that depends on it is deleted.

We can create ConfigMaps and Secrets out of key-value files or just by providing the values in a command line:

kubectl create configmap adf-config  
--from-literal=db.url="jdbc:oracle:thin:@myhost:1521:xe"

kubectl create secret generic adf-secret 
--from-literal=db.username="system
--from-literal=db.pwd="welcome1"


Having done that we can specify in the yaml file that values for the environment variables should be fetched from adf-config ConfigMap and adf-secret Secret:

spec:
      containers:
      - image: sample-adf
        env:
        - name: DB_URL
          valueFrom:
               configMapKeyRef:
                   name: adf-config
                   key: db.url
        - name: DB_USERNAME
          valueFrom:
               secretKeyRef:
                   name: adf-secret
                   key: db.username
        - name: DB_PWD
          valueFrom:
               secretKeyRef:
                   name: adf-secret
                   key: db.pwd


That's it!

31 Aug 2018

Remote access to Minikube with Kubectl

Let's say you need to install a Kubernetes cluster in your organization for development and testing purposes. Minikube looks like a perfect fit for that job. It was specially designed for users looking to try out Kubernetes or develop with it day-to-day. It runs a single-node Kubernetes cluster inside a VM on a standalone machine. So, you found a server for that, followed the insulation guide to install a virtual box with Minikube on it and now you can easily deploy pods to the K8s cluster with kubectl from that server. In order to be able to do the same remotely from your laptop you have to do some extra movements:

1. Install kubectl on your laptop if you don't have it.
2. Copy .minikube folder from the server with Minikube to your laptop (e.g. to /Users/fedor/work/minikube)
3. Update clusters, contexts and users sections in your kubectl config file on your laptop ($HOME/.kube/config) with the following content
apiVersion: v1
clusters:
- cluster:   
    insecure-skip-tls-verify: true
    server: https://YOUR_SERVER:51928
  name: minikube
contexts:
- context:
    cluster: minikube
    user: minikube
  name: minikube
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
  user:
    client-certificate: /Users/fedor/work/minikube/client.crt
    client-key: /Users/fedor/work/minikube/client.key

4. Go to the server and stop Minikube with
minikube stop
5. Forward a port for the Minikube VM from 8443 guest port to 51928 host port.


6. Start Minikube with  
minikube start
7. Check from your laptop that it works:
kubectl get pods

That's it!

30 Jul 2018

Run Oracle XE Docker Container on Amazon EKS

Recently Amazon announced general availability of their new service Amazon Elastic Container Service for Kubernetes (Amazon EKS). This is a managed service to deploy, manage and scale containerized applications using K8s on AWS. I decided to get my hands dirty with it and deployed a Docker container with Oracle XE Database to a K8s cluster on Amazon EKS. In this post I am going to describe what I did to make that happen.

1. Create Oracle XE Docker image.

First of all we need a Docker image with Oracle XE database:

1.1 Clone Oracle GitHub repository to build docker images:
git clone https://github.com/oracle/docker-images.git oracle-docker-images

It will create oracle-docker-images folder.

1.2 Download Oracle XE binaries from OTN

1.3 Copy the downloaded stuff to ../oracle-docker-images/OracleDatabase/SingleInstance/dockerfiles/11.2.0.2 folder

1.4 Build the Docker image
./buildDockerImage.sh -v 11.2.0.2 -x -I

1.5 Check the new image

docker images oracle/database:11.2.0.2-xe
1.6. Rename the image so you can push it to Docker Hub. E.g.:
docker tag oracle/database:11.2.0.2-xe eugeneflexagon/database:11.2.0.2-xe

Ok, so having done that, we have Oracle XE Docker image stored in Docker Hub repository.

2. Create K8s cluster on Amazon EKS.

Assuming that you have already AWS account, take your favorite tambourine (you will need it) and create a K8s cluster following this guide Getting Started with Amazon EKS (a good example of how complicated you can make a "getting started guide").

Once you are able to see your working nodes in Ready status, you're good to move forward
kubectl get nodes --watch
3. Configure Load Balancer

In AWS console go to your EC2 Dashboard and look at the Load Balancers tab:




Click on Create Load Balancer, select Network Load Balancer:



change the listener port to 1521



and specify in Availability Zones the VPC that you have just created for the K8s cluster:

The scheme should be internet-facing.

4.
Deploy Oracle XE Docker container to the K8s cluster.

4.1 Create a yaml file with the following content:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: oraclexe
  labels:
    run: oraclexe
spec:
  replicas: 1
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      labels:
        run: oraclexe
    spec:   
     volumes:
       - name: dshm
         emptyDir:
           medium: Memory
     containers:
       - image: eugeneflexagon/database:11.2.0.2-xe
         volumeMounts:
           - mountPath: /dev/shm
             name: dshm
         imagePullPolicy: Always
         name: oraclexe
         ports:
           - containerPort: 1521
             protocol: TCP
     imagePullSecrets:
       - name: wrelease
     restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: oraclexe-svc
spec:
  selector:
    run: oraclexe
  ports:
    - port: 1521
      targetPort: 1521
  type: LoadBalancer

4.2  Deploy it:
kubectl apply -f oraclexe-deployment.yaml

5. Check how it works

5.1. Get a list of pods and check the logs:
kubectl get pods

kubectl logs -f POD_NAME



Once you see in the logs DATABASE IS READY TO USE! the database container is up and running.

Note, that the container while starting generated a password for sys and system users. You can find this password in the log:



5.2 Get external IP address of the service:
kubectl get svc

Wait until the address in EXTERNAL-IP column turns from PENDING into something meaningful:



5.3 Connect to the DB:

That's it!

31 Mar 2018

Deploying to K8s cluster with Fn Function

An essential step of any CI/CD pipeline is deployment. If the pipeline operates with Docker containers and deploys to K8s clusters then the goal of the deployment step is to deploy a specific Docker image (stored on some container registry) to a specific K8s cluster.  Let's say there is a VM where this deployment step is being performed. There are a couple of things to be done with that VM before it can be used as a deploying-to-kuberenetes machine:
  • install kubectl (K8s CLI) 
  • configure access to K8s clusters where we are going to deploy 
Having the VM configured, the deployment step does the following:
# kubeconfig file contains access configuration to all K8s clusters we need
# each configuration is called "context"
export KUBECONFIG=kubeconfig

# switch to "google-cloud-k8s-dev" context (K8s cluster on Google Cloud for Dev)
# so all subsequent kubectl commands are applied to that K8s cluster
kubectl config  use-context google-cloud-k8s-dev

# actually deploy by applying k8s-deployment.yaml file
# containing instructions on what image should be deployed and how  
kubectl apply -f k8s-deployment.yaml

In this post I am going to show how we can create a preconfigured Docker container capable of deploying a Docker image to a K8s cluster. So, basically, it is going to work as a function with two parameters: docker image, K8s context. Therefore we are going to create a function in Fn Project basing on this "deployer" container and deploy to K8s just by invoking the function over http.

The deployer container is going to be built from a Dockerfile with the following content:
FROM ubuntu

# install kubectl
ADD https://storage.googleapis.com/kubernetes-release/release/v1.6.4/bin/linux/amd64/kubectl /usr/local/bin/kubectl
ENV HOME=/config
RUN chmod +x /usr/local/bin/kubectl
RUN export PATH=$PATH:/usr/local/bin

# install rpl
RUN apt-get update
RUN apt-get install rpl -y

# copy into container k8s configuration file with access to all K8s clusters
COPY kubeconfig kubeconfig

# copy into container yaml file template with IMAGE_NAME placeholder
# and an instruction on how to deploy the container to K8s cluster
COPY k8s-deployment.yaml k8s-deployment.yaml

# copy into container a shell script performing the deployment
COPY deploy.sh /usr/local/bin/deploy.sh
RUN chmod +x /usr/local/bin/deploy.sh

ENTRYPOINT ["xargs","/usr/local/bin/deploy.sh"]

It is worth looking at the k8s-deployment.yaml file. It contains IMAGE_NAME placeholder which is going to be replaced with the exact Docker image name while deployment:

apiVersion: extensions/v1beta1
kind: Deployment

...

    spec:
      containers:
      - image: IMAGE_NAME
        imagePullPolicy: Always
...

The deploy.sh script which is being invoked once the container is started has the following content:
#!/bin/bash

# replace IMAGE_NAME placeholder in yaml file with the first shell parameter 
rpl IMAGE_NAME $1 k8s-deployment.yaml

export KUBECONFIG=kubeconfig

# switch to K8s context specified in the second shell parameter
kubectl config  use-context $2

# deploy to K8s cluster
kubectl apply -f k8s-deployment.yaml

So, we are going to build a docker image from the Dockerfile by invoking this docker command:
docker build -t efedorenko/k8sdeployer:1.0 .
Assuming there is Fn Project up and running somewhere (e.g. on K8s cluster as it is described in this post) we can create an Fn application:
fn apps create k8sdeployerapp
Then create a route to the k8sdeployer container:
fn routes create k8sdeployerapp /deploy efedorenko/k8sdeployer:1.0
We have created a function deploying a Docker image to a K8s cluster. This function can be invoked over http like this:
curl http://35.225.120.28:80/r/k8sdeployer -d "google-cloud-k8s-dev efedorenko/happyeaster:latest"
This call will deploy efedorenko/happyeaster:latest Docker image to a K8s cluster on Google Cloud Platform.


That's it!



24 Mar 2018

Run Fn Functions on K8s on Google Cloud Platform

Recently, I have been playing a lot with Functions and Project Fn. Eventually, I got to the point where I had to go beyond a playground on my laptop and go to the real wild world. An idea of running Fn on a K8s cluster seemed very attractive to me and I decided to do that somewhere on prem or in the cloud.  After doing some research on how to install and configure K8s cluster on your own on a bare metal I came to a conclusion that I was too lazy for that. So, I went (flew) to the cloud.

In this post I am going to show how to run Fn on Kubernetes cluster hosted on the Google Cloud Platform. Why Google? There are plenty of other cloud providers with the K8s services.
The thing is that Google really has Kubernetes cluster in the cloud which is available for everyone. They give you the service right away without asking to apply for a preview mode access (aka we'll reach out to you once we find you good enough for that), explaining why you need it, checking your background, credit history, etc. So, Google.

Once you got through all formalities and finally have access to the Google Kubernetes Engine, go to the Quickstarts page and follow the instructions to install Google Cloud SDK.

If you don't have kubectl installed on your machine you can install it with gcloud:
gcloud components install kubectl

Follow the instructions on Kubernetes Engine Quickstart to configure gcloud and create a K8s cluster by invoking the following commands:
gcloud container clusters create fncluster
gcloud container clusters get-credentials fncluster
Check the result with kubectl:
kubectl cluster-info
This will give you a list of K8s services in your cluster and their URLs.

Ok, so this is our starting point. We have a new K8s cluster in the cloud on one hand and Fn project on another hand. Let's get them married.

We need to install a tool managing Kubernetes packages (charts). Something similar to apt/yum/dnf/pkg on Linux. The tool is Helm. Since I am a happy Mac user I just did that:
brew install kubernetes-helm

The rest of Helm installation options are available here.

The next step is to install Tiller in the K8s cluster. This is a server part of Helm:
kubectl create serviceaccount --namespace kube-system tiller
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
helm init --service-account tiller --upgrade

If you don't have Fn installed locally, you will want to install it so you have Fn CLI on your machine (on Mac or Linux):  

curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install > setup.sh
chmod u+x setup.sh
sudo ./setup.sh

Install Fn on K8s cluster with Helm (assuming you do have git client):
git clone git@github.com:fnproject/fn-helm.git && cd fn-helm
helm dep build fn
helm install --name fn-release fn

Wait (a couple of minutes) until Google Kubernetes Engine assigns an external IP to the Fn API in the cluster. Check it with:
kubectl get svc --namespace default -w fn-release-fn-api

Configure your local Fn client with access to Fn running on K8s cluster
export FN_API_URL=http://$(kubectl get svc --namespace default fn-release-fn-api -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):80

Basically, it's done. Let's check it:
  fn apps create adfbuilderapp 
  fn apps list

Now we can build ADF applications with an Fn function as it is described in my previous post. Only this time the function will run and therefore building job will be performed somewhere high in the cloud.


That's it!