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.
- AWS account
- Aws client
- Go 1.12
And for testing the Oracle RDS:
- sqlplus
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.
On the "Templates" card we are selecting the "Free tier" option for the purpose of this tutorial.
On "Settings" define the name of the database and the username and password.
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.
Then click "Create Database" and wait for the database to be created.
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');
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
}
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"]
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
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
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"}]