Merge from master into my fork

This commit is contained in:
Evan Yeager 2017-04-04 13:24:40 -06:00
Родитель e8e821f255 1a200a1ced
Коммит c8805f9dc2
53 изменённых файлов: 1705 добавлений и 469 удалений

5
.gitignore поставляемый
Просмотреть файл

@ -1,2 +1,5 @@
.DS_Store
.vscode
.vscode
node_modules
node_modules/
node_modules/*

27
Jenkinsfile поставляемый Normal file
Просмотреть файл

@ -0,0 +1,27 @@
node {
checkout scm
env.DOCKER_API_VERSION="1.23"
sh "git rev-parse --short HEAD > commit-id"
tag = readFile('commit-id').replace("\n", "").replace("\r", "")
appName = "hello-kenzan"
registryHost = "127.0.0.1:30400/"
imageName = "${registryHost}${appName}:${tag}"
env.BUILDIMG=imageName
stage "Build"
sh "docker build -t ${imageName} -f applications/hello-kenzan/Dockerfile applications/hello-kenzan"
stage "Push"
sh "docker push ${imageName}"
stage "Deploy"
sh "sed 's#127.0.0.1:30400/hello-kenzan:latest#'$BUILDIMG'#' applications/hello-kenzan/k8s/deployment.yaml | kubectl apply -f -"
sh "kubectl rollout status deployment/hello-kenzan"
}

228
README.md
Просмотреть файл

@ -1,5 +1,229 @@
# Kubernetes white paper for Linux.com
# Kubernetes ci/cd whitepaper for Linux.com
Running `build.rb` compiles docs.yml into `README.md` into `RUNME.sh` where all commands can be automatically executed, one at a time.
This readme is dynamically generated when `node readme.js`
## Interactive tutorial version
* clone this repo
* Ensure you are starting with a clean slate: `minikube delete; minikube rm -rf ~/.minikube; rm -rf ~/.kube`
* run `npm install`
Begin the tutorial `npm start`
## Manual tutorial version
## Part 1
### Part 1
### Step1
Start up the cluster with minikibe
`minikube start --memory 6000 --cpus 2 --kubernetes-version v1.6.0`
### Step2
Enable addons
`minikube addons enable heapster; minikube addons enable ingress`
### Step3
Wait 20 seconds and view minikube dashboard
`sleep 20; minikube service kubernetes-dashboard --namespace kube-system`
### Step4
Deploy the public nginx image from DockerHub
`kubectl run nginx --image nginx --port 80`
### Step5
Create a service for deployment
`kubectl expose deployment nginx --type NodePort --port 80`
### Step6
Launch browser to test service
`minikube service nginx`
### Step7
Install registry
`kubectl apply -f manifests/registry.yml`
### Step8
Wait for registry to deploy
`kubectl rollout status deployments/registry`
### Step9
View registry UI
`minikube service registry-ui`
### Step10
Edit the contents of applications/hello-kenzan/index.html. This will open the file with the nano editor. When finished press ctrl + x to exit and confirm save.
`nano applications/hello-kenzan/index.html`
### Step11
We will now build the image with a special name that is pointing at our cluster registry.
`docker build -t 127.0.0.1:30400/hello-kenzan:latest -f applications/hello-kenzan/Dockerfile applications/hello-kenzan`
### Step12
Before we can push our image we need to set up a temporary proxy. This is a container that listens on 127.0.0.1:30400 and forwads to our cluster. By default the docker client can only push to non https via localhost.
`docker stop socat-registry; docker rm socat-registry; docker run -d -e "REGIP=`minikube ip`" --name socat-registry -p 30400:5000 chadmoon/socat:latest bash -c "socat TCP4-LISTEN:5000,fork,reuseaddr TCP4:`minikube ip`:30400"`
### Step13
We can now push our image.
`docker push 127.0.0.1:30400/hello-kenzan:latest`
### Step14
Stop the registry proxy.
`docker stop socat-registry;`
### Step15
Now that our image is on the cluster we can deploy the manifests
`kubectl apply -f applications/hello-kenzan/k8s/deployment.yaml`
### Step16
View the app
`minikube service hello-kenzan`## Part 2
### Part 2
### Step1
Install Jenkins
`kubectl apply -f manifests/jenkins.yml; kubectl rollout status deployment/jenkins`
### Step2
Get Jenkins admin password
`kubectl exec -it `kubectl get pods --selector=app=jenkins --output=jsonpath={.items..metadata.name}` cat /root/.jenkins/secrets/initialAdminPassword`
### Step3
Enter the admin password from above and choose "suggested plugins". Create a new job with type pipeline. Scroll down and under "pipeline script" choose "Pipeline script from SCM". Under SCM choose GIT. Fork repo and put "repository url" as your fork, such as https://github.com/kenzanlabs/kubernetes-ci-cd.git. Save and run the job.
`minikube service jenkins`
### Step4
View updated application
`minikube service hello-kenzan`
### Step5
Push a change to your fork. Run job again. View changes
`minikube service hello-kenzan`## Part 4
### Part 4
### Step1
Bootstrap etcd operator on the cluster
`scripts/etcd.sh`
### Step2
Run job to create etcd directory
`kubectl create -f manifests/etcd-job.yml`
### Step3
Check job status
`kubectl describe jobs/etcd-job`
### Step4
build kubescale image
`docker build -t 127.0.0.1:30400/kubescale:latest -f applications/kubescale/Dockerfile applications/kubescale`
### Step5
build scaling image
`docker build -t 127.0.0.1:30400/set:latest -f applications/kubescale/set/Dockerfile applications/kubescale/set`
### Step6
Start the registry proxy.
`docker stop socat-registry; docker rm socat-registry; docker run -d -e "REGIP=`minikube ip`" --name socat-registry -p 30400:5000 chadmoon/socat:latest bash -c "socat TCP4-LISTEN:5000,fork,reuseaddr TCP4:`minikube ip`:30400"`
### Step7
Push the kubescale image
`docker push 127.0.0.1:30400/kubescale:latest`
### Step8
Push the scaling image
`docker push 127.0.0.1:30400/set:latest`
### Step9
Stop the registry proxy
`docker stop socat-registry`
### Step10
Deploy kubescale
`kubectl apply -f applications/kubescale/k8s/kubescale.yml; kubectl rollout status deployment/kubescale`
### Step11
Deploy scaling set
`kubectl apply -f applications/kubescale/k8s/set.yml; kubectl rollout status deployment/set`
### Step12
View kubescale application
`minikube service kubescale`

Просмотреть файл

@ -1,5 +1,4 @@
<p><h2 style="font-family:sans-serif">Hello from Kenzan! You've successfully built and run the Hello-Kenzan app.</h2> </p>
<p style="font-family:sans-serif">The Hello-Kenzan app is a modified version of the <a href="https://hub.docker.com/_/nginx/">nginx web server image</a>. If you open up the <b>kubernetes-ci-cd/part1/hello-kenzan/DockerFile</b>, you will note several things:</p>
<img src="DockerFileEx.jpg">
<p style="font-family:sans-serif">For more from Kenzan, check out our <a href="http://techblog.kenzan.com/">blog</a>.</p>
<img src="DockerFileEx.jpg">

Просмотреть файл

@ -30,23 +30,23 @@ spec:
tier: hello-kenzan
spec:
containers:
- image: 127.0.0.1:30912/hellokenzan:latest
- image: 127.0.0.1:30400/hello-kenzan:latest
name: hello-kenzan
ports:
- containerPort: 80
name: hello-kenzan
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: hello-kenzan-ingress
spec:
rules:
- host: hello-kenzan.192.168.99.100.xip.io
http:
paths:
- path: /
backend:
serviceName: hello-kenzan
servicePort: 80
# ---
# apiVersion: extensions/v1beta1
# kind: Ingress
# metadata:
# name: hello-kenzan-ingress
# spec:
# rules:
# - host: hello-kenzan.MINIKUBEIP.xip.io
# http:
# paths:
# - path: /
# backend:
# serviceName: hello-kenzan
# servicePort: 80

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -0,0 +1,38 @@
apiVersion: v1
kind: Service
metadata:
name: kubescale
labels:
app: kubescale
spec:
ports:
- port: 3000
targetPort: 3000
selector:
app: kubescale
tier: kubescale
type: NodePort
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: kubescale
labels:
app: kubescale
spec:
strategy:
type: Recreate
template:
metadata:
labels:
app: kubescale
tier: kubescale
spec:
containers:
- image: 127.0.0.1:30400/kubescale:latest
imagePullPolicy: Always
name: kubescale
ports:
- containerPort: 3000
name: kubescale

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -12,6 +12,6 @@ COPY down.sh /down.sh
RUN npm install
RUN mpm install -g nodemon
RUN npm install -g nodemon
CMD ["nodemon", "server.js"]

Просмотреть файл

Просмотреть файл

Просмотреть файл

17
manifests/etcd-job.yml Normal file
Просмотреть файл

@ -0,0 +1,17 @@
apiVersion: batch/v1
kind: Job
metadata:
name: etcd-job
spec:
template:
metadata:
name: etcd-job
spec:
containers:
- name: etcd-job
image: tenstartups/etcdctl
env:
- name: ETCDCTL_ENDPOINT
value: 'http://example-etcd-cluster-client-service:2379'
command: ["etcdctl", "mkdir", "pod-list"]
restartPolicy: Never

Просмотреть файл

@ -73,24 +73,4 @@ spec:
path: /var/run/docker.sock
- name: jenkins-persistent-storage
persistentVolumeClaim:
claimName: jenkins-claim
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: jenkins-services
annotations:
ingress.kubernetes.io/rewrite-target: /
spec:
backend:
serviceName: default-http-backend
servicePort: 80
rules:
- host: jenkins.127.0.0.1.xip.io
http:
paths:
- path: /
backend:
serviceName: jenkins
servicePort: 8080
claimName: jenkins-claim

Просмотреть файл

@ -0,0 +1,23 @@
FROM node:6
RUN mkdir /app
COPY . /app
WORKDIR /app
RUN apt-get update
RUN apt-get install -y apache2
RUN npm install -g loadtest
RUN npm install -g nodemon
RUN npm install
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
RUN chmod +x ./kubectl; mv ./kubectl /usr/local/bin/kubectl
CMD ["nodemon", "index.js"]

Просмотреть файл

@ -0,0 +1,95 @@
var express = require('express')
var app = express()
var http = require('http').Server(app);
var io = require('socket.io')(http);
var path = require("path");
var Etcd = require('node-etcd')
app.use(express.static('public'))
var exec = require('child_process').exec;
var bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
//etcd = new Etcd("http://localhost:2379")
///tmp/test-etcd/etcdctl --endpoints "http://example-etcd-cluster-client-service:2379" mkdir pod-list
etcd = new Etcd("http://example-etcd-cluster-client-service:2379")
//etcd.mkdir("pods");
watcher = etcd.watcher("pod-list", null, {recursive: true})
watcher.on("change", showVal);
function showVal(val) {
pods = etcd.getSync("pod-list",{ recursive: true })
io.emit('pods', { pods: pods.body.node.nodes });
}
app.post('/scale', function (req, res) {
exec('kubectl scale --replicas=' + req.body.count + ' deployment/set', function(error, stdout, stderr) {
res.send("scaled to " + req.body.count);
});
})
app.post('/loadtest/concurrent', function (req, res) {
//svc = "http://localhost:8001/api/v1/proxy/namespaces/default/services/set:80"
svc = "http://set:80/"
// exec('loadtest -c ' + req.body.count + ' -n ' + req.body.count + ' http://set', function(error, stdout, stderr) {
exec('ab -c ' + req.body.count + ' -n ' + req.body.count + ' ' + svc, function(error, stdout, stderr) {
console.log(stdout);
res.send(stdout);
});
})
app.post('/loadtest/consecutive', function (req, res) {
svc = "http://set:80/"
// exec('loadtest -c ' + req.body.count + ' -n ' + req.body.count + ' http://set', function(error, stdout, stderr) {
exec('ab -c 1 -n ' + req.body.count + ' ' + svc, function(error, stdout, stderr) {
console.log(stdout);
res.send(stdout);
});
})
app.get('/up/:podId', function (req, res) {
etcd.set("pod-list/" + req.params.podId, req.params.podId);
res.send('done');
})
app.get('/down/:podId', function (req, res) {
etcd.del("pod-list/" + req.params.podId, req.params.podId);
res.send('done');
})
app.get('/hit/:podId', function (req, res) {
var d = new Date();
var n = d.getTime();
io.emit('hit', { podId: req.params.podId, time: n });
console.log('hit!');
res.send('done')
})
io.on('connection', function(socket){
pods = etcd.getSync("pod-list",{ recursive: true })
io.emit('pods', { pods: pods.body.node.nodes });
});
app.get('/',function(req,res){
res.sendFile(path.join(__dirname+'/public/index.html'));
});
http.listen(3000, function () {
console.log('Example app listening on port 3000!')
})

Просмотреть файл

@ -0,0 +1,15 @@
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: dashboard-ingress
namespace: kube-system
spec:
rules:
- host: dashboard.127.0.0.1.xip.io
http:
paths:
- path: /
backend:
serviceName: kubernetes-dashboard
servicePort: 80

Просмотреть файл

@ -0,0 +1,46 @@
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-services
namespace: kube-system
annotations:
ingress.kubernetes.io/rewrite-target: /
spec:
backend:
serviceName: default-http-backend
servicePort: 80
rules:
- host: dashboard.127.0.0.1.xip.io
http:
paths:
- path: /
backend:
serviceName: kubernetes-dashboard
servicePort: 80
- host: grafana.127.0.0.1.xip.io
http:
paths:
- path: /
backend:
serviceName: monitoring-grafana
servicePort: 80
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-services
annotations:
ingress.kubernetes.io/rewrite-target: /
spec:
backend:
serviceName: default-http-backend
servicePort: 80
rules:
- host: kubescale.127.0.0.1.xip.io
http:
paths:
- path: /
backend:
serviceName: kubescale
servicePort: 3000

Просмотреть файл

@ -0,0 +1,96 @@
kind: PersistentVolume
apiVersion: v1
metadata:
name: jenkins
labels:
type: local
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/data/jenkins/"
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: jenkins-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Service
metadata:
name: jenkins
labels:
app: jenkins
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: jenkins
tier: jenkins
type: NodePort
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: jenkins
labels:
app: jenkins
spec:
strategy:
type: Recreate
template:
metadata:
labels:
app: jenkins
tier: jenkins
spec:
containers:
- image: chadmoon/jenkins-docker-kubectl:latest
name: jenkins
securityContext:
privileged: true
ports:
- containerPort: 8080
name: jenkins
volumeMounts:
- name: jenkins-persistent-storage
mountPath: /root/.jenkins
- name: docker
mountPath: /var/run/docker.sock
volumes:
- name: docker
hostPath:
path: /var/run/docker.sock
- name: jenkins-persistent-storage
persistentVolumeClaim:
claimName: jenkins-claim
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: jenkins-services
annotations:
ingress.kubernetes.io/rewrite-target: /
spec:
backend:
serviceName: default-http-backend
servicePort: 80
rules:
- host: jenkins.127.0.0.1.xip.io
http:
paths:
- path: /
backend:
serviceName: jenkins
servicePort: 8080

Просмотреть файл

@ -0,0 +1,142 @@
kind: PersistentVolume
apiVersion: v1
metadata:
name: registry
labels:
type: local
spec:
capacity:
storage: 4Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/data/registry/"
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: registry-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 4Gi
---
apiVersion: v1
kind: Service
metadata:
name: registry
labels:
app: registry
spec:
ports:
- port: 5000
targetPort: 5000
nodePort: 30400
name: registry
selector:
app: registry
tier: registry
type: NodePort
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: registry
labels:
app: registry
spec:
strategy:
type: Recreate
template:
metadata:
labels:
app: registry
tier: registry
spec:
containers:
- image: registry:2
name: registry
volumeMounts:
- name: docker
mountPath: /var/run/docker.sock
- name: registry-persistent-storage
mountPath: /var/lib/registry
ports:
- containerPort: 5000
name: registry
- name: registryui
image: hyper/docker-registry-web
ports:
- containerPort: 8080
env:
- name: REGISTRY_URL
value: http://localhost:5000/v2
- name: REGISTRY_NAME
value: cluster-registry
volumes:
- name: docker
hostPath:
path: /var/run/docker.sock
- name: registry-persistent-storage
persistentVolumeClaim:
claimName: registry-claim
# ---
# apiVersion: extensions/v1beta1
# kind: Deployment
# metadata:
# name: registryui
# spec:
# replicas: 1
# template:
# metadata:
# labels:
# app: registryui
# spec:
# containers:
# - name: registryui
# image: hyper/docker-registry-web
# ports:
# - containerPort: 8080
# env:
# - name: REGISTRY_URL
# value: http://registry:5000/v2
# - name: REGISTRY_NAME
# value: cluster-registry
# ---
# apiVersion: v1
# kind: Service
# metadata:
# name: registryui
# labels:
# app: registryui
# spec:
# ports:
# - port: 8080
# targetPort: 8080
# name: registry
# selector:
# app: registryui
# tier: registryui
# type: NodePort
# ---
# apiVersion: extensions/v1beta1
# kind: Ingress
# metadata:
# name: registryui-ingress
# spec:
# rules:
# - host: registry.192.168.64.3.xip.io
# http:
# paths:
# - path: /
# backend:
# serviceName: registryui
# servicePort: 8080

Просмотреть файл

@ -0,0 +1,43 @@
apiVersion: v1
kind: Service
metadata:
name: set
labels:
app: set
spec:
ports:
- port: 80
targetPort: 80
selector:
app: set
tier: set
type: NodePort
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: set
labels:
app: set
spec:
strategy:
type: Recreate
template:
metadata:
labels:
app: set
tier: set
spec:
containers:
- name: lifecycle-demo-container
image: 127.0.0.1:30400/set:latest
lifecycle:
postStart:
exec:
command: ["/up.sh"]
preStop:
exec:
command: ["/down.sh"]

Просмотреть файл

@ -0,0 +1,35 @@
apiVersion: v1
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: traefik-ingress-controller
namespace: kube-system
labels:
k8s-app: traefik-ingress-lb
spec:
replicas: 1
selector:
matchLabels:
k8s-app: traefik-ingress-lb
template:
metadata:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
spec:
terminationGracePeriodSeconds: 60
hostNetwork: true
containers:
- image: traefik
name: traefik-ingress-lb
ports:
- name: http
containerPort: 80
hostPort: 80
- name: admin
containerPort: 8081
args:
- -d
- --web
- --web.address=:8081
- --kubernetes

18
manifests/kubescale/ksh Executable file
Просмотреть файл

@ -0,0 +1,18 @@
#!/bin/sh
if [ "$1" = "" ]; then
echo "Usage: ksh <pod> [flags_to_kubectl]"
exit 1
fi
POD=$1
shift
COLUMNS=`tput cols`
LINES=`tput lines`
TERM=xterm
KUBE_SHELL=${KUBE_SHELL:-bash}
kubectl exec -i -t $POD "$@" -- env COLUMNS=$COLUMNS LINES=$LINES TERM=$TERM "$KUBE_SHELL"
#$ ./ksh pod_name
#$ ./ksh pod_name --namespace=kube-system
#$ KUBE_SHELL=sh ./ksh pod_name

Просмотреть файл

@ -0,0 +1,29 @@
{
"name": "app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"@blueprintjs/core": "^1.12.0",
"body-parser": "^1.17.1",
"classnames": "^2.2.5",
"express": "^4.15.2",
"express-ws": "^3.0.0",
"node-etcd": "^5.0.3",
"node-serialize": "0.0.4",
"nodejs-etcd": "^0.1.1",
"react": "^15.4.2",
"react-addons-css-transition-group": "^15.4.2",
"react-dom": "^15.4.2",
"socket.io": "^1.7.3",
"tether": "^1.4.0",
"ws": "^2.2.1"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
"author": "",
"license": "ISC"
}

Просмотреть файл

@ -0,0 +1,232 @@
<!DOCTYPE HTML>
<html>
<head>
<link href="https://unpkg.com/normalize.css@^4.1.1" rel="stylesheet" />
<link href="https://unpkg.com/@blueprintjs/core@^1.11.0/dist/blueprint.css" rel="stylesheet" />
<meta charset="utf-8">
<title>Chat example</title>
<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="http://code.jquery.com/color/jquery.color-2.1.2.min.js"></script>
<style>
.card{
width: 250px;
margin: 5px;
float: left;
}
</style>
<script>
var socket = io();
var firstRun = true;
socket.on('hit', function(msg){
console.log("hit!" + msg.podId)
pulse(msg.podId);
});
socket.on('pods', function(msg){
console.log(msg);
setInterval(function(){
if ($("#current").val() != $("#count").val()){
$("#progress").show();
}else{
$("#progress").hide();
}
});
$("#pods").html('');
$("#current").val(msg.pods.length)
if(firstRun){
$("#count").val(msg.pods.length)
firstRun = false;
}
msg.pods.forEach(function(pod) {
$("#pods").append('<div style="position:relative;" class="' + pod.value + ' card pt-card pt-elevation-2">' + pod.value + '</div>')
});
})
function down(){
$("#count").val(($("#count").val()*1) - 1);
scale();
}
function up(){
$("#count").val(($("#count").val()*1) + 1);
scale();
}
function scale() {
$.post( "/scale", { count: $("#count").val() } );
$("#progress").show();
}
function loadTest() {
$.post( "/loadtest/" + $("#loadType").val() , { count: $("#loadTest").val() } );
//$("#progress").show();
//pulse("set-1165364669-d0lsf")
}
function pulse(podId) {
//$('.pods').removeClass('pulse')
// $('.' + podId + ' .star').fadeIn( 300, function() {
// $('.' + podId + ' .star').fadeOut( 300)
// });
//$('.' + podId + ' .star').show().fadeOut(500)
//$('.' + podId).slideUp(200).slideDown(200);
//var el = $('<div class="star" style="top:5px;right:5px;font-size:20px;color:#3DCC91"></div>');
$('.' + podId).animate( { backgroundColor: "#3DCC91" }, 500 ).animate( { backgroundColor: "#ffffff" }, 500 ).delay(1000);
//el.fadeOut(600)
//$('.' + podId).append()
//$('.' + podId + ' .star').fadeIn(300).fadeOut(300)
//$('.' + podId).append("<div></div>")
// $('.' + podId).css("backgroundColor", '#3DCC91');
// $('.' + podId).animate({backgroundColor: '#ffffff'}, 500);
}
</script>
</head>
<body>
<nav class="pt-navbar pt-dark">
<div class="pt-navbar-group pt-align-left">
<div class="pt-navbar-heading">KubeScale</div>
<div>
</div>
</div>
<div class="pt-navbar-group pt-align-right">
Current Replicas: &nbsp; <div class="pt-numeric-input pt-control-group"><div class="pt-input-group"><input size="3" id="current" type="text" class="pt-input" readonly style="padding-right: 0px;"></div></div>
<span class="pt-navbar-divider"></span>
Desired Replicas: &nbsp; <div class="pt-numeric-input pt-control-group"><div class="pt-input-group"><input size="3" type="text" onChange="scale()" id="count" class="pt-input" style="padding-right: 0px;"></div><div class="pt-button-group pt-vertical pt-fixed"><button onClick="up()" type="button" class="pt-button pt-icon-chevron-up"></button><button onClick="down()" type="button" class="pt-button pt-icon-chevron-down"></button></div></div>
<span class="pt-navbar-divider"></span>
<!--
<div class="pt-numeric-input pt-control-group"><div class="pt-input-group"><input id="loadTest" style="width:40px" type="text" class="pt-input" style="padding-right: 0px;"></div><div class="pt-button-group pt-vertical pt-fixed"><button type="button" class="pt-button pt-icon-chevron-up"></button><button type="button" class="pt-button pt-icon-chevron-down"></button></div></div>
-->
# of Requests: &nbsp;
<div class="pt-control-group">
<div class="pt-input-group">
<input type="text" size="3" id="loadTest" class="pt-input" />
</div>
<div class="pt-input-group">
<div class="pt-select">
<select id="loadType">
<option selected value="concurrent">Concurrent</option>
<option value="consecutive">Consecutive</option>
</select>
</div>
</div>
<button type="button" onClick="loadTest()" class="pt-button pt-intent-success">Load Test</button>
</div>
<div class="pt-input-group">
</div>
&nbsp;&nbsp;
</div>
</nav>
<div style="height:5px;">
<div id="progress" class="pt-progress-bar pt-intent-primary">
<div class="pt-progress-meter" style="width: 100%"></div>
</div>
</div>
<div id="pods"></div>
</body>
</html>

Просмотреть файл

@ -42,6 +42,24 @@ spec:
tier: registry
type: NodePort
---
apiVersion: v1
kind: Service
metadata:
name: registry-ui
labels:
app: registry
spec:
ports:
- port: 8080
targetPort: 8080
name: registry
selector:
app: registry
tier: registry
type: NodePort
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:

6
old/part1/hello-kenzan/Jenkinsfile поставляемый
Просмотреть файл

@ -7,7 +7,7 @@ node {
sh "git rev-parse --short HEAD > commit-id"
tag = readFile('commit-id').replace("\n", "").replace("\r", "")
appName = "hellokenzan"
appName = "hello-kenzan"
registryHost = "127.0.0.1:30912/"
imageName = "${registryHost}${appName}:${tag}"
env.BUILDIMG=imageName
@ -18,11 +18,9 @@ node {
stage "Push"
sh "docker login -u TOKEN -p 1konzk48p4i9e0fklikd45pdh9 127.0.0.1:30912"
sh "docker push ${imageName}"
stage "Deploy"
sh "sed 's#127.0.0.1:30912/hellokenzan:latest#'$BUILDIMG'#' part1/hello-kenzan/k8s/deployment.yaml | kubectl apply -f -"
sh "sed 's#__IMAGE__#'$BUILDIMG'#' part1/hello-kenzan/k8s/deployment.yaml | kubectl apply -f -"
}

Просмотреть файл

@ -1,5 +1,5 @@
#!/bin/bash
docker build -t `minikube ip`:30912/kr8sswordz:1.0.4 .
docker push `minikube ip`:30912/kr8sswordz:1.0.4
docker build -t `minikube ip`:30912/kr8sswordz:1.0.0 .
docker push `minikube ip`:30912/kr8sswordz:1.0.0
kubectl apply -f k8s/deployment.yaml

Двоичные данные
old/part2/pages/favicon.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 5.8 KiB

Просмотреть файл

@ -0,0 +1,66 @@
[
{
"word": "Kenzan",
"wordNbr": 1,
"startx" : 6,
"starty" : 0,
"wordOrientation": "down",
"hint": "Our company name :-)"
},
{
"word": "Minikube",
"wordNbr": 2,
"startx" : 9,
"starty" : 0,
"wordOrientation": "down",
"hint": "Tool that makes it easy to run kubernetes locally"
},
{
"word": "Replication",
"wordNbr": 3,
"startx" : 11,
"starty" : 0,
"wordOrientation": "down",
"hint": "Controller that ensures a specific number of pods are running at a given time"
},
{
"word": "Service",
"wordNbr": 4,
"startx" : 5,
"starty" : 1,
"wordOrientation": "across",
"hint": "Abstraction which defines a logical set of pods"
},
{
"word": "Kubectl",
"wordNbr": 5,
"startx" : 3,
"starty" : 4,
"wordOrientation": "down",
"hint": "cli tool for running commands against Kubernetes clusters"
},
{
"word": "Deployment",
"wordNbr": 6,
"startx" : 2,
"starty" : 7,
"wordOrientation": "across",
"hint": "Defines declarative updates for pods and replica sets"
},
{
"word": "Yaml",
"wordNbr": 7,
"startx" : 0,
"starty" : 10,
"wordOrientation": "across",
"hint": "Configuration format other then json"
},
{
"word": "k8",
"wordNbr": 5,
"startx" : 3,
"starty" : 4,
"wordOrientation": "across",
"hint": "Shorthand for Kubernetes"
}
]

Просмотреть файл

@ -1,23 +1,38 @@
import React from 'react';
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import homePageActions from '../../actions/homepageActions';
import PuzzleComponent from './PuzzleComponent';
import InstanceComponent from './InstancesComponent';
import DataFlowArrow from './DataFlowArrow';
import puzzleArray from '../../../puzzle.json';
class HomePage extends React.Component {
constructor (props) {
super(props);
this.state = {
puzzleGrid: [],
downHintsArray: [],
acrossHintsArray: [],
instanceData: {
instanceFinalCount: 3,
instanceCurrentCount: 3
}
};
this.initializeGrid = this.initializeGrid.bind(this);
this.initializePuzzleArray = this.initializePuzzleArray.bind(this);
this.addLetterToPuzzleArray = this.addLetterToPuzzleArray.bind(this);
this.handleSlider = this.handleSlider.bind(this);
this.onScale = this.onScale.bind(this);
}
componentWillMount () {
// iterate through json and load array. Also populate Across and Down arrays
this.initializePuzzleArray();
}
handleSlider (event, value) {
const instanceData = Object.assign({}, this.state.instanceData, {
instanceCurrentCount: value,
@ -40,10 +55,66 @@ class HomePage extends React.Component {
});
}
initializeGrid () {
const puzzleGrid = [];
const maxRows = 12;
const maxColumns = 11;
for (var i = 0; i < maxColumns; i++) {
puzzleGrid.push(new Array(maxRows).fill(''));
}
console.log(puzzleGrid);
return puzzleGrid;
}
initializePuzzleArray () {
const downHintsArray = puzzleArray.filter((word) => {
return (word.wordOrientation === 'down');
});
const acrossHintsArray = puzzleArray.filter((word) => {
return (word.wordOrientation === 'across');
});
let puzzleGrid = [...this.initializeGrid()];
puzzleArray.forEach((wordObj, index) => {
const lettersArray = wordObj.word.split('');
lettersArray.forEach((letter, index) => {
puzzleGrid = this.addLetterToPuzzleArray(puzzleGrid, wordObj, letter, index);
});
});
this.setState({
downHintsArray,
acrossHintsArray,
puzzleGrid
});
}
addLetterToPuzzleArray (puzzleGrid, wordObj, letter, index) {
const letterObj = {
word: wordObj.word,
wordNbr: wordObj.wordNbr,
positionInWord: index,
cellLetter: letter,
wordOrientation: wordObj.wordOrientation,
x: wordObj.wordOrientation === 'across' ? wordObj.startx + index : wordObj.startx,
y: wordObj.wordOrientation === 'across' ? wordObj.starty : wordObj.starty + index
};
puzzleGrid[letterObj.y][letterObj.x] = letterObj;
return puzzleGrid;
}
render () {
return (
<div className="home-page">
<PuzzleComponent />
<PuzzleComponent
puzzleGrid={this.state.puzzleGrid}
downHintsArray={this.state.downHintsArray}
acrossHintsArray={this.state.acrossHintsArray}
/>
<div className="data-flow">
<DataFlowArrow className="k8instances" />
</div>
@ -71,6 +142,22 @@ class HomePage extends React.Component {
}
}
HomePage.propTypes = {};
HomePage.propTypes = {
params: PropTypes.objectOf(PropTypes.string),
actions: PropTypes.objectOf(PropTypes.func),
state: PropTypes.object
};
export default HomePage;
function mapStateToProps (state) {
return {
state: state
};
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(homePageActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(HomePage);

Просмотреть файл

@ -1,274 +1,85 @@
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from '../../actions/puzzleActions';
import Cell from '../shared/Cell';
import Slider from '../shared/Slider';
import _ from 'lodash';
class PuzzleComponent extends React.Component {
constructor (props) {
super(props);
this.state = {
cells: [],
downHints: [],
acrossHints: [],
puzzleGrid: []
};
this.initializePuzzleArray = this.initializePuzzleArray.bind(this);
this.convertPuzzleGridToPuzzleArray = this.convertPuzzleGridToPuzzleArray.bind(this);
this.onCellInput = this.onCellInput.bind(this);
this.reloadPuzzle = this.reloadPuzzle.bind(this);
this.clearPuzzle = this.clearPuzzle.bind(this);
this.submitPuzzle = this.submitPuzzle.bind(this);
}
componentWillMount () {
this.props.actions.getPuzzleData();
}
componentWillReceiveProps (newProps) {
if (this.props.puzzleArray !== newProps.puzzleArray) {
this.initializePuzzleArray(newProps.puzzleArray);
}
}
initializeGrid () {
const puzzleGrid = [];
const maxRows = 12;
const maxColumns = 11;
for (var i = 0; i < maxColumns; i++) {
puzzleGrid.push(new Array(maxRows).fill(''));
}
return puzzleGrid;
}
initializePuzzleArray (puzzleArray) {
const downHintsArray = puzzleArray.filter((word) => {
return (word.wordOrientation === 'down');
});
const acrossHintsArray = puzzleArray.filter((word) => {
return (word.wordOrientation === 'across');
});
const downHints = this.buildHints(downHintsArray);
const acrossHints = this.buildHints(acrossHintsArray);
let puzzleGrid = [...this.initializeGrid()];
puzzleArray.forEach((wordObj, index) => {
const lettersArray = wordObj.word.split('');
const enteredValueArray = wordObj.enteredValue.split('');
lettersArray.forEach((letter, index) => {
puzzleGrid = this.addLetterToPuzzleArray(puzzleGrid, wordObj, letter, enteredValueArray[index], index);
});
});
const cells = this.buildCells(puzzleGrid);
this.setState({
downHints,
acrossHints,
puzzleGrid,
cells
});
}
addLetterToPuzzleArray (puzzleGrid, wordObj, letter, enteredValue, index) {
const letterObj = {
word: wordObj.word,
wordNbr: wordObj.wordNbr,
positionInWord: index,
cellLetter: letter,
currentValue: enteredValue,
wordOrientation: wordObj.wordOrientation,
x: wordObj.wordOrientation === 'across' ? wordObj.startx + index : wordObj.startx,
y: wordObj.wordOrientation === 'across' ? wordObj.starty : wordObj.starty + index
};
puzzleGrid[letterObj.y][letterObj.x] = letterObj;
return puzzleGrid;
}
buildCells (puzzleGrid) {
return puzzleGrid.map((column, index) => {
return column.map((cell, i) => {
return (
<Cell
key={index + i}
id={'cell'.concat('-', index, '-', i)}
orientation={cell.wordOrientation}
letter={cell.cellLetter}
value={cell.currentValue}
isEmpty={!_.isInteger(cell.positionInWord)}
positionInWord={cell.positionInWord}
wordNbr={cell.wordNbr}
onCellInput={this.onCellInput}
/>
);
});
});
}
onCellInput (e) {
const cellData = e.target.name.split('-');
const row = cellData[1];
const col = cellData[2];
const puzzleGrid = this.state.puzzleGrid.map((colData, rowIndex) => {
return colData.map((cell, colIndex) => {
if (rowIndex == row && colIndex == col) {
return Object.assign({}, cell, {
currentValue: e.target.value
});
} else {
return cell;
}
});
});
const cells = this.buildCells(puzzleGrid);
this.setState({
puzzleGrid,
cells
});
}
buildHints (hintsArray) {
return _.sortBy(hintsArray, 'wordNbr').map((word, index) => {
function PuzzleComponent (props) {
const cells = props.puzzleGrid.map((column, index) => {
return column.map((cell, i) => {
return (
<li key={index}>
<p className="bold inline">{word.wordNbr}.</p> <p className="inline">{word.hint}</p>
</li>
<Cell
key={index + i}
orientation={cell.wordOrientation}
letter={cell.cellLetter}
isEmpty={_.isInteger(cell.positionInWord)}
positionInWord={cell.positionInWord}
wordNbr={cell.wordNbr}
/>
);
});
}
convertPuzzleGridToPuzzleArray () {
const submission = this.props.puzzleArray.map((word) => {
const startx = word.startx;
const starty = word.starty;
const length = word.word.length;
const direction = word.wordOrientation;
const enteredLetters = this.state.puzzleGrid.map((colData, row) => {
return colData.map((cell, col) => {
if (startx === col && starty === row) {
return cell.currentValue || '*';
}
for (let i = 1; i < length; i++) {
if (direction === 'down' && startx === col && starty + i === row) {
return cell.currentValue || '*';
} else if (direction === 'across' && startx + i === col && starty === row) {
return cell.currentValue || '*';
}
}
});
});
const enteredValue = enteredLetters.reduce((arr, val) => {
return arr.concat(val);
}).join('');
return Object.assign({}, word, {
enteredValue
});
});
return submission;
}
reloadPuzzle () {
this.props.actions.getPuzzleData();
}
clearPuzzle () {
let submission = [...this.props.puzzleArray];
submission = submission.map(obj => {
const letters = obj.enteredValue.length;
obj.enteredValue = new Array(letters).fill('*').join('');
return obj;
});
this.props.actions.submitPuzzleData(this.props.puzzleId, submission);
}
submitPuzzle (e) {
e.preventDefault();
const submission = this.convertPuzzleGridToPuzzleArray();
this.props.actions.submitPuzzleData(this.props.puzzleId, submission);
}
render () {
const sliderProperties = {
min: 1,
max: 10,
step: 1,
defaultValue: 1,
onChange: () => {}
};
});
const downHints = _.sortBy(props.downHintsArray, 'wordNbr').map((word, index) => {
return (
<div className="crossword-container">
<div className="puzzle-container">
<form onSubmit={this.submitPuzzle}>
<div className="puzzle">
{this.state.cells}
</div>
<Slider properties={sliderProperties} title={'Concurrent Requests: '}/>
<div className="button-row">
<button className="secondary" type="button" onClick={this.reloadPuzzle}>Reload</button>
<button className="secondary" type="button" onClick={this.clearPuzzle}>Clear</button>
<input className="button primary" type="submit" />
</div>
</form>
<li key={index}>
<p className="bold inline">{word.wordNbr}.</p> <p className="inline">{word.hint}</p>
</li>
);
});
const acrossHints = _.sortBy(props.acrossHintsArray, 'wordNbr').map((word, index) => {
return (
<li key={index}>
<p className="bold inline">{word.wordNbr}.</p> <p className="inline">{word.hint}</p>
</li>
);
});
const sliderProperties = {
min: 1,
max: 10,
step: 1,
defaultValue: 1,
onChange: () => {}
};
return (
<div className="crossword-container">
<div className="puzzle-container">
<div className="puzzle">
{cells}
</div>
<div className="puzzle-hints">
<div className="hint-container">
<div className="hint-category down">
<h6 className="bold">Down</h6>
<ul>
{this.state.downHints}
</ul>
</div>
<div className="hint-category across">
<h6 className="bold">Across</h6>
<ul>
{this.state.acrossHints}
</ul>
</div>
<Slider properties={sliderProperties} title={'Concurrent Requests: '}/>
<div className="button-row">
<button className="secondary">Reload</button>
<button className="secondary">Clear</button>
<button className="primary">Submit</button>
</div>
</div>
<div className="puzzle-hints">
<div className="hint-container">
<div className="hint-category down">
<h6 className="bold">Down</h6>
<ul>
{downHints}
</ul>
</div>
<div className="hint-category across">
<h6 className="bold">Across</h6>
<ul>
{acrossHints}
</ul>
</div>
</div>
</div>
);
}
</div>
);
}
PuzzleComponent.propTypes = {
actions: PropTypes.objectOf(PropTypes.func),
state: PropTypes.object,
puzzleArray: PropTypes.array,
puzzleId: PropTypes.string
downHintsArray: PropTypes.array,
acrossHintsArray: PropTypes.array,
puzzleGrid: PropTypes.array.isRequired
};
function mapStateToProps (state) {
return {
puzzleId: state.puzzle.id,
puzzleArray: state.puzzle.puzzleData
};
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(PuzzleComponent);
export default PuzzleComponent;

Просмотреть файл

@ -1,42 +1,15 @@
import React, {PropTypes} from 'react';
import classNames from 'classnames';
import React from 'react';
function Cell (props) {
const cellClass = classNames({
cell: true,
isEmpty: props.isEmpty
});
const Cell = ({isEmpty, positionInWord, wordNbr, letter}) => {
const currentVal = props.value === '*' ? '' : props.value;
const inputClass = classNames({
incorrect: currentVal && currentVal.toLowerCase() !== props.letter.toLowerCase()
});
const showWordNumber = positionInWord === 0 ? true : false
return (
<div className={cellClass}>
<div className="superscript-number">{props.positionInWord === 0 ? props.wordNbr : ''}</div>
<input
name={props.id}
type="text"
maxLength="1"
size="1"
onChange={props.onCellInput}
className={inputClass}
value={currentVal}
disabled={props.isEmpty}
/>
<div className={'cell ' + isEmpty}>
<div className="superscript-number">{showWordNumber ? wordNbr : ''}</div>
<input type="text" maxLength="1" size="1" defaultValue={letter}/>
</div>
);
}
Cell.propTypes = {
id: PropTypes.string.isRequired,
isEmpty: PropTypes.bool.isRequired,
positionInWord: PropTypes.number,
wordNbr: PropTypes.number,
letter: PropTypes.string,
value: PropTypes.string,
onCellInput: PropTypes.func
};
export default Cell;

Просмотреть файл

@ -1 +0,0 @@
../node_modules

Просмотреть файл

@ -1,7 +0,0 @@
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -y socat
CMD ["socat"]

Просмотреть файл

@ -1,121 +0,0 @@
#!/usr/bin/env bash
kubectl delete namespace etcd
sleep 5
kubectl create namespace etcd
echo "starting cluster"
minikube start --memory 5000 --cpus 4 --kubernetes-version v1.6.0-rc.1
# echo "enabling addons"
# minikube addons enable heapster
minikube addons enable ingress
echo "installing etcd operator"
kubectl create -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/deployment.yaml
kubectl rollout status -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/deployment.yaml
until kubectl get thirdpartyresource cluster.etcd.coreos.com
do
echo "waiting for operator"
sleep 2
done
echo "installing registry"
kubectl apply -f k8s/registry.yml
kubectl rollout status deployment/registry
echo "pausing for 10 seconds for operator to settle"
sleep 10
kubectl create -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/example-etcd-cluster.yaml
echo "installing etcd cluster service"
kubectl create -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/example-etcd-cluster-nodeport-service.json
echo "waiting for etcd cluster to turnup"
until kubectl get pod example-etcd-cluster-0002
do
echo "waiting for etcd cluster to turnup"
sleep 2
done
sleep 5
echo "creating pod-list etcd directory"
kubectl exec -it example-etcd-cluster-0000 apk update
kubectl exec -it example-etcd-cluster-0000 apk add ca-certificates
kubectl exec -it example-etcd-cluster-0000 apk update-ca-certificates
kubectl exec -it example-etcd-cluster-0000 apk add bash wget
kubectl exec -it example-etcd-cluster-0000 wget https://gist.githubusercontent.com/moondev/86ebfc39998049d3f0c10848f4c72c57/raw/62cb237a115d4884cec4c5d94751cf5586f44b4b/mkdir.sh
kubectl exec -it example-etcd-cluster-0000 chmod +x mkdir.sh
kubectl exec -it example-etcd-cluster-0000 /mkdir.sh
echo "building kubescale image"
TAG=new1
docker build -t 127.0.0.1:30400/kubescale:$TAG .
# echo "building set image"
cd set; docker build -t 127.0.0.1:30400/set:$TAG -f set/Dockerfile .
echo "forwarding registry port"
export MINIKUBEIP=`minikube ip`
#temp container for forwarding to registry
docker run -d -e "MINIKUBEIP=$MINIKUBEIP" --name socat-registry -p 30400:5000 chadmoon/socat:latest bash -c "socat TCP4-LISTEN:5000,fork,reuseaddr TCP4:$MINIKUBEIP:30400"
sleep 5
echo "pushing kubescale image"
docker push 127.0.0.1:30400/kubescale:latest
docker push 127.0.0.1:30400/set:latest
echo "pushing set image"
docker push 127.0.0.1:30400/set:latest
sleep 2
echo "killing port-forward"
docker stop socat-registry
docker rm socat-registry
echo "deploying kubescale and set"
kubectl apply -f k8s/kubescale.yml
kubectl rollout status deployment/kubescale
kubectl apply -f k8s/set.yml
kubectl rollout status deployment/set
#temp container for forwarding to registry
docker stop socat-minikube
docker rm socat-minikube
proxy container for ingress
docker run -d -e "MINIKUBEIP=$MINIKUBEIP" --name socat-minikube -p 80:80 chadmoon/socat:latest bash -c "socat TCP4-LISTEN:80,fork,reuseaddr TCP4:$MINIKUBEIP:80"
# kubectl apply -f k8s/ing.yml
kubectl apply -f k8s/jenkins.yml
kubectl rollout status deployments/jenkins
sleep 10
open http://$MINIKUBEIP:31980 || true
xdg-open http://$MINIKUBEIP:31980 || true

28
package.json Normal file
Просмотреть файл

@ -0,0 +1,28 @@
{
"name": "kubernetes-ci-cd",
"version": "1.0.0",
"description": "## Get started Requirements * nodejs * kubectl * virtualbox * minikube * At least 4GB of available memory, 8GB for part 5",
"main": "start.js",
"dependencies": {
"cmdify": "^0.0.4",
"colors": "^1.1.2",
"inquirer": "^3.0.6",
"rx": "^4.1.0",
"yamljs": "^0.2.8"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node start.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/kenzanlabs/kubernetes-ci-cd.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/kenzanlabs/kubernetes-ci-cd/issues"
},
"homepage": "https://github.com/kenzanlabs/kubernetes-ci-cd#readme"
}

46
readme.js Executable file
Просмотреть файл

@ -0,0 +1,46 @@
#!/usr/bin/node
YAML = require('yamljs');
var fs = require('fs');
var markdown = "# Kubernetes ci/cd whitepaper for Linux.com\n\n This readme is dynamically generated when `node readme.js`";
markdown = markdown + "\n\n## Interactive tutorial version"
markdown = markdown + "\n* clone this repo\n"
markdown = markdown + "\n* Ensure you are starting with a clean slate: `minikube delete; minikube rm -rf ~/.minikube; rm -rf ~/.kube`\n"
markdown = markdown + "\n* run `npm install`\n"
markdown = markdown + "\nBegin the tutorial `npm start`"
markdown = markdown + "\n\n## Manual tutorial version\n\n"
YAML.load('steps.yml', function(docs)
{
docList = docs.parts;
var parts = docs.parts;
parts.forEach(function (item) {
markdown = markdown + "## " + item.name + "\n"
var part = item.name;
markdown = markdown + "\n\n### " + item.name + "\n\n"
var stepNum = 0;
var stepList = item.steps;
// console.log(item.steps);
stepList.forEach(function (step) {
stepNum++;
markdown = markdown + "\n\n### Step" + stepNum
markdown = markdown + "\n\n" + step.cap
markdown = markdown + "\n\n`" + step.com + "`"
})
})
fs.writeFile("README.md", markdown, function(err) {
if(err) {
return console.log(err);
}
console.log("README.md saved!");
});
});

27
scripts/etcd.sh Executable file
Просмотреть файл

@ -0,0 +1,27 @@
#!/usr/bin/env bash
echo "installing etcd operator"
kubectl create -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/deployment.yaml
kubectl rollout status -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/deployment.yaml
until kubectl get thirdpartyresource cluster.etcd.coreos.com
do
echo "waiting for operator"
sleep 2
done
echo "pausing for 10 seconds for operator to settle"
sleep 10
kubectl create -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/example-etcd-cluster.yaml
echo "installing etcd cluster service"
kubectl create -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/example-etcd-cluster-nodeport-service.json
echo "waiting for etcd cluster to turnup"
until kubectl get pod example-etcd-cluster-0002
do
echo "waiting for etcd cluster to turnup"
sleep 2
done

78
scripts/kubescale.sh Executable file
Просмотреть файл

@ -0,0 +1,78 @@
#!/bin/bash
# echo "creating pod-list etcd directory"
# kubectl exec -it example-etcd-cluster-0000 apk update
# kubectl exec -it example-etcd-cluster-0000 apk add ca-certificates
# kubectl exec -it example-etcd-cluster-0000 apk update-ca-certificates
# kubectl exec -it example-etcd-cluster-0000 apk add bash wget
# kubectl exec -it example-etcd-cluster-0000 wget https://gist.githubusercontent.com/moondev/86ebfc39998049d3f0c10848f4c72c57/raw/62cb237a115d4884cec4c5d94751cf5586f44b4b/mkdir.sh
# kubectl exec -it example-etcd-cluster-0000 chmod +x mkdir.sh
kubectl exec -it example-etcd-cluster-0000 echo "#!/usr/bin/env bash\n\nexport ETCDCTL_ENDPOINT='http://example-etcd-cluster-client-service:2379'\nectdctl mkdir pod-list" > /mkd.sh
kubectl exec -it example-etcd-cluster-0000 chmod 0777 /mkd.sh
kubectl exec -it example-etcd-cluster-0000 cat /mkd.sh
# echo "building kubescale image"
# TAG=latest
# docker build -t 127.0.0.1:30400/kubescale:$TAG -f
# # echo "building set image"
# cd set; docker build -t 127.0.0.1:30400/set:$TAG -f set/Dockerfile .
# echo "forwarding registry port"
# export MINIKUBEIP=`minikube ip`
# #temp container for forwarding to registry
# docker run -d -e "MINIKUBEIP=$MINIKUBEIP" --name socat-registry -p 30400:5000 chadmoon/socat:latest bash -c "socat TCP4-LISTEN:5000,fork,reuseaddr TCP4:$MINIKUBEIP:30400"
# sleep 5
# echo "pushing kubescale image"
# docker push 127.0.0.1:30400/kubescale:latest
# docker push 127.0.0.1:30400/set:latest
# echo "pushing set image"
# docker push 127.0.0.1:30400/set:latest
# sleep 2
# echo "killing port-forward"
# docker stop socat-registry
# docker rm socat-registry
# echo "deploying kubescale and set"
# kubectl apply -f k8s/kubescale.yml
# kubectl rollout status deployment/kubescale
# kubectl apply -f k8s/set.yml
# kubectl rollout status deployment/set
# #temp container for forwarding to registry
# docker stop socat-minikube
# docker rm socat-minikube
# proxy container for ingress
# docker run -d -e "MINIKUBEIP=$MINIKUBEIP" --name socat-minikube -p 80:80 chadmoon/socat:latest bash -c "socat TCP4-LISTEN:80,fork,reuseaddr TCP4:$MINIKUBEIP:80"
# # kubectl apply -f k8s/ing.yml
# kubectl apply -f k8s/jenkins.yml
# kubectl rollout status deployments/jenkins
# sleep 10
# open http://$MINIKUBEIP:31980 || true
# xdg-open http://$MINIKUBEIP:31980 || true

59
start.js Executable file
Просмотреть файл

@ -0,0 +1,59 @@
#!/usr/bin/node
YAML = require('yamljs');
var inquirer = require('inquirer');
var Rx = require('rx');
const execSync = require('child_process').execSync;
var environment = process.env;
var config = {
maxBuffer: 10000 * 1024,
env: environment
};
var prompts = new Rx.Subject();
var stepIndex = 1;
var commands = [];
inquirer.prompt(prompts).ui.process.subscribe(
function (answers) {
answerIndex = answers.name*1
answerIndex = answerIndex - 1;
cmd = commands[answerIndex];
execSync(cmd, {stdio:[0,1,2], env: environment})
},
function(err){
console.log('error')
},
function(message){
console.log('complete')
}
);
prompts.onNext({type: 'confirm',name: "Begin", message: "Welcome to the Linux.com interactive Kubernetes tutorial by Kenzan. Press enter to begin\n", default: true});
YAML.load('steps.yml', function(docs)
{
docList = docs.parts;
var parts = docs.parts;
parts.forEach(function (item) {
var part = item.name;
var stepNum = 0;
var stepList = item.steps;
stepList.forEach(function (step) {
stepNum++;
commands.push(step.com)
prompts.onNext({type: 'confirm',name: stepIndex, message: "\n \n \n" + part + " Step: " + stepNum + "\n" + step.cap + "\n \n" + step.com + "\n \nPress enter to the run the above command for the step.", default: true});
stepIndex++;
})
})
prompts.onCompleted();
});

112
steps.yml Normal file
Просмотреть файл

@ -0,0 +1,112 @@
parts:
- name: Part 1
intro: In this part we will setup a local cluster with minikube, deploy a public image from dockerhub, customize that image, and then finally deploy it inside our local cluster.
steps:
- cap: Start up the cluster with minikibe
com: minikube start --memory 6000 --cpus 2 --kubernetes-version v1.6.0
- cap: Enable addons
com: minikube addons enable heapster; minikube addons enable ingress
- cap: Wait 20 seconds and view minikube dashboard
com: sleep 20; minikube service kubernetes-dashboard --namespace kube-system
- cap: Deploy the public nginx image from DockerHub
com: kubectl run nginx --image nginx --port 80
- cap: Create a service for deployment
com: kubectl expose deployment nginx --type NodePort --port 80
- cap: Launch browser to test service
com: minikube service nginx
- cap: Install registry
com: kubectl apply -f manifests/registry.yml
- cap: Wait for registry to deploy
com: kubectl rollout status deployments/registry
- cap: View registry UI
com: minikube service registry-ui
- cap: Edit the contents of applications/hello-kenzan/index.html. This will open the file with the nano editor. When finished press ctrl + x to exit and confirm save.
com: nano applications/hello-kenzan/index.html
- cap: We will now build the image with a special name that is pointing at our cluster registry.
com: docker build -t 127.0.0.1:30400/hello-kenzan:latest -f applications/hello-kenzan/Dockerfile applications/hello-kenzan
- cap: Before we can push our image we need to set up a temporary proxy. This is a container that listens on 127.0.0.1:30400 and forwads to our cluster. By default the docker client can only push to non https via localhost.
com: docker stop socat-registry; docker rm socat-registry; docker run -d -e "REGIP=`minikube ip`" --name socat-registry -p 30400:5000 chadmoon/socat:latest bash -c "socat TCP4-LISTEN:5000,fork,reuseaddr TCP4:`minikube ip`:30400"
- cap: We can now push our image.
com: docker push 127.0.0.1:30400/hello-kenzan:latest
- cap: Stop the registry proxy.
com: docker stop socat-registry;
- cap: Now that our image is on the cluster we can deploy the manifests
com: kubectl apply -f applications/hello-kenzan/k8s/deployment.yaml
- cap: View the app
com: minikube service hello-kenzan
- name: Part 2
intro: In this part we will Setup Jenkins, and setup an automated jon to build, push and deploy our custom appliction.
steps:
- cap: Install Jenkins
com: kubectl apply -f manifests/jenkins.yml; kubectl rollout status deployment/jenkins
- cap: Get Jenkins admin password
com: kubectl exec -it `kubectl get pods --selector=app=jenkins --output=jsonpath={.items..metadata.name}` cat /root/.jenkins/secrets/initialAdminPassword
- cap: Enter the admin password from above and choose "suggested plugins". Create a new job with type pipeline. Scroll down and under "pipeline script" choose "Pipeline script from SCM". Under SCM choose GIT. Fork repo and put "repository url" as your fork, such as https://github.com/kenzanlabs/kubernetes-ci-cd.git. Save and run the job.
com: minikube service jenkins
- cap: View updated application
com: minikube service hello-kenzan
- cap: Push a change to your fork. Run job again. View changes
com: minikube service hello-kenzan
- name: Part 4
intro: Kubescale
steps:
- cap: Bootstrap etcd operator on the cluster
com: scripts/etcd.sh
- cap: Run job to create etcd directory
com: kubectl create -f manifests/etcd-job.yml
- cap: Check job status
com: kubectl describe jobs/etcd-job
- cap: build kubescale image
com: docker build -t 127.0.0.1:30400/kubescale:latest -f applications/kubescale/Dockerfile applications/kubescale
- cap: build scaling image
com: docker build -t 127.0.0.1:30400/set:latest -f applications/kubescale/set/Dockerfile applications/kubescale/set
- cap: Start the registry proxy.
com: docker stop socat-registry; docker rm socat-registry; docker run -d -e "REGIP=`minikube ip`" --name socat-registry -p 30400:5000 chadmoon/socat:latest bash -c "socat TCP4-LISTEN:5000,fork,reuseaddr TCP4:`minikube ip`:30400"
- cap: Push the kubescale image
com: docker push 127.0.0.1:30400/kubescale:latest
- cap: Push the scaling image
com: docker push 127.0.0.1:30400/set:latest
- cap: Stop the registry proxy
com: docker stop socat-registry
- cap: Deploy kubescale
com: kubectl apply -f applications/kubescale/k8s/kubescale.yml; kubectl rollout status deployment/kubescale
- cap: Deploy scaling set
com: kubectl apply -f applications/kubescale/k8s/set.yml; kubectl rollout status deployment/set
- cap: View kubescale application
com: minikube service kubescale