Deploying sample Go application on Kubernetes with an Oracle RDS - dpsp-summit/wiki GitHub Wiki

In this tutorial we are going to create a sample application in Go and communicate with an Oracle RDS.

Requirements

  • AWS account
  • Aws client
  • Go 1.12

And for testing the Oracle RDS:

  • sqlplus

Creating the Oracle RDS

Firts we create an Oracle RDS on AWS for our Contacts DB.

Go to the AWS console -> Services -> RDS

Select "Create Database"

On "Engine options" select "Oracle" and the edition that suits your needs. image

On the "Templates" card we are selecting the "Free tier" option for the purpose of this tutorial. image

On "Settings" define the name of the database and the username and password. image

We are leaving everything else as default except for "Connectivity". Since we are using a different VPN than the one used by our Kubernetes cluster, we need to enable our DB to be publicly accessible. Also, we are creating a new Security Group that allows request only from our cluster IPs.

image

Then click "Create Database" and wait for the database to be created.

Initializing the DB

Connect to the Oracle instance using the following command:

sqlplus username/mypassword@host:1521/ORCL

Where you will need to replace the username, password and host from the Oracle RDS that you created previously.

Once connected, run the following instructions:

CREATE TABLE username.contacts (
  id NUMBER(5) PRIMARY KEY,
  name VARCHAR2(20) NOT NULL);

INSERT INTO contacts values (1, 'Contact 1');
INSERT INTO contacts values (2, 'Contact 2');
INSERT INTO contacts values (2, 'Contact 3');
INSERT INTO contacts values (2, 'Contact 4');

Creating the Go Application

We are going to do a simple Go application that returns the contacts from the DB.

package main
import (
    "os"
    "encoding/json"
    "log"
    "net/http"
    "github.com/gorilla/mux"
    "fmt"
    "database/sql"
    _ "gopkg.in/goracle.v2"
)

func main() {

    testDB()

    router := mux.NewRouter()
    router.HandleFunc("/contacts", GetPeople).Methods("GET")
    
    log.Fatal(http.ListenAndServe(":8000", router))
}

func GetPeople(w http.ResponseWriter, r *http.Request) {
    
    db := dbConn()

    rows, err := db.Query("select * from contacts")
    if err != nil {
        fmt.Println("Error running query")
        fmt.Println(err)
        return
    }
    defer rows.Close()
 
    var (
      id int
      name string
    )

    var products []Product

    for rows.Next() {
        rows.Scan(&id, &name)
        fmt.Printf("id: %d, name: %s\n", id, name)
        products = append(products, Product{Id: id, Name: name})
    }

    json.NewEncoder(w).Encode(products)
}

type Product struct {
    Id        int   `json:"id,omitempty"`
    Name      string   `json:"name,omitempty"`
}

func dbConn() (db *sql.DB) {
    dbDriver := "goracle"
    dbUser := os.Getenv("DB_USERNAME")
    dbPass := os.Getenv("DB_PASSWORD")
    dbServer := os.Getenv("DB_SERVER")
    dbName := os.Getenv("DB_NAME")
    db, err := sql.Open(dbDriver, dbUser+"/"+dbPass+"@" + dbServer + ":1521/"+dbName)
    if err != nil {
        panic(err.Error())
    }
    return db
}

Create the Docker Image

Since the Oracle Driver requires some specific files, we need to download them first and place them in the root of the project to be able to create the image:

  • oracle-instantclient19.3-basic-19.3.0.0.0-1.x86_64.rpm
  • oracle-instantclient19.3-sqlplus-19.3.0.0.0-1.x86_64.rpm
  • oracle-instantclient19.3-devel-19.3.0.0.0-1.x86_64.rpm

On the build-stage we use a Go image to build our application, and on the production stage we copy the files needed for the oracle driver. This is the Dockerfile of our application:

FROM golang:1.12 as build-stage

# Set the Current Working Directory inside the container
WORKDIR $GOPATH/src/welcome-app

# Copy go project files
COPY . .

# Download all the dependencies
# https://stackoverflow.com/questions/28031603/what-do-three-dots-mean-in-go-command-line-invocations
RUN go get -d -v ./...

# Install the package
RUN go install -v ./...

RUN go build main.go

FROM ubuntu:16.04 as production

COPY . .

RUN apt-get update
RUN apt-get install -y alien

# Should download basic, sqlplus and devel rpm packages from https://www.oracle.com/technetwork/topics/linuxx86-64soft-092277.html
RUN alien -i oracle-instantclient19.3-basic-19.3.0.0.0-1.x86_64.rpm
RUN alien -i oracle-instantclient19.3-sqlplus-19.3.0.0.0-1.x86_64.rpm
RUN alien -i oracle-instantclient19.3-devel-19.3.0.0.0-1.x86_64.rpm

RUN export LD_LIBRARY_PATH=/usr/lib/oracle/19.3/client64/lib/${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
RUN ldconfig

RUN apt-get install -y libaio1

# This container exposes port 8000 to the outside world
EXPOSE 8000

COPY --from=build-stage /go/src/welcome-app/main .

# Run the executable
CMD ["./main"]

Create the external service on Kubernetes

Since our application is going to be an external resource for the Kubernetes cluster, we need to create it first. Create the contacts_db_service.yaml with the following content:

apiVersion: v1
kind: Service
metadata:
  name: contacts-db
spec:
  type: ExternalName
  externalName: contacts-db.cul44wokm9o4.us-west-2.rds.amazonaws.com
  ports:
  - port: 1521

Create the service:

kubectl create -f contacts_db_service.yaml

Deploy the application

In order to deploy the application we need to create a contacts.yaml with the following deployment and service:

kind: Deployment
apiVersion: apps/v1
metadata:
  name: contacts
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: contacts
  template:
    metadata:
      labels:
        app: contacts
    spec:
      containers:
      - name: contacts
        image: fbereche/contacts-api:2.0.0
        env:
        - name: DB_SERVER
          value: contacts-db
        - name: DB_USERNAME
          value: username
        - name: DB_PASSWORD
          value: mypassword
        - name: DB_NAME
          value: ORCL
        ports:
        - containerPort: 8000
---
apiVersion: v1
kind: Service
metadata:
  name: contacts-service
spec:
  selector:
    app: contacts
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8000

Run the configuration:

kubectl create -f deployment.yaml

Testing the app

After deploying the contacts service, we should see a new Load Balancer being created on the AWS console. Similar to this:

afd0aa50ea0fd11e9b53a02f515ffc3f-1489305742.us-west-2.elb.amazonaws.com

We can confirm this is the same URL shown for our contacts-service on Kubernetes:

kubectl get svc

The result should show something similar to this:

NAME               TYPE           CLUSTER-IP       EXTERNAL-IP                                                               PORT(S)        AGE
kubernetes         ClusterIP      100.64.0.1       <none>                                                                    443/TCP        14d
mysql-server       ClusterIP      100.67.125.198   <none>                                                                    3306/TCP       22h
contacts-service   LoadBalancer   100.65.111.242   afd0aa50ea0fd11e9b53a02f515ffc3f-1489305742.us-west-2.elb.amazonaws.com   80:31808/TCP   22h

It will take a few minutes to transition from OutOfService to InService. After it is ready, we can do a GET to the following URL:

afd0aa50ea0fd11e9b53a02f515ffc3f-1489305742.us-west-2.elb.amazonaws.com/products

We should get the following information:

[{"id":1,"name":"Name 1"},{"id":2,"name":"Name 2"}]
⚠️ **GitHub.com Fallback** ⚠️