Skip to content

Commit ffb02f4

Browse files
author
RahulMR42
committed
Stateful set
1 parent d510920 commit ffb02f4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+618
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# General
22
.DS_Store
3+
.idea
4+
**.idea**
35
.AppleDouble
46
.LSOverride
57

oci-deployment-examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ All about OCI devops deployment samples ..
1919
<details>
2020
<summary>Canary deployment strategy - click to expand</summary>
2121

22+
* [Deploy to OKE with a stateful backend data with canary strategy](./oci-devops-oke-canary-with-stateful-app/)
2223
* [Deploy to OKE following OCI Devops canary deployment strategy](./oci-devops-deploy-with-canary-model-oke/)
2324
* [Deploy to Instances following OCI Devops canary deployment strategy](./oci-devops-deploy-instances-with-canary/)
2425

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.DS**
2+
.idea**

oci-deployment-examples/oci-devops-oke-canary-with-stateful-app/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
An illustration of OCI Deployment Canary strategy with a stateful application.---Context-------- The illustration provides a deployment flow of a sample application with a stateful data source shared across .- The deployment follows [OCI Canary Deployment strategy model](https://docs.oracle.com/en/solutions/plan-mad-strats-devops/understand-depoyment-architectures.html#GUID-1E607CF8-3446-476F-9372-355937F209ED).- Objective here to ensure backend data consistency across canary and production stages (with in the same stateful data source).- Here the focus is only for OCI Deployment pipeline ,but the same can be integrated along with other OCI devops services as well (Build, Code repo etc)![](images/oci_context.png)Application topology---- Sample application build using Guestbook app - https://kubernetes.io/docs/tutorials/stateless-application/guestbook/- Stateful database is build using redis - inspired by blog by Okteto - https://www.okteto.com/blog/connect-applications-across-namespaces/- We will be using Oracle Kubernetes Engine (OKE) for application deployment.Specific instructions to download only this example .---``` $ git init oci-devops-oke-canary-with-stateful-app $ cd oci-devops-deploy-with-canary-model-oke $ git remote add origin https://github.com/oracle-devrel/oci-devops-examples $ git config core.sparsecheckout true $ echo "oci-deployment-examples/oci-devops-oke-canary-with-stateful-app/*">>.git/info/sparse-checkout $ git pull --depth=1 origin main```Procedure---- Create an OCI Dynamic group and add below rules.```ALL {resource.type = 'devopsdeploypipeline', resource.compartment.id = 'ocid1.compartment.xxxx'}```- Create an OCI Policy as below.```Allow dynamic-group <DG NAME> to manage repos in compartment <COMPARTMENT NAME>Allow dynamic-group <DG NAME> to manage generic-artifacts in compartment <COMPARTMENT NAME>Allow dynamic-group <DG NAME> to use ons-topics in compartment <COMPARTMENT NAME>Allow dynamic-group <DG NAME> to read all-artifacts in compartment <COMPARTMENT NAME>Allow dynamic-group <DG NAME> to manage cluster-family in compartment <COMPARTMENT NAME>```- Create an OCI Container registry repo (enable as public repo ) - https://docs.oracle.com/en-us/iaas/Content/Registry/home.htm![](images/oci_container_registr.png)- Create an OKE - https://docs.oracle.com/en-us/iaas/Content/ContEng/Tasks/contengcreatingclusterusingoke_topic-Using_the_Console_to_create_a_Quick_Cluster_with_Default_Settings.htm#create-quick-cluster![](images/oci_oke.png)- We are using NGINX to switch the traffic between two namespaces (Canary and Production).- To do so , Use ``Access Cluster option `` of OKE ,launch the cloud shell and set the config to access the cluster.![](images/oci_oke_access_cluster.png)- We will be following the procedure mentioned [here](https://docs.oracle.com/en-us/iaas/Content/ContEng/Tasks/contengsettingupingresscontroller.htm) to setup the ingress controller.```$ kubectl create clusterrolebinding <my-cluster-admin-binding> --clusterrole=cluster-admin --user=<user-OCID>$ export version="Latest Nginix release version " - https://github.com/kubernetes/ingress-nginx#support-versions-table$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-${version}/deploy/static/provider/cloud/deploy.yaml$ Validate the Nginx installation.```![](images/oci_nginx_validation.png)- Create the docker images and push to the OCI Artifact registry```markdown$ git clone <REPO URL>$ cd <REPO NAME>/dockerArtifacts/v0/$ docker build -t <OCI REGION>.ocir.io/<NAMESPACE>/<REPO NAME>:v0 -f Dockerfile . # Create image with version V0.$ cd ../v1/$ docker build -t <OCI REGION>.ocir.io/<NAMESPACE>/<REPO NAME>:v0 -f Dockerfile . # Create image with version V1.$ docker push <OCI REGION>.ocir.io/<NAMESPACE>/<REPO NAME> # Push both the images back to the OCI artifact repo.```- Validate the images are visible under the Container registry repo created.![](images/oci_container_images.png)- Create an OCI Notification topic which will be associated with devops project - https://docs.oracle.com/en-us/iaas/Content/Notification/home.htm- Create a new Devops Project - https://docs.oracle.com/en-us/iaas/Content/devops/using/devops_projects.htm![](images/oci_projecta.png)- Ensure to enable logs for the OCI Devops Projects.![](images/oci_devops_logs.png)- Create an OCI Devops environment (type as Oracle Kubernetes Engine) - https://docs.oracle.com/en-us/iaas/Content/devops/using/create_oke_environment.htm ![](images/oci_devops_env.png)- Create an OCI devops artifact with type 'Kubernetes manifest' and store the artifacts from file [guestbook_app.yaml](appConfig/guestbook_app.yml)![](images/oci_artifact_guestbook.png)- Create another OCI devops artifact with type 'Kubernetes manifest' and store the artifacts from file [redis.yaml](appConfig/redis.yml)![](images/oci_artifact_redis.png)- Create an OCI deployment pipeline for the Redis DB deployment.- With in the deployment pipeline add a deployment stage as type `Apply manifest to Your Kubernetes`![](images/oci_deploy_stagea.png)- Associate with the `OKE devops environment` and `Redis.yaml` artifact . Use the namespace as `ns-prod` to deploy the DB under production namespace.![](images/oci_deploy_stage_redis.png)- Create another OCI devops deployment pipeline for the application with Canary deployment.- Use 'Canary Strategy' as the stage type.![](images/oci_canary_stage.png)- Select the deployment type as OKE , use `ns-canary` as canary namespace and `ns-prod` for production namespace.- Associate it with the guest book artifacts created. ![](images/oci_oke_canary_1.png)- Use `guestbook-ingress` as the NGINX ingress name.- Use ramp limit as 25% under canary shift so that 25 % of traffic will be served via the new version.![](images/oci_oke_canary_2.png)- You may skip the validation step and add at least one as approval count .![](images/oci_oke_canary_3.png)![](images/oci_oke_canary_4.png)- The final view of deployment pipeline is as below .![](images/oci_deploymentpipeline_guestbook.png)- Let us do the db deployment , to do so ,use the `redis db` pipeline created and execute it via `Run pipeline option.![](images/oci_redis_dbdeployment.png)- Wait for all the steps to complete .![](images/oci_redis_deploy_steps.png)- Validate the redis deployment using `kubectl` over cloudshell .```markdown$ kubectl get all -n ns-prod |grep -i redis$ kubectl logs -n ns-prod pod/redis-0```![](images/oci_kubectl_redis.png)- Switch to the deployment pipeline created for guestbook.- Add two deployment parameters as below```markdown- "version" with no default value ,its the docker image version that will be used .- "redis_hosts" with default value as redis.ns-prod.svc.cluster.local ,its the service endpoint to access the redis db. ```![](images/oci_deploy_param.png)- Do a manual run of the guestbook deployment pipeline with image version as `v0`.![](images/oci_deploy_guestbook1.png)- Since its the first deployment we wont be seeing any canary shift as it needs ingress controller to be established (at least one time) with in the namespaces.- So approve the stage and complete until the last step for the deployment.![](images/oci_deploy_approval.png)![](images/oci_deploy_approved.png)- Wait for all the deployment steps to complete .![](images/oci_deployment_all_steps.png)- To validate the application ,switch to `OCI Cloud shell and use Kubectl`.```markdown$ kubectl get all,ing -n ns-prod```![](images/oci_kubectl_geting.png)- Fetch the Ingress public ip and use a browser to view the application .![](images/oci_guestbook_app.png)- Please note you may have a different colour ,which is perfectly ok ,but if you see an error stating `Waiting for DB connection` , recheck your Redis URL and Redis deployment ,as it is unable to connect to the data base- Let us add some data to the application and press submit.![](images/oci_app_data.png)- Switch to the deployment pipeline and make a new deployment using version `v1`![](images/oci_pipeline_v2_run.png)- Once the stage for `Canary traffic shift` is completed ,refresh the browser and view that 25 % of traffic will be via version v2. ![](images/oci_pipeline_canary_shift_ok.png)- You may disable/clear the cache of your browser if it’s not appearing after some refresh.![](images/oci_app_v2_canary.png)- As you may see the version is changed to v1 and the data is persisting .Let us add some data to the page again .![](images/oci_app_canary_data.png)- If you refresh you can see that V0 is also existing as the V1 is routed via canary ,with 25 % of traffic to it .- Also the data is always persisting . ![](images/oci_app_with_canaryv1_prod_v0.png)- Switch to the deployment pipeline and approve for the V1 deployment for production.![](images/oci_app_v1_approval.png)- Once all the stages are completed ,you can see the application is 100 % v1 and data is always persisting.![](images/final_version.png)Acknowledgements------ https://github.com/okteto/go-guestbook- https://kubernetes.io/docs/tutorials/stateless-application/guestbook/Contributors====Author : Rahul M R.Collaborators : Michael LiLast release : May 2022### Back to examples.----- 🍿 [Back to OCI Devops Deployment sample](./../README.md)- 🏝️ [Back to OCI Devops sample](./../../README.md)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: guestbook
5+
spec:
6+
replicas: 1
7+
selector:
8+
matchLabels:
9+
app: guestbook
10+
template:
11+
metadata:
12+
labels:
13+
app: guestbook
14+
spec:
15+
containers:
16+
- image: us-ashburn-1.ocir.io/fahdabidiroottenancy/mr-guestbook:${version}
17+
name: guestbook
18+
env:
19+
- name: REDIS_HOST
20+
value: ${redis_host}
21+
22+
---
23+
24+
25+
apiVersion: v1
26+
kind: Service
27+
metadata:
28+
name: guestbook
29+
spec:
30+
type: ClusterIP
31+
ports:
32+
- name: guestbook
33+
port: 8080
34+
selector:
35+
app: guestbook
36+
37+
---
38+
apiVersion: networking.k8s.io/v1
39+
kind: Ingress
40+
metadata:
41+
name: poc-ing
42+
annotations:
43+
kubernetes.io/ingress.class: "nginx"
44+
spec:
45+
rules:
46+
- http:
47+
paths:
48+
- path: /
49+
pathType: Prefix
50+
backend:
51+
service:
52+
name: guestbook
53+
port:
54+
number: 8080
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: redis
5+
labels:
6+
app: redis
7+
spec:
8+
selector:
9+
app: redis
10+
ports:
11+
- name: redis
12+
port: 6379
13+
14+
---
15+
16+
apiVersion: apps/v1
17+
kind: StatefulSet
18+
metadata:
19+
name: redis
20+
spec:
21+
serviceName: redis
22+
replicas: 1
23+
selector:
24+
matchLabels:
25+
app: redis
26+
template:
27+
metadata:
28+
labels:
29+
app: redis
30+
selector: redis
31+
spec:
32+
containers:
33+
- name: redis
34+
image: redis:6
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM golang:buster as builder
2+
3+
WORKDIR /usr/src/app
4+
COPY go.mod go.sum ./
5+
RUN go mod download
6+
COPY main.go .
7+
RUN CGO_ENABLED=0 GOOS=linux go build -o /usr/local/bin/guestbook
8+
9+
FROM debian:buster
10+
COPY ./public/index.html public/index.html
11+
COPY ./public/script.js public/script.js
12+
COPY ./public/style.css public/style.css
13+
COPY --from=builder /usr/local/bin/guestbook /usr/local/bin/guestbook
14+
CMD ["/usr/local/bin/guestbook"]
15+
EXPOSE 8080
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module github.com/okteto/guestbook
2+
3+
go 1.15
4+
5+
require (
6+
github.com/codegangsta/negroni v1.0.0
7+
github.com/gorilla/mux v1.8.0
8+
github.com/xyproto/simpleredis v0.0.0-20200923133513-36cf6df0f760
9+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
2+
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
3+
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
4+
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
5+
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
6+
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
7+
github.com/xyproto/pinterface v0.0.0-20200201214933-70763765f31f h1:x97Isxzsv8Aj1QMh8vNxKzI85Fvgg2areAJlIaMQ4zY=
8+
github.com/xyproto/pinterface v0.0.0-20200201214933-70763765f31f/go.mod h1:X5B5pKE49ak7SpyDh5QvJvLH9cC9XuZNDcl5hEyYc34=
9+
github.com/xyproto/simpleredis v0.0.0-20200923133513-36cf6df0f760 h1:K5aLh/GzKwre9Mb9J2Y4H318HOLyW4UAeGOkshnSvXc=
10+
github.com/xyproto/simpleredis v0.0.0-20200923133513-36cf6df0f760/go.mod h1:U/ZOQqa0ggBGPs+d0y7r50BY6FyFTh5WhWf7F8f1MBM=
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
Copyright 2014 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package main
15+
16+
import (
17+
"encoding/json"
18+
"fmt"
19+
"net/http"
20+
"os"
21+
"strings"
22+
23+
"github.com/codegangsta/negroni"
24+
"github.com/gorilla/mux"
25+
"github.com/xyproto/simpleredis"
26+
)
27+
28+
var (
29+
mainPool *simpleredis.ConnectionPool
30+
)
31+
32+
func ListRangeHandler(rw http.ResponseWriter, req *http.Request) {
33+
key := mux.Vars(req)["key"]
34+
list := simpleredis.NewList(mainPool, key)
35+
members := HandleError(list.GetAll()).([]string)
36+
membersJSON := HandleError(json.MarshalIndent(members, "", " ")).([]byte)
37+
rw.Write(membersJSON)
38+
}
39+
40+
func ListPushHandler(rw http.ResponseWriter, req *http.Request) {
41+
key := mux.Vars(req)["key"]
42+
value := mux.Vars(req)["value"]
43+
list := simpleredis.NewList(mainPool, key)
44+
HandleError(nil, list.Add(value))
45+
ListRangeHandler(rw, req)
46+
}
47+
48+
func InfoHandler(rw http.ResponseWriter, req *http.Request) {
49+
info := HandleError(mainPool.Get(0).Do("INFO")).([]byte)
50+
rw.Write(info)
51+
}
52+
53+
func EnvHandler(rw http.ResponseWriter, req *http.Request) {
54+
environment := make(map[string]string)
55+
for _, item := range os.Environ() {
56+
splits := strings.Split(item, "=")
57+
key := splits[0]
58+
val := strings.Join(splits[1:], "=")
59+
environment[key] = val
60+
}
61+
62+
envJSON := HandleError(json.MarshalIndent(environment, "", " ")).([]byte)
63+
rw.Write(envJSON)
64+
}
65+
66+
func HandleError(result interface{}, err error) (r interface{}) {
67+
if err != nil {
68+
panic(err)
69+
}
70+
return result
71+
}
72+
73+
func main() {
74+
host := os.Getenv("REDIS_HOST")
75+
mainPool = simpleredis.NewConnectionPoolHost(fmt.Sprintf("%s:6379", host))
76+
defer mainPool.Close()
77+
78+
r := mux.NewRouter()
79+
r.Path("/lrange/{key}").Methods("GET").HandlerFunc(ListRangeHandler)
80+
r.Path("/rpush/{key}/{value}").Methods("GET").HandlerFunc(ListPushHandler)
81+
r.Path("/info").Methods("GET").HandlerFunc(InfoHandler)
82+
r.Path("/env").Methods("GET").HandlerFunc(EnvHandler)
83+
84+
n := negroni.Classic()
85+
n.UseHandler(r)
86+
n.Run(":8080")
87+
}

0 commit comments

Comments
 (0)