├── .gitignore ├── 0_quickstart.adoc ├── 0_setenv_minikube.sh ├── 0_setenv_minishift.sh ├── 10_funstuff.adoc ├── 11_crds.adoc ├── 1_installation_started.adoc ├── 2_building_running.adoc ├── 3_logs.adoc ├── 4_kubectl_exec.adoc ├── 5_configuration.adoc ├── 6_discovery.adoc ├── 7_live_ready.adoc ├── 8_deployment_techniques.adoc ├── 9_databases.adoc ├── 9_debugging.adoc ├── config ├── other.properties └── some.properties ├── hello ├── go │ ├── Dockerfile │ ├── myrest │ ├── myrest.go │ └── readme.txt ├── microprofile │ ├── pom.xml │ ├── readme.txt │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── burrsutter │ │ └── swarmdemo │ │ └── rest │ │ └── HelloWorldEndpoint.java ├── nodejs │ ├── .devcontainer │ │ ├── Dockerfile │ │ └── devcontainer.json │ ├── Dockerfile │ ├── hello-http.js │ ├── package-lock.json │ ├── package.json │ └── readme.txt ├── python │ ├── Dockerfile │ ├── app.py │ ├── readme.txt │ └── requirements.txt ├── quarkus │ ├── .dockerignore │ ├── buildNativeLinux.sh │ ├── buildNativeMac.sh │ ├── build_push_docker.sh │ ├── build_push_quay.sh │ ├── dockerbuild.sh │ ├── dockerbuild_openshift.sh │ ├── dockerpush_docker.sh │ ├── dockerpush_quay.sh │ ├── kubefiles │ │ ├── Deployment.yml │ │ ├── Deployment_quay.yml │ │ ├── Dockerfile │ │ ├── Dockerfile.openshift │ │ └── Service.yml │ ├── poller.sh │ ├── pom.xml │ ├── quarkus.log │ ├── readme.txt │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── redhat │ │ └── developer │ │ └── demo │ │ └── GreetingEndpoint.java ├── springboot │ ├── .devcontainer │ │ ├── Dockerfile │ │ └── devcontainer.json │ ├── Dockerfile │ ├── Dockerfile.openshift │ ├── Dockerfile_Java11 │ ├── Dockerfile_Memory │ ├── Dockerfile_Memory2 │ ├── build_push_docker.sh │ ├── build_push_quay.sh │ ├── pom.xml │ ├── readme.txt │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── burrsutter │ │ ├── HellobootApplication.java │ │ └── MyRESTController.java ├── springbootjib │ ├── pom.xml │ ├── readme.txt │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── burrsutter │ │ ├── HellobootApplication.java │ │ └── MyRESTController.java ├── springbootjshift │ ├── pom.xml │ ├── readme.adoc │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── burrsutter │ │ ├── HellobootApplication.java │ │ └── MyRESTController.java └── vertx │ ├── pom.xml │ ├── readme.txt │ └── src │ └── main │ └── java │ └── com │ └── burrsutter │ └── MainVerticle.java ├── hellodata └── boot_postgres │ ├── .devcontainer │ ├── Dockerfile │ └── devcontainer.json │ ├── .gitignore │ ├── Dockerfile │ ├── anotherquestion.json │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ ├── readme.adoc │ ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── boot_postgres │ │ │ │ ├── BootPostgresApplication.java │ │ │ │ ├── controller │ │ │ │ ├── AnswerController.java │ │ │ │ └── QuestionController.java │ │ │ │ ├── exception │ │ │ │ └── ResourceNotFoundException.java │ │ │ │ ├── model │ │ │ │ ├── Answer.java │ │ │ │ ├── AuditModel.java │ │ │ │ └── Question.java │ │ │ │ └── repository │ │ │ │ ├── AnswerRepository.java │ │ │ │ └── QuestionRepository.java │ │ └── resources │ │ │ ├── application-local-container.properties │ │ │ ├── application-local.properties │ │ │ └── application.properties │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── boot_postgres │ │ └── BootPostgresApplicationTests.java │ └── testquestion.json ├── images ├── chrome_rest_api.png ├── minikube_dashboard.png ├── openshift_dashboard.png ├── pgadmin_add_question.png ├── pgadmin_add_server.png ├── pgadmin_add_server2.png ├── pgadmin_query_questions.png ├── pgadmin_query_questions1.png ├── pgadmin_schema_creation.png ├── stern_output.png └── virtualbox_ui.png ├── kubefiles ├── kafka-strimzi-minikube.yml ├── my-service-8080.yml ├── my-service.yml ├── myboot-deployment-canary.yml ├── myboot-deployment-configuration-secret.yml ├── myboot-deployment-configuration.yml ├── myboot-deployment-liveready.yml ├── myboot-deployment-quay.yml ├── myboot-deployment-resources.yml ├── myboot-deployment.yml ├── myboot-service.yml ├── mybootdata-deployment.yml ├── mybootdata-service.yml ├── mynode-deployment-new.yml ├── mynode-deployment-quay.yml ├── mynode-deployment.yml ├── mynode-service.yml ├── myspace-namespace.yml ├── postgres-deployment.yml ├── postgres-pv.yml ├── postgres-pvc.yml ├── postgres-service.yml └── yourspace-namespace.yml ├── minikube_dashboard.sh ├── pizzas ├── cheese-pizza.yaml ├── clean.sh ├── deploy.sh ├── meat-lovers.yaml ├── pizza-controller.yaml ├── pizza-crd.yaml ├── setnamespace.sh ├── sync.py ├── test-pizza.json ├── test.sh ├── veggie-lovers.yaml └── webhook-py.yaml ├── poll_myboot.sh ├── poll_myboot_shift.sh ├── poll_mynode.sh ├── poll_mynode_shift.sh ├── readme.adoc ├── setcontext.sh ├── start_minikube.sh └── start_minishift.sh /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | 27 | ### Other ### 28 | target 29 | .DS_Store 30 | .mvn 31 | .vscode 32 | bin 33 | 34 | -------------------------------------------------------------------------------- /0_quickstart.adoc: -------------------------------------------------------------------------------- 1 | = Step 0: Quickstart 2 | Burr Sutter 3 | 4 | === Resources 5 | https://kubernetes.io/docs/user-guide/kubectl/ 6 | 7 | https://kubernetes.io/docs/reference/kubectl/cheatsheet 8 | 9 | 10 | === Create a Directory 11 | ---- 12 | mkdir ~/11steps 13 | cd ~/11steps 14 | ---- 15 | 16 | === git clone materials 17 | ---- 18 | git clone https://github.com/burrsutter/9stepsawesome 19 | ---- 20 | 21 | === Make a directory for executables 22 | ---- 23 | mkdir bin 24 | cd bin 25 | ---- 26 | 27 | === Download minikube & kubectl 28 | 29 | Some lines are Mac OS based, but if you are on Linux, change `darwin` to `linux`. 30 | 31 | ---- 32 | curl -Lo minikube https://storage.googleapis.com/minikube/releases/v1.7.2/minikube-darwin-amd64 33 | 34 | chmod +x minikube 35 | 36 | ./minikube version 37 | 38 | curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.15.0/bin/darwin/amd64/kubectl 39 | 40 | chmod +x kubectl 41 | 42 | cd .. 43 | ---- 44 | 45 | === Set Environment Variables 46 | ---- 47 | export MINIKUBE_HOME=/Users/burrsutter/11steps; 48 | export PATH=$MINIKUBE_HOME/bin:$PATH 49 | export KUBECONFIG=$MINIKUBE_HOME/.kube/config 50 | export KUBE_EDITOR="code -w" 51 | ---- 52 | 53 | === Startup Minikube 54 | ---- 55 | minikube --profile 11steps config set memory 6144 56 | minikube --profile 11steps config set cpus 2 57 | minikube --profile 11steps config set vm-driver virtualbox 58 | minikube --profile 11steps config set kubernetes-version v1.14.0 59 | minikube start --profile 11steps 60 | ---- 61 | 62 | === Run kubectl commands 63 | ---- 64 | kubectl cluster-info 65 | kubectl get nodes 66 | kubectl get nodes --show-labels 67 | kubectl get namespaces 68 | kubectl config view 69 | cat $KUBECONFIG 70 | kubectl config current-context 71 | minikube --profile 11steps config view 72 | kubectl get pods --all-namespaces 73 | kubectl get pods --all-namespaces --show-labels 74 | kubectl get pods --all-namespaces -o wide 75 | 76 | minikube --profile 11steps dashboard 77 | ---- 78 | 79 | TIP: "minikube profile 11steps" will set that as the default, make it sticky, therefore you will not have to type "minikube --profile 11steps" for subsequent commands 80 | 81 | === Deploy Something 82 | ---- 83 | kubectl create deployment myapp --image=quay.io/burrsutter/myboot:v1 84 | 85 | kubectl get events 86 | kubectl get events --sort-by=.metadata.creationTimestamp 87 | kubectl get all 88 | kubectl logs -l app=myapp 89 | 90 | kubectl expose deployment myapp --port=8080 --type=LoadBalancer 91 | 92 | kubectl get services 93 | ---- 94 | 95 | === Talk to App 96 | ---- 97 | minikube --profile 11steps ip 98 | 99 | minikube --profile 11steps service myapp --url 100 | 101 | curl http://192.168.99.100:31045 102 | 103 | minikube --profile 11steps service myapp 104 | 105 | curl $(minikube -p 11steps ip):$(kubectl get service/myapp -o jsonpath="{.spec.ports[*].nodePort}" -n default) 106 | 107 | watch kubectl get pods 108 | 109 | while true; do curl $(minikube -p 11steps ip):$(kubectl get service/myapp -o jsonpath="{.spec.ports[*].nodePort}" -n default); sleep .3; done 110 | 111 | kubectl scale deployment myapp --replicas=3 112 | ---- 113 | 114 | === Update the App 115 | ---- 116 | kubectl set image deployment/myapp myboot=quay.io/burrsutter/myboot:v2 117 | ---- 118 | 119 | === Update the App Again 120 | ---- 121 | kubectl set image deployment/myapp myboot=quay.io/burrsutter/quarkus-demo:1.0.0 122 | ---- 123 | 124 | === Clean Up 125 | ---- 126 | kubectl delete service myapp 127 | 128 | kubectl delete deployment myapp 129 | 130 | minikube --profile 11steps stop 131 | 132 | ls -lh ~/11steps/.minikube/machines/11steps 133 | 134 | minikube --profile 11steps delete 135 | ---- 136 | -------------------------------------------------------------------------------- /0_setenv_minikube.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo 'source 0_setenv.sh' 4 | 5 | export MINIKUBE_HOME=/Users/burrsutter/minikube; 6 | export PATH=$MINIKUBE_HOME/bin:$PATH 7 | # export KUBECONFIG=$MINIKUBE_HOME/.kube/config:$MINIKUBE_HOME/.kube/kubconfig2 8 | export KUBECONFIG=$MINIKUBE_HOME/9steps/.kube/config 9 | export KUBE_EDITOR="code -w" 10 | 11 | echo 'the following after minikube as started' 12 | echo 'eval $(minikube --profile 9steps docker-env)' 13 | -------------------------------------------------------------------------------- /0_setenv_minishift.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo 'source 0_setenv.sh' 4 | 5 | export MINISHIFT_HOME=/Users/burrsutter/minishift_1.33.0; 6 | export PATH=$MINISHIFT_HOME/bin:$PATH 7 | # export KUBECONFIG=$MINISHIFT_HOME/.kube/config:$MINISHIFT_HOME/.kube/kubconfig2 8 | export KUBECONFIG=$MINISHIFT_HOME/.kube/config 9 | export KUBE_EDITOR="code -w" 10 | 11 | echo 'the following after minikube as started' 12 | echo 'eval $(minishift --profile 9steps docker-env)' 13 | -------------------------------------------------------------------------------- /10_funstuff.adoc: -------------------------------------------------------------------------------- 1 | = Fun Stuff 2 | Burr Sutter 3 | 4 | === Create a Namespace 5 | ---- 6 | kubectl create namespace funstuff 7 | kubectl config set-context --current --namespace=funstuff 8 | ---- 9 | 10 | === Build Images (Optional) 11 | 12 | ---- 13 | cd hello/python 14 | docker build -t 9stepsawesome/mypython . 15 | 16 | cd ../go 17 | docker build -t 9stepsawesome/mygo . 18 | 19 | cd ../nodejs 20 | docker build -t 9stepsawesome/mynode . 21 | ---- 22 | 23 | === Publish Images (Optional) 24 | ---- 25 | docker login quay.io # docker.io 26 | 27 | docker tag quay.io/burrsutter/mypython:1.0.0 28 | docker push quay.io/burrsutter/mypython:1.0.0 29 | 30 | docker tag quay.io/burrsutter/mygo:1.0.0 31 | docker push quay.io/burrsutter/mygo:1.0.0 32 | 33 | docker tag quay.io/burrsutter/mynode:1.0.0 34 | docker push quay.io/burrsutter/mynode:1.0.0 35 | 36 | ---- 37 | 38 | === Deploy Images 39 | 40 | Using kubectl run to turn the local images into pods 41 | 42 | ---- 43 | kubectl run mypython --image=9stepsawesome/mypython:latest --port=8000 --image-pull-policy=Never --generator=run-pod/v1 44 | 45 | kubectl run mygo --image=9stepsawesome/mygo:latest --port=8000 --image-pull-policy=Never --generator=run-pod/v1 46 | 47 | kubectl run mynode --image=9stepsawesome/mynode:latest --port=8000 --image-pull-policy=Never --generator=run-pod/v1 48 | 49 | ---- 50 | 51 | OR 52 | 53 | using a Deployment manifest to deploy the remotely published images 54 | 55 | === Deploy mypython 56 | ---- 57 | cat < 196 | Selector: inservice=mypods 197 | Type: LoadBalancer 198 | IP: 10.104.58.121 199 | Port: http 8000/TCP 200 | TargetPort: 8000/TCP 201 | NodePort: http 31996/TCP 202 | Endpoints: 172.17.0.24:8000,172.17.0.25:8000,172.17.0.26:8000 203 | Session Affinity: None 204 | External Traffic Policy: Cluster 205 | Events: 206 | ---- 207 | 208 | The Endpoints now reflect the IP addresses of the 3 pods with the inservice:mypods label 209 | 210 | === See the Pod IPs 211 | ---- 212 | kubectl get pods -o wide 213 | NAME READY STATUS RESTARTS AGE IP 214 | mygo-deployment-77d5ccbbdd-rs2ks 1/1 Running 0 27m 172.17.0.25 215 | mynode-deployment-bdcb5855d-c9p7t 1/1 Running 0 28m 172.17.0.24 216 | mypython-deployment-7465d9f67b-lfq2v 1/1 Running 0 36m 172.17.0.26 217 | ---- 218 | 219 | === See the Endpoints 220 | ---- 221 | kubectl get endpoints 222 | NAME ENDPOINTS AGE 223 | my-service 172.17.0.24:8000,172.17.0.25:8000,172.17.0.26:8000 18m 224 | ---- 225 | 226 | === Remove a Pod from the Service 227 | by removing the "inservice" label 228 | ---- 229 | kubectl label pod -l app=mypython inservice- 230 | 231 | kubectl get pods --show-labels 232 | NAME READY STATUS RESTARTS AGE LABELS 233 | mygo-deployment-77d5ccbbdd-rs2ks 1/1 Running 0 35m app=mygo,inservice=mypods,pod-template-hash=77d5ccbbdd 234 | mynode-deployment-bdcb5855d-c9p7t 1/1 Running 0 36m app=mynode,inservice=mypods,pod-template-hash=bdcb5855d 235 | mypython-deployment-7465d9f67b-lfq2v 1/1 Running 0 44m app=mypython,pod-template-hash=7465d9f67b 236 | 237 | kubectl get endpoints 238 | NAME ENDPOINTS AGE 239 | my-service 172.17.0.24:8000,172.17.0.25:8000 24m 240 | ---- 241 | 242 | === Add it back 243 | ---- 244 | kubectl label pod -l app=mypython inservice=mypods 245 | ---- 246 | 247 | === Clean up 248 | ---- 249 | kubectl delete namespace funstuff 250 | ---- -------------------------------------------------------------------------------- /11_crds.adoc: -------------------------------------------------------------------------------- 1 | = CRDs & Controllers 2 | 3 | Custom Resources extend the API 4 | 5 | Custom Controllers provide the functionality - continually maintains the desired state - to monitor its state and reconcile the resource to match with the configuration 6 | 7 | https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/ 8 | 9 | https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/ 10 | 11 | Custom Resource Definitions (CRDs) in version 1.7 12 | 13 | === CRDs 14 | ---- 15 | $ kubectl get crds 16 | $ kubectl api-resources 17 | ---- 18 | 19 | === Example CRD 20 | ---- 21 | apiVersion: apiextensions.k8s.io/v1beta1 22 | kind: CustomResourceDefinition 23 | metadata: 24 | name: pizzas.mykubernetes.burrsutter.com 25 | labels: 26 | app: pizzamaker 27 | mylabel: stuff 28 | annotations: 29 | "yourannotation": morestuff 30 | spec: 31 | group: mykubernetes.burrsutter.com 32 | scope: Namespaced 33 | version: v1beta2 34 | names: 35 | kind: Pizza 36 | listKind: PizzaList 37 | plural: pizzas 38 | singular: pizza 39 | shortNames: 40 | - pz 41 | ---- 42 | 43 | === Add Pizzas 44 | ---- 45 | $ kubectl apply -f pizza-crd.yaml 46 | 47 | ---- 48 | 49 | === Now part of the API 50 | ---- 51 | $ kubectl get crds | grep pizza 52 | pizzas.mykubernetes.burrsutter.com 2019-09-04T19:40:09Z 53 | $ kubectl api-resources | grep burr 54 | ---- 55 | 56 | 57 | === Make some Pizzas 58 | ---- 59 | $ kubectl create namespace pizzahat 60 | 61 | $ kubectl config set-context --current --namespace=pizzahat 62 | 63 | $ kubectl apply -f cheese-pizza.yaml 64 | 65 | $ kubectl get pizzas 66 | 67 | $ kubectl describe pizza burrcheese 68 | ---- 69 | 70 | === Make more Pizzas 71 | ---- 72 | 73 | $ kubectl apply -f meat-lovers.yaml 74 | 75 | $ kubectl apply -f veggie-lovers.yaml 76 | 77 | $ kubectl get pizzas --all-namespaces 78 | ---- 79 | 80 | === Eat all Pizzas 81 | ---- 82 | $ kubectl delete pizzas --all 83 | ---- 84 | 85 | https://github.com/strimzi/strimzi-kafka-operator/blob/master/install/cluster-operator/040-Crd-kafka.yaml[Example CRD] 86 | 87 | 88 | == Controllers 89 | 90 | 91 | Build a controller via Metacontroller: 92 | https://metacontroller.app/guide/create/ 93 | 94 | https://github.com/GoogleCloudPlatform/metacontroller 95 | 96 | Note: https://github.com/GoogleCloudPlatform/metacontroller/issues/184[Future?] 97 | 98 | === Setup Metacontroller 99 | 100 | ---- 101 | $ kubectl create namespace metacontroller 102 | 103 | $ kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/metacontroller/master/manifests/metacontroller-rbac.yaml 104 | 105 | $ kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/metacontroller/master/manifests/metacontroller.yaml 106 | 107 | $ kubectl get pods --all-namespaces 108 | 109 | $ stern metacontroller-0 -n metacontroller 110 | 111 | $ kubectl apply -f pizza-controller.yaml 112 | 113 | metacontroller-0 metacontroller I0904 22:23:35.324855 1 metacontroller.go:148] sync CompositeController pizza-controller 114 | metacontroller-0 metacontroller I0904 22:23:35.324899 1 factory.go:104] Starting shared informer for pizzas in mykubernetes.burrsutter.com/v1beta2 115 | metacontroller-0 metacontroller I0904 22:23:35.324940 1 factory.go:104] Starting shared informer for pods in v1 116 | metacontroller-0 metacontroller I0904 22:23:35.325019 1 controller.go:165] Starting Pizza CompositeController 117 | metacontroller-0 metacontroller I0904 22:23:35.325029 1 controller.go:169] Waiting for Pizza CompositeController caches to sync 118 | metacontroller-0 metacontroller I0904 22:23:35.325039 1 controller.go:32] Waiting for caches to sync for Pizza controller 119 | metacontroller-0 metacontroller I0904 22:23:35.325453 1 reflector.go:202] Starting reflector *unstructured.Unstructured (30m0s) from metacontroller.app/dynamic/informer/factory.go:111 120 | metacontroller-0 metacontroller I0904 22:23:35.325476 1 reflector.go:240] Listing and watching *unstructured.Unstructured from metacontroller.app/dynamic/informer/factory.go:111 121 | metacontroller-0 metacontroller I0904 22:23:35.325947 1 reflector.go:202] Starting reflector *unstructured.Unstructured (30m0s) from metacontroller.app/dynamic/informer/factory.go:111 122 | metacontroller-0 metacontroller I0904 22:23:35.325963 1 reflector.go:240] Listing and watching *unstructured.Unstructured from metacontroller.app/dynamic/informer/factory.go:111 123 | metacontroller-0 metacontroller I0904 22:23:35.525234 1 shared_informer.go:123] caches populated 124 | metacontroller-0 metacontroller I0904 22:23:35.525259 1 controller.go:39] Caches are synced for Pizza controller 125 | 126 | ---- 127 | 128 | === Add Pizza Controller 129 | ---- 130 | 131 | kubectl -n pizzahat create configmap pizza-controller --from-file=sync.py 132 | 133 | kubectl -n pizzahat apply -f webhook-py.yaml 134 | ---- 135 | 136 | === Deploy some Pizzas 137 | 138 | ---- 139 | $ stern metacontroller-0 -n metacontroller 140 | 141 | $ stern pizza-controller 142 | 143 | $ watch kubectl get pods 144 | 145 | $ kubectl -n pizzahat apply -f veggie-lovers.yaml 146 | 147 | $ kubectl logs burrveggie 148 | ---- 149 | 150 | === Eat the Pizza 151 | ---- 152 | $ kubectl delete pizza burrveggie 153 | $ kubectl delete namespace pizzahat 154 | ---- 155 | 156 | === Kafka via OperatorHub 157 | 158 | ---- 159 | $ kubectl create namespace franz 160 | 161 | $ kubectl config set-context --current --namespace=franz 162 | 163 | $ curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/0.12.0/install.sh | bash -s 0.12.0 164 | 165 | $ kubectl create -f https://operatorhub.io/install/strimzi-kafka-operator.yaml 166 | 167 | $ kubectl get csv -n operators 168 | 169 | $ kubectl get crds | grep kafka 170 | 171 | $ watch kubectl get pods 172 | 173 | $ kubectl apply -f https://raw.githubusercontent.com/burrsutter/9stepsawesome/master/kubefiles/kafka-strimzi-minikube.yml 174 | 175 | $ kubectl get kafkas 176 | ---- 177 | 178 | === Clean up 179 | ---- 180 | $ kubectl delete kafka burr-cluster 181 | $ kubectl delete namespace franz 182 | ---- -------------------------------------------------------------------------------- /2_building_running.adoc: -------------------------------------------------------------------------------- 1 | = Step 2: Building Images & Running Containers 2 | Burr Sutter 3 | 4 | ifndef::codedir[:codedir: kubefiles] 5 | ifndef::imagesdir[:imagesdir: images] 6 | 7 | == Namespaces 8 | The resources (your apps) you create can be organized into namespaces which are particularly helpful when dealing with a larger team. 9 | 10 | You can create a namespace via simple command 11 | ---- 12 | $ kubectl create namespace demo 13 | $ kubectl get namespaces 14 | NAME STATUS AGE 15 | default Active 22h 16 | demo Active 6m 17 | kube-public Active 21h 18 | kube-system Active 22h 19 | ---- 20 | 21 | The -o yaml trick is a great way to see what the command created 22 | 23 | ---- 24 | $ kubectl get namespace demo -o yaml 25 | apiVersion: v1 26 | kind: Namespace 27 | metadata: 28 | creationTimestamp: 2018-07-29T22:27:27Z 29 | name: demo 30 | resourceVersion: "21330" 31 | selfLink: /api/v1/namespaces/demo 32 | uid: 92a63dee-937e-11e8-9a0f-080027cd86aa 33 | spec: 34 | finalizers: 35 | - kubernetes 36 | status: 37 | phase: Active 38 | ---- 39 | 40 | You can also use the 41 | ---- 42 | $ kubectl get namespace/demo --export -o yaml 43 | ---- 44 | for something cleaner, something you might store and use later with kubectl apply -f 45 | 46 | 47 | Deleting a Namespace is also available via command. Deleting the namespace will remove all other objects that were created within this namespace. 48 | 49 | ---- 50 | $ kubectl delete namespace/demo 51 | ---- 52 | 53 | Your objects should be created via a file that can be checked into source control. Create a Namespace via yaml. 54 | 55 | ---- 56 | $ cd 9stepsawesome 57 | $ kubectl create -f kubefiles/myspace-namespace.yml # <1> 58 | $ kubectl config set-context --current --namespace=myspace # <2> 59 | # or if you are using minishift and oc, there is a shortcut 60 | $ oc project myspace 61 | # or if you installed kubectx, try 62 | # kubens myspace 63 | ---- 64 | <1> Using the yaml allows you to place this file in a source repository 65 | <2> The setting of the context means you have to type -n myspace less often. Also, when using Minishift, 66 | "oc project myspace" is the equivalent command. In OpenShift projects = namespaces. 67 | 68 | [source,yaml,subs="normal,attributes"] 69 | ---- 70 | include::kubefiles/myspace-namespace.yml[] 71 | ---- 72 | 73 | You can confirm your context with 74 | ---- 75 | $ kubectl config view --minify --template '{{ index .contexts 0 "context" "namespace" }}' 76 | # or if you are using minishift and oc then there is a handy shortcut 77 | $ oc project 78 | # or you can use kubens tool which comes with kubectx (brew install kubectx) 79 | $ kubens 80 | ---- 81 | 82 | == Pods 83 | 84 | https://kubernetes.io/docs/concepts/workloads/pods/pod-overview/[Pod] 85 | 86 | Create a standalone Pod 87 | 88 | ---- 89 | cat < 294 | $ curl $(minikube -p 9steps ip):8080 <2> 295 | # If working, you can stop the container with 296 | $ docker stop myboot 297 | ---- 298 | <1> You can check the status of the container from another window using the command: docker logs -f myboot 299 | <2> Since the docker container is running "inside" the minikube/minishift VM, you need to use its IP address 300 | 301 | 302 | == Deployment 303 | 304 | Next, deploy it as a pod inside of Kubernetes/OpenShift. You could use the "kubectl run" command but here we will use a Deployment 305 | ---- 306 | $ kubectl create -f kubefiles/myboot-deployment.yml 307 | ---- 308 | 309 | And then see what happened 310 | ---- 311 | $ kubectl get all 312 | NAME READY STATUS RESTARTS AGE 313 | pod/myboot-79bfc988c8-ttz5r 1/1 Running 0 25s 314 | 315 | NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE 316 | deployment.apps/myboot 1 1 1 1 25s 317 | 318 | NAME DESIRED CURRENT READY AGE 319 | replicaset.apps/myboot-79bfc988c8 1 1 1 25s 320 | ---- 321 | 322 | == Service 323 | 324 | Create a Service 325 | ---- 326 | $ kubectl create -f kubefiles/myboot-service.yml 327 | ---- 328 | 329 | Get the nodePort 330 | ---- 331 | $ kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}" 332 | ---- 333 | 334 | Curl that url + nodePort 335 | ---- 336 | $ curl $(minikube -p 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}") 337 | ---- 338 | 339 | Perhaps build a little loop to curl that endpoint 340 | [source, bash] 341 | ---- 342 | while true 343 | do 344 | curl $(minikube -p 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}") 345 | sleep .5; 346 | done 347 | ---- 348 | 349 | == Scale 350 | 351 | Let's scale up the application to 3 replicas, there are several possible ways to achieve this result. 352 | 353 | You can edit the myboot-deployment.yml, updating replicas to 3 354 | ---- 355 | $ kubectl replace -f kubefiles/myboot-deployment.yml 356 | ---- 357 | 358 | Or use the kubectl scale command 359 | ---- 360 | $ kubectl scale --replicas=3 deployment/myboot 361 | ---- 362 | 363 | Or you might patch it 364 | ---- 365 | $ kubectl patch deployment/myboot -p '{"spec":{"replicas":3}}' 366 | ---- 367 | 368 | Or use the kubectl edit command which essentially gives you "vi" for editing the deployment yaml 369 | ---- 370 | $ kubectl edit deployment/myboot 371 | /replicas 372 | esc-w-q 373 | ---- 374 | 375 | Note: You can use VS Code as your editor with export KUBE_EDITOR="code -w" 376 | 377 | 378 | You can then use "kubectl get pods" to see the pods that have been created 379 | ---- 380 | $ kubectl get pods 381 | ---- 382 | 383 | Note: 3 pods might push you out of your memory limits for your VM. Check your memory usage with: 384 | ---- 385 | $ minishift -p 9steps ssh 386 | # or 387 | $ minikube ssh 388 | $ free -m 389 | $ top -o %MEM 390 | ---- 391 | 392 | == Update 393 | 394 | Update MyRESTController.java 395 | ---- 396 | greeting = environment.getProperty("GREETING","Bonjour"); 397 | ---- 398 | 399 | Compile & Build the fat jar 400 | ---- 401 | mvn clean package 402 | ---- 403 | 404 | You can test with "java -jar target/boot-demo-0.0.1.jar" and "curl localhost:8080". Ideally, you would have unit tests executed with "mvn test" as well. 405 | 406 | Build the new docker image with a v2 tag 407 | ---- 408 | $ docker build -t 9stepsawesome/myboot:v2 . 409 | $ docker images | grep myboot 410 | ---- 411 | 412 | Rollout the update 413 | ---- 414 | $ kubectl set image deployment/myboot myboot=9stepsawesome/myboot:v2 415 | OR 416 | $ kubectl set image deployment/myboot myboot=quay.io/burrsutter/myboot:v2 417 | ---- 418 | 419 | And if you were running your polling curl command, you might see 420 | ---- 421 | Aloha from Spring Boot! 346 on myboot-79bfc988c8-ttz5r 422 | Aloha from Spring Boot! 270 on myboot-79bfc988c8-ntb8d 423 | Aloha from Spring Boot! 348 on myboot-79bfc988c8-ttz5r 424 | curl: (7) Failed to connect to 192.168.99.105 port 31218: Connection refused 425 | curl: (7) Failed to connect to 192.168.99.105 port 31218: Connection refused 426 | curl: (7) Failed to connect to 192.168.99.105 port 31218: Connection refused 427 | curl: (7) Failed to connect to 192.168.99.105 port 31218: Connection refused 428 | curl: (7) Failed to connect to 192.168.99.105 port 31218: Connection refused 429 | curl: (7) Failed to connect to 192.168.99.105 port 31218: Connection refused 430 | curl: (7) Failed to connect to 192.168.99.105 port 31218: Connection refused 431 | Bonjour from Spring Boot! 0 on myboot-757658fc4c-qnvqx 432 | Bonjour from Spring Boot! 1 on myboot-5955897c9b-zthj9 433 | ---- 434 | 435 | We will be working on those errors when we address Liveness and Readiness probes in Step 7 436 | 437 | For now, undo the update, going back to v1 438 | 439 | ---- 440 | $ kubectl rollout undo deployment/myboot 441 | ---- 442 | 443 | Scale to 1 444 | ---- 445 | $ kubectl scale --replicas=1 deployment/myboot 446 | ---- 447 | 448 | Hit the sysresources endpoint 449 | ---- 450 | $ curl $(minikube -p 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}")/sysresources 451 | Memory: 1324 Cores: 2 452 | ---- 453 | 454 | Note: you can use echo to see what this URL really looks like on your machine 455 | ---- 456 | $ echo $(minikube -p 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}")/sysresources 457 | 192.168.99.105:30479/sysresources 458 | ---- 459 | 460 | The results of the "sysresources" call should be about 1/4 memory and all the cores that were configured for the VM. You can double check this with the following command: 461 | 462 | ---- 463 | $ minikube config view 464 | - cpus: 2 465 | - memory: 6144 466 | - vm-driver: virtualbox 467 | ---- 468 | 469 | Now, let's apply resource contraints via Kubernetes & deployment yaml. Look at myboot-deployment-resources.yml 470 | 471 | ---- 472 | $ kubectl replace -f kubefiles/myboot-deployment-resources.yml 473 | ---- 474 | 475 | Curl sysresources again 476 | ---- 477 | $ curl $(minikube -p 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}")/sysresources 478 | Memory: 1324 Cores: 2 479 | ---- 480 | 481 | In another terminal window, watch the pods 482 | ---- 483 | $ kubectl get pods -w 484 | ---- 485 | 486 | and now curl the consume endpoint 487 | ---- 488 | $ curl $(minikube -p 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}")/consume 489 | ---- 490 | 491 | and you should see a OOMKilled 492 | ---- 493 | $ kubectl get pods -w 494 | NAME READY STATUS RESTARTS AGE 495 | myboot-68d666dd8d-m9m5r 1/1 Running 0 1m 496 | myboot-68d666dd8d-m9m5r 0/1 OOMKilled 0 2m 497 | myboot-68d666dd8d-m9m5r 1/1 Running 1 2m 498 | ---- 499 | 500 | And you can also see the OOMKilled with the kubectl describe command 501 | ---- 502 | $ kubectl describe pod -l app=myboot 503 | ... 504 | Last State: Terminated 505 | Reason: OOMKilled 506 | Exit Code: 137 507 | Started: Tue, 31 Jul 2018 13:42:18 -0400 508 | Finished: Tue, 31 Jul 2018 13:44:24 -0400 509 | Ready: True 510 | Restart Count: 1 511 | ... 512 | ---- 513 | 514 | Mystery to be solved in future steps! 515 | 516 | -------------------------------------------------------------------------------- /3_logs.adoc: -------------------------------------------------------------------------------- 1 | = Step 3: Logs 2 | Burr Sutter 3 | 4 | ifndef::imagesdir[:imagesdir: images] 5 | 6 | There are various "production-ready" ways to do log gathering and viewing across a Kubernetes/OpenShift cluster. Many folks like some flavor of ELK or EFK. 7 | 8 | The focus here is on stuff a developer needs to get access to do help understand the behavior of their application running inside of a pod. 9 | 10 | Make sure you are running 3 replicas (3 pods/instances of your application) 11 | ---- 12 | $ kubectl scale --replicas=3 deployment/myboot 13 | ---- 14 | 15 | and you can see them with kubectl get pods 16 | 17 | ---- 18 | $ kubectl get pods 19 | NAME READY STATUS RESTARTS AGE 20 | myboot-68d666dd8d-cwtls 1/1 Running 0 3m 21 | myboot-68d666dd8d-n874b 1/1 Running 0 3m 22 | myboot-68d666dd8d-r8zpn 1/1 Running 0 11m 23 | ---- 24 | 25 | There is kubectl logs which is useful 26 | 27 | ---- 28 | $ kubectl logs myboot-68d666dd8d-r8zpn -f 29 | ---- 30 | 31 | or you can try stern, easily installable via homebrew on Mac 32 | https://github.com/wercker/stern 33 | 34 | ---- 35 | $ brew install stern 36 | ---- 37 | 38 | and use it like so 39 | 40 | ---- 41 | $ stern myboot 42 | ---- 43 | 44 | and in another terminal window, poll the various instances of the application 45 | 46 | ---- 47 | while true 48 | do 49 | curl $(minikube ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}") 50 | sleep .5; 51 | done 52 | ---- 53 | 54 | and stern will let you see the logs across the 3 different pods. 55 | 56 | .stern output 57 | image::stern_output.png[Stern] 58 | 59 | This is more exciting when you might have 3 different microservices where the names are similiar enough to use stern. 60 | 61 | stern also offers a --label selector 62 | 63 | and if you are running Istio make sure to add the -c to specify your business logic container, instead of the sidecar...unless you wish to view the sidecar's logs :-) 64 | 65 | Another option is kail 66 | ---- 67 | $ brew tap boz/repo 68 | $ brew install boz/repo/kail 69 | $ kail 70 | ---- 71 | 72 | This will output the logs across your namespace, in our case, the 3 replicas of myboot are all in the same namespace 73 | 74 | You can use use kail and isolate a specific deployment, specific container (a pod can have more than one container) and look back in time 75 | 76 | ---- 77 | $ kail --deploy=myboot -c myboot --since=1h 78 | ---- 79 | -------------------------------------------------------------------------------- /4_kubectl_exec.adoc: -------------------------------------------------------------------------------- 1 | = Step 4: exec Magic 2 | Burr Sutter 3 | 4 | ifndef::codedir[:codedir: kubefiles] 5 | ifndef::imagesdir[:imagesdir: images] 6 | 7 | == Linux FTW 8 | Those pods are running little Linux machines (and JVMs or whatever you might have loaded in) 9 | 10 | ---- 11 | $ kubectl get pods --show-labels 12 | NAME READY STATUS RESTARTS AGE LABELS 13 | myboot-68d666dd8d-m9m5r 1/1 Running 1 43m app=myboot,pod-template-hash=2482228848 14 | ---- 15 | 16 | or 17 | 18 | ---- 19 | $ kubectl get pods -l app=myboot 20 | kubectl get pods -l app=myboot 21 | NAME READY STATUS RESTARTS AGE 22 | myboot-68d666dd8d-m9m5r 1/1 Running 1 44m 23 | ---- 24 | 25 | You should still be running the resource contrained deployment, which you can double-check with kubectl describe 26 | 27 | ---- 28 | $ kubectl describe deployment/myboot 29 | ... 30 | Limits: 31 | cpu: 1 32 | memory: 400Mi 33 | Requests: 34 | cpu: 250m 35 | memory: 300Mi 36 | ... 37 | ---- 38 | 39 | You can hit its "consume" endpoint some more, watching it OOMKilled and restart 40 | 41 | ---- 42 | $ curl $(minikube --profile 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}")/consume 43 | ---- 44 | 45 | and 46 | 47 | ---- 48 | $ kubectl get pods -w 49 | NAME READY STATUS RESTARTS AGE 50 | myboot-68d666dd8d-m9m5r 1/1 Running 1 1h 51 | myboot-68d666dd8d-m9m5r 0/1 OOMKilled 1 1h 52 | myboot-68d666dd8d-m9m5r 1/1 Running 2 1h 53 | ---- 54 | 55 | But now let's try to figure out WHY this Java-based thing is getting killed. 56 | 57 | You can "exec" into the running pod. 58 | 59 | First you need the pod identifier and while you could just copy and paste it, it is more fun to play with jsonpath and bash scripting 60 | 61 | ---- 62 | $ PODID=$(kubectl get pods -l app=myboot -o 'jsonpath={.items[0].metadata.name}') 63 | #OR 64 | # PODID=$(kubectl get pods -l app=myboot -o name) 65 | $ echo $PODID 66 | ---- 67 | 68 | and then use that to exec into the pod 69 | 70 | ---- 71 | $ kubectl exec -i -t $PODID /bin/bash 72 | root@myboot-68d666dd8d-m9m5r:/app# echo 'now inside the pod' 73 | now inside the pod 74 | ---- 75 | 76 | Try some fun commands like 77 | ---- 78 | $ ps -ef 79 | UID PID PPID C STIME TTY TIME CMD 80 | root 1 0 0 18:48 ? 00:00:00 /bin/sh -c java -XX:+PrintFlagsF 81 | root 5 1 0 18:48 ? 00:00:07 java -XX:+PrintFlagsFinal -XX:+P 82 | root 38 0 0 19:05 pts/0 00:00:00 /bin/bash 83 | root 42 38 0 19:05 pts/0 00:00:00 ps -ef 84 | ---- 85 | 86 | or 87 | 88 | ---- 89 | $ top 90 | top - 19:06:41 up 6:40, 0 users, load average: 0.28, 0.20, 0.21 91 | Tasks: 4 total, 1 running, 3 sleeping, 0 stopped, 0 zombie 92 | %Cpu(s): 1.9 us, 1.4 sy, 0.0 ni, 96.5 id, 0.0 wa, 0.0 hi, 0.2 si, 0.0 st 93 | KiB Mem : 6101432 total, 3008716 free, 915304 used, 2177412 buff/cache 94 | KiB Swap: 1023996 total, 1023468 free, 528 used. 5081804 avail Mem 95 | 96 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 97 | 1 root 20 0 4292 756 684 S 0.0 0.0 0:00.00 sh 98 | 5 root 20 0 4044220 272940 16816 S 0.0 4.5 0:07.57 java 99 | 38 root 20 0 19960 3648 3084 S 0.0 0.1 0:00.00 bash 100 | 43 root 20 0 42788 3424 2952 R 0.0 0.1 0:00.00 top 101 | ---- 102 | 103 | or 104 | 105 | what linux distribution is that image using? 106 | ---- 107 | $ cat /etc/os-release 108 | PRETTY_NAME="Debian GNU/Linux 9 (stretch)" 109 | NAME="Debian GNU/Linux" 110 | VERSION_ID="9" 111 | VERSION="9 (stretch)" 112 | ID=debian 113 | HOME_URL="https://www.debian.org/" 114 | SUPPORT_URL="https://www.debian.org/support" 115 | BUG_REPORT_URL="https://bugs.debian.org/" 116 | ---- 117 | 118 | or 119 | 120 | ---- 121 | $ free -h 122 | total used free shared buff/cache available 123 | Mem: 5.8G 893M 2.9G 25M 2.1G 4.8G 124 | Swap: 999M 528K 999M 125 | ---- 126 | 127 | And now you might see part of the problem. "free" is not cgroups aware, it thinks it has access to the whole VMs memory. 128 | 129 | No wonder the JVM reports a larger than accurate Max memory 130 | ---- 131 | $ curl localhost:8080/sysresources 132 | Memory: 1324 Cores: 2 133 | $ java -version 134 | openjdk version "1.8.0_151" 135 | OpenJDK Runtime Environment (build 1.8.0_151-8u151-b12-1~deb9u1-b12) 136 | OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode) 137 | $ java -XshowSettings:vm -version 138 | VM settings: 139 | Max. Heap Size (Estimated): 1.29G 140 | Ergonomics Machine Class: server 141 | Using VM: OpenJDK 64-Bit Server VM 142 | 143 | openjdk version "1.8.0_151" 144 | OpenJDK Runtime Environment (build 1.8.0_151-8u151-b12-1~deb9u1-b12) 145 | OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode) 146 | ---- 147 | 148 | Check out the cgroups settings 149 | 150 | ---- 151 | $ cd /sys/fs/cgroup/memory/ 152 | $ cat memory.limit_in_bytes 153 | 419430400 154 | ---- 155 | 156 | And if you divide that 419430400 by 1024 and 1024, you end up with the 400 that was specified in the deployment yaml 157 | 158 | If you have a JVM of 1.8.0_131 or higher then you can try the experimental options 159 | ---- 160 | $ java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm -version 161 | VM settings: 162 | Max. Heap Size (Estimated): 112.00M 163 | Ergonomics Machine Class: server 164 | Using VM: OpenJDK 64-Bit Server VM 165 | 166 | openjdk version "1.8.0_181" 167 | OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-1~deb9u1-b13) 168 | OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode) 169 | ---- 170 | 171 | and you will notice a very different calculation for Max heap, now it is 112M, about 1/4 of the cgroups constrained memory. 172 | 173 | To leave this pod, simply type "exit" and hit enter 174 | 175 | ---- 176 | $ exit 177 | ---- 178 | 179 | You can now either manually set -Xmx or use the experimental flags for JVM startup. We will be able to do this by creating a new version of the container for our myboot deployment. 180 | 181 | A Dockerfile called "Dockerfile_Memory" with the memory settings we need has been provided already in the hello/springboot directory. 182 | ---- 183 | $ docker build -f Dockerfile_Memory -t 9stepsawesome/myboot:v2 . <1> 184 | # delete any pods based on that image 185 | $ kubectl delete pods -l app=myboot 186 | ---- 187 | <1> _For this to work, your myboot deployment must be looking for an image of this name and version. If you came here from step 2 then you might have left your deployment rolled back and pointing to v1 (causing the creation of this new image to be ignored). Here's how you check and update:_ 188 | ---- 189 | $ echo $(kubectl get deployment/myboot -o jsonpath="{.spec.template.spec.containers[*].image}") 190 | 9stepsawesome/myboot:v1 191 | $ kubectl set image deployment/myboot myboot=9stepsawesome/myboot:v2 192 | ---- 193 | 194 | Wait a moment for the pod to be recreated from the new image then 195 | ---- 196 | $ curl $(minikube --profile 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}")/sysresources 197 | Memory: 112 Cores: 2 198 | ---- 199 | 200 | Now, your memory is more accurate though your core count is still high. They are working on this in later versions of Java. 201 | 202 | And consume should no longer blow up 203 | ---- 204 | $ curl $(minikube --profile 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}")/consume 205 | ---- 206 | 207 | 208 | More information on https://kubernetes.io/docs/tasks/debug-application-cluster/get-shell-running-container/[kubectl exec] 209 | -------------------------------------------------------------------------------- /5_configuration.adoc: -------------------------------------------------------------------------------- 1 | = Step 5: Configuration 2 | Burr Sutter 3 | 4 | == Externalize Configuration 5 | 6 | *_An app’s config is everything that is likely to vary between deploys (staging, production, developer environments, etc)._* 7 | 8 | https://12factor.net/config[12 Factor Apps] 9 | 10 | == Environment Variables 11 | 12 | MyRESTController.java includes a small chunk of code that looks to the environment 13 | 14 | [source,java] 15 | ---- 16 | @RequestMapping("/configure") 17 | public String configure() { 18 | String databaseConn = environment.getProperty("DBCONN","Default"); 19 | String msgBroker = environment.getProperty("MSGBROKER","Default"); 20 | String hello = environment.getProperty("GREETING","Default"); 21 | String love = environment.getProperty("LOVE","Default"); 22 | return "Configuration: \n" 23 | + "databaseConn=" + databaseConn + "\n" 24 | + "msgBroker=" + msgBroker + "\n" 25 | + "hello=" + hello + "\n" 26 | + "love=" + love + "\n"; 27 | } 28 | ---- 29 | 30 | Environment variables can be manipulated at the Deployment level. Changes cause pod redeployment. 31 | 32 | ---- 33 | $ kubectl get pods -w 34 | ---- 35 | 36 | If you have more than one pod, tweak the replicas on the deployment descriptor 37 | ---- 38 | $ kubectl edit deployment myboot 39 | or 40 | $ kubectl scale --replicas=1 deployment/myboot 41 | ---- 42 | 43 | Ask the application for its configuration 44 | ---- 45 | $ curl $(minikube --profile 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}")/configure 46 | Configuration for : myboot-9bfc7b4b4-p69hr 47 | databaseConn=Default 48 | msgBroker=Default 49 | greeting=Default 50 | love=Default 51 | ---- 52 | 53 | Then set the env var at the Deployment 54 | ---- 55 | $ kubectl set env deployment/myboot GREETING="namaste" 56 | ---- 57 | 58 | and you should notice the associated pods being "reborn" 59 | 60 | ---- 61 | myboot-d79789574-8qpzb 0/1 Pending 0 0s 62 | myboot-578ff6dfc7-8krbb 1/1 Terminating 0 40s 63 | myboot-d79789574-8qpzb 0/1 Pending 0 0s 64 | myboot-d79789574-8qpzb 0/1 ContainerCreating 0 0s 65 | myboot-d79789574-8qpzb 1/1 Running 0 1s 66 | ---- 67 | 68 | and you can all the /configure endpoint 69 | ---- 70 | $ curl $(minikube --profile 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}")/configure 71 | Configuration: 72 | databaseConn=Default 73 | msgBroker=Default 74 | hello=namaste 75 | love=Default 76 | ---- 77 | 78 | Set the other variables 79 | 80 | ---- 81 | $ kubectl set env deployment/myboot LOVE="Aloha" 82 | $ kubectl set env deployment/myboot DBCONN="jdbc:sqlserver://45.91.12.123:1443;user=MyUserName;password=*****;" 83 | ---- 84 | 85 | You can see the set variables via the -o yaml trick 86 | ---- 87 | $ kubectl get deployment/myboot -o yaml 88 | # or 89 | $ kubectl get deployment/myboot -o json 90 | ... 91 | spec: 92 | containers: 93 | - env: 94 | - name: hello 95 | value: namaste 96 | - name: GREETING 97 | value: namaste 98 | - name: LOVE 99 | value: Aloha 100 | - name: DBCONN 101 | value: jdbc:sqlserver://45.91.12.123:1443;user=MyUserName;password=*****; 102 | image: 9stepsawesome/myboot:v1 103 | ... 104 | ---- 105 | 106 | as well as via the describe command 107 | 108 | ---- 109 | $ kubectl describe deployment/myboot 110 | ... 111 | Requests: 112 | cpu: 250m 113 | memory: 300Mi 114 | Environment: 115 | hello: namaste 116 | GREETING: namaste 117 | LOVE: Aloha 118 | DBCONN: jdbc:sqlserver://45.91.12.123:1443;user=MyUserName;password=*****; 119 | Mounts: 120 | Volumes: 121 | ... 122 | ---- 123 | 124 | And you can unset the env vars with a "-" 125 | ---- 126 | $ kubectl set env deployment/myboot LOVE- 127 | $ kubectl set env deployment/myboot DBCONN- 128 | $ kubectl set env deployment/myboot GREETING- 129 | ---- 130 | 131 | Note: You if you are quick on your curl command, you might notice that you receive some errors while the pod is being restarted. 132 | The problem is replicas == 1 (there is a single pod/instance of your app) and you have not yet mastered the live/ready probe (coming in step 7). 133 | 134 | == ConfigMaps 135 | 136 | Create a ConfigMap from a directory called config 137 | 138 | ---- 139 | $ kubectl create cm my-config --from-env-file=config/some.properties 140 | ---- 141 | 142 | ---- 143 | $ kubectl get cm 144 | NAME DATA AGE 145 | my-config 2 6s 146 | ---- 147 | 148 | ---- 149 | $ kubectl describe cm my-config 150 | Name: my-config 151 | Namespace: myspace 152 | Labels: 153 | Annotations: 154 | 155 | Data 156 | ==== 157 | GREETING: 158 | ==== 159 | jambo 160 | LOVE: 161 | ==== 162 | Amour 163 | Events: 164 | ---- 165 | 166 | ---- 167 | $ kubectl get cm my-config -o yaml 168 | apiVersion: v1 169 | data: 170 | GREETING: jambo 171 | LOVE: Amour 172 | kind: ConfigMap 173 | metadata: 174 | creationTimestamp: 2018-08-02T03:15:01Z 175 | name: my-config 176 | namespace: myspace 177 | resourceVersion: "168479" 178 | selfLink: /api/v1/namespaces/myspace/configmaps/my-config 179 | uid: 3e52c2a7-9602-11e8-968e-08002783251f 180 | ---- 181 | 182 | Change the deployment to look for its env from the configmap 183 | ---- 184 | $ kubectl replace -f kubefiles/myboot-deployment-configuration.yml 185 | ---- 186 | 187 | Replacement of the Deployment should cause the pods to respawn, if not or just for good measure you can delete them and they will get recreated 188 | ---- 189 | $ kubectl delete pod -l app=myboot 190 | ---- 191 | 192 | ---- 193 | $ curl $(minikube --profile 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}")/configure 194 | Configuration for : myboot-694954fc6d-fzzf4 195 | databaseConn=Default 196 | msgBroker=Default 197 | hello=jambo 198 | love=Amour 199 | ---- 200 | 201 | and switch to the other properties file by recreating the CM 202 | ---- 203 | $ kubectl delete cm my-config 204 | $ kubectl create cm my-config --from-env-file=config/other.properties 205 | $ kubectl delete pod -l app=myboot 206 | $ curl $(minikube --profile 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}")/configure 207 | Configuration for : myboot-694954fc6d-nzdvx 208 | databaseConn=jdbc:sqlserver://123.123.123.123:1443;user=MyUserName;password=*****; 209 | msgBroker=tcp://localhost:61616?jms.useAsyncSend=true 210 | hello=Default 211 | love=Default 212 | ---- 213 | 214 | There are a lot more ways to have fun with ConfigMaps, the core documentation has you manipulate a Pod specification instead of a Deployment but the results are basically the same 215 | https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap 216 | 217 | == Secrets 218 | 219 | The CM example earlier had an example of a database connection string ("user=MyUserName;password=*****"). Sensistive data like passwords can place in a different vessel known as a Secret. 220 | 221 | ---- 222 | $ kubectl create secret generic mysecret --from-literal=user='MyUserName' --from-literal=password='mypassword' 223 | ---- 224 | 225 | ---- 226 | $ kubectl get secrets 227 | ---- 228 | 229 | The user & password are not immediately visible 230 | ---- 231 | $ kubectl describe secret mysecret 232 | Name: mysecret 233 | Namespace: myspace 234 | Labels: 235 | Annotations: 236 | 237 | Type: Opaque 238 | 239 | Data 240 | ==== 241 | password: 10 bytes 242 | user: 10 bytes 243 | ---- 244 | 245 | ---- 246 | $ kubectl get secret mysecret -o yaml 247 | apiVersion: v1 248 | data: 249 | password: bXlwYXNzd29yZA== 250 | user: TXlVc2VyTmFtZQ== 251 | kind: Secret 252 | metadata: 253 | creationTimestamp: "2019-10-08T19:34:43Z" 254 | name: mysecret 255 | namespace: myspace 256 | resourceVersion: "28240" 257 | selfLink: /api/v1/namespaces/myspace/secrets/mysecret 258 | uid: adce776c-ea02-11e9-a569-080027f773ed 259 | type: Opaque 260 | ---- 261 | 262 | It is base64 encoded 263 | ---- 264 | $ echo 'bXlwYXNzd29yZA==' | base64 --decode 265 | mypassword 266 | $ echo 'TXlVc2VyTmFtZQ==' | base64 --decode 267 | MyUserName 268 | ---- 269 | 270 | OR 271 | ---- 272 | $ kubectl get secret mysecret -o jsonpath='{.data.password}' | base64 --decode 273 | mypassword 274 | ---- 275 | 276 | Secrets are provided to the Pod via Volume Mounts 277 | ---- 278 | volumeMounts: 279 | - name: mysecretvolume 280 | mountPath: /mystuff/mysecretvolume 281 | ---- 282 | 283 | New deployment with the secret volume 284 | ---- 285 | $ kubectl replace -f kubefiles/myboot-deployment-configuration-secret.yml 286 | ---- 287 | 288 | Exec into the newly created pod, to see the volume mount 289 | ---- 290 | $ kubectl exec -it myboot-5d6ff444bd-x4bgh /bin/bash 291 | $ cd /mystuff/secretstuff 292 | $ cat user 293 | MyUserName 294 | $ cat password 295 | mypassword 296 | ---- 297 | 298 | You could provide the location of "/mystuff/mysecretvolume" to the pod via an environment variable so the application knows where to go looking. 299 | 300 | More information on https://kubernetes.io/docs/concepts/configuration/secret/[Secrets] -------------------------------------------------------------------------------- /6_discovery.adoc: -------------------------------------------------------------------------------- 1 | = Step 6: Service Discovery & Load-balancing 2 | Burr Sutter 3 | 4 | Now we need two endpoints to experiment with as one needs to another another. In this case, our Spring Boot app will be calling the Node.js app. 5 | 6 | And let's also experiment with two namespaces to make it a wee bit more interesting. 7 | 8 | ---- 9 | $ kubectl create -f kubefiles/yourspace-namespace.yml 10 | ---- 11 | 12 | and then load a Node.js endpoint into that new namespace 13 | ---- 14 | $ cd hello/nodejs 15 | # test it locally 16 | $ npm start 17 | $ curl localhost:8000 18 | $ ctrl-c 19 | # build the docker image and test it 20 | $ docker build -t 9stepsawesome/mynode:v1 . 21 | $ docker run -it -p 8000:8000 9stepsawesome/mynode:v1 22 | $ curl $(minikube --profile 9steps ip):8000 23 | $ ctrl-c 24 | # deploy it into minikube/minishift 25 | $ cd ../.. # to the main 9stepsawesome directory 26 | $ kubectl create -f kubefiles/mynode-deployment.yml -n yourspace 27 | $ kubectl create -f kubefiles/mynode-service.yml -n yourspace 28 | $ kubectl get all -n yourspace 29 | ---- 30 | 31 | Now invoke the "callinganother" endpoint on the myboot service 32 | ---- 33 | $ curl $(minikube --profile 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}" -n myspace)/callinganother 34 | Node Hello on mynode-7dc8bddd4d-spv5h 0 35 | ---- 36 | 37 | The magic is simply knowing the correct URL 38 | [source,java] 39 | ---- 40 | // ..svc.cluster.local 41 | String url = "http://mynode.yourspace.svc.cluster.local:8000/"; 42 | ---- 43 | 44 | If you are within the same namespace then you can simply use 45 | 46 | ---- 47 | String url = "http://mynode:8000/"; 48 | ---- 49 | 50 | 2 replicas of mynode 51 | ---- 52 | $ kubectl scale deployment/mynode --replicas=2 -n yourspace 53 | ---- 54 | 55 | and now invoke the "callinganother" again and again 56 | 57 | [source,bash] 58 | ---- 59 | $ curl $(minikube --profile 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}")/callinganother 60 | Node Hello on mynode-7dc8bddd4d-spv5h 4 61 | $ curl $(minikube --profile 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}")/callinganother 62 | Node Hello on mynode-7dc8bddd4d-6gb8j 1 63 | ---- 64 | 65 | If you wish to update the Node.js endpoint code, just remember the -n yourspace parameter 66 | 67 | ---- 68 | $ docker build -t 9stepsawesome/mynode:v1 . 69 | $ kubectl delete pod -l app=mynode -n yourspace 70 | ---- 71 | The delete will cause another deployment based on the new image. 72 | 73 | And if you wish to "switch" namespaces and avoid typing the -n yourspace all the time, just remember the kubectx and kubens tips mentioned way back in Step 1 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /7_live_ready.adoc: -------------------------------------------------------------------------------- 1 | = Step 7: Live and Ready 2 | Burr Sutter 3 | 4 | Remember that both our rolling update (seen in Step 2) and our setting/unsetting env variables (seen in Step 5) caused the pods to "restart". The problem is that clients (polling curl) also received error messages during this "downtime". 5 | 6 | ---- 7 | $ kubectl scale --replicas=3 deployment/myboot 8 | $ IP=$(minikube --profile 9steps ip) 9 | $ PORT=$(kubectl get service myboot -o jsonpath="{.spec.ports[*].nodePort}") 10 | $ while true; do curl $IP:$PORT; sleep .3; done 11 | $ kubectl set image deployment/myboot myboot=9stepsawesome/myboot:v2 12 | $ kubectl set image deployment/myboot myboot=9stepsawesome/myboot:v1 13 | ---- 14 | 15 | Now, let's have a zero-downtime rolling update/deployment. 16 | 17 | Add the Liveness and Readiness probe to your deployment yaml. 18 | 19 | [source,yaml] 20 | ---- 21 | apiVersion: extensions/v1beta1 22 | kind: Deployment 23 | metadata: 24 | labels: 25 | app: myboot 26 | name: myboot 27 | spec: 28 | replicas: 1 29 | selector: 30 | matchLabels: 31 | app: myboot 32 | template: 33 | metadata: 34 | labels: 35 | app: myboot 36 | spec: 37 | containers: 38 | - name: myboot 39 | image: 9stepsawesome/myboot:v1 40 | ports: 41 | - containerPort: 8080 42 | envFrom: 43 | - configMapRef: 44 | name: my-config 45 | resources: 46 | requests: 47 | memory: "300Mi" 48 | cpu: "250m" # 1/4 core 49 | limits: 50 | memory: "400Mi" 51 | cpu: "1000m" # 1 core 52 | livenessProbe: 53 | httpGet: 54 | port: 8080 55 | path: / 56 | initialDelaySeconds: 10 57 | periodSeconds: 5 58 | timeoutSeconds: 2 59 | readinessProbe: 60 | httpGet: 61 | path: /health 62 | port: 8080 63 | initialDelaySeconds: 10 64 | periodSeconds: 3 65 | ---- 66 | 67 | and replace the current Deployment in the current environment 68 | 69 | ---- 70 | $ kubectl replace -f kubefiles/myboot-deployment-liveready.yml 71 | ---- 72 | 73 | and do a describe to see the new probes in place. 74 | 75 | ---- 76 | $ kubectl describe deployment/myboot 77 | ... 78 | myboot: 79 | Image: 9stepsawesome/myboot:v1 80 | Port: 8080/TCP 81 | Host Port: 0/TCP 82 | Limits: 83 | cpu: 1 84 | memory: 400Mi 85 | Requests: 86 | cpu: 250m 87 | memory: 300Mi 88 | Liveness: http-get http://:http/ delay=10s timeout=2s period=5s #success=1 #failure=3 89 | Readiness: http-get http://:8080/health delay=10s timeout=1s period=3s #success=1 #failure=3 90 | Environment Variables from: 91 | my-config ConfigMap Optional: false 92 | ... 93 | ---- 94 | 95 | now try the rolling update with the poller hitting the endpoints 96 | 97 | start the poller in terminal 1 98 | 99 | [source,bash] 100 | ---- 101 | #!/bin/bash 102 | 103 | while true 104 | do 105 | curl $(minikube --profile 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}") 106 | sleep .3; 107 | done 108 | ---- 109 | 110 | scale up to 2 replicas 111 | 112 | ---- 113 | $ kubectl scale deployment/myboot --replicas=2 114 | ---- 115 | 116 | Make sure you have v2 of the Spring Boot image ready. Change the Java code to say 'Bonjour' and rebuild 117 | ---- 118 | $ mvn clean package 119 | $ docker build -t 9stepsawesome/myboot:v2 . 120 | ---- 121 | 122 | now rollout the update 123 | 124 | ---- 125 | $ kubectl set image deployment/myboot myboot=9stepsawesome/myboot:v2 126 | ---- 127 | 128 | and there will no errors 129 | 130 | ---- 131 | Aloha from Spring Boot! 115 on myboot-859cbbfb98-lnc8q 132 | Aloha from Spring Boot! 116 on myboot-859cbbfb98-lnc8q 133 | Aloha from Spring Boot! 117 on myboot-859cbbfb98-lnc8q 134 | Bonjour from Spring Boot! 0 on myboot-5b686c586f-ccv5r 135 | Bonjour from Spring Boot! 1 on myboot-5b686c586f-ccv5r 136 | ---- 137 | 138 | Rolling back is also as clean 139 | 140 | ---- 141 | $ kubectl rollout undo deployment/myboot 142 | ---- 143 | 144 | ---- 145 | Bonjour from Spring Boot! 30 on myboot-5b686c586f-ccv5r 146 | Bonjour from Spring Boot! 31 on myboot-5b686c586f-ccv5r 147 | Bonjour from Spring Boot! 32 on myboot-5b686c586f-ccv5r 148 | Aloha from Spring Boot! 0 on myboot-859cbbfb98-4rvl8 149 | Aloha from Spring Boot! 1 on myboot-859cbbfb98-4rvl8 150 | ---- 151 | 152 | Once you understand the basics then you can try the advanced demonstration. Where a stateful shopping cart is preserved across a rolling update based on leveraging the readiness probe. 153 | 154 | https://github.com/redhat-developer-demos/popular-movie-store 155 | 156 | 157 | More information on Live & Ready 158 | https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ 159 | 160 | -------------------------------------------------------------------------------- /8_deployment_techniques.adoc: -------------------------------------------------------------------------------- 1 | = Step 8: Deployment Techniques: Blue/Green, Canary 2 | Burr Sutter 3 | 4 | == Blue/Green 5 | 6 | https://martinfowler.com/bliki/BlueGreenDeployment.html[Description of Blue/Green Deployment] 7 | 8 | Switching over to yourspace as the default since we will be manipulating the Node (mynode) app. 9 | 10 | ---- 11 | $ kubectl config set-context --current --namespace=yourspace 12 | # or kubens yourspace 13 | $ kubectl get pods 14 | NAME READY STATUS RESTARTS AGE 15 | mynode-68b9b9ffcc-4kkg5 1/1 Running 0 15m 16 | mynode-68b9b9ffcc-wspxf 1/1 Running 0 18m 17 | ---- 18 | 19 | Two pods of mynode based on the example in Step 6 but if you need to get your replicas sorted try 20 | ---- 21 | $ kubectl scale deployment/mynode --replicas=2 -n yourspace 22 | ---- 23 | and -n yourspace should not be necessary based on the set-context earlier 24 | 25 | 26 | Poll mynode 27 | 28 | [source,bash] 29 | ---- 30 | #!/bin/bash 31 | 32 | while true 33 | do 34 | curl $(minikube --profile 9steps ip):$(kubectl get service/mynode -o jsonpath="{.spec.ports[*].nodePort}") 35 | sleep .3; 36 | done 37 | ---- 38 | 39 | ---- 40 | Node Hello on mynode-668959c78d-j66hl 3 41 | Node Hello on mynode-668959c78d-j66hl 4 42 | Node Hello on mynode-668959c78d-j66hl 5 43 | ---- 44 | 45 | Let's call this version BLUE (the color does not matter) and we wish to deploy GREEN 46 | 47 | You should currently be pointing at the v1 image 48 | ---- 49 | $ kubectl describe deployment mynode 50 | ... 51 | Pod Template: 52 | Labels: app=mynode 53 | Containers: 54 | mynode: 55 | Image: 9stepsawesome/mynode:v1 56 | Port: 8000/TCP 57 | Host Port: 0/TCP 58 | Environment: 59 | ... 60 | ---- 61 | 62 | Modify hello-http.js to say Bonjour 63 | ---- 64 | $ cd hello/nodejs 65 | $ vi hello-http.js 66 | res.end('Bonjour from Node.js! ' + cnt++ + ' on ' + process.env.HOSTNAME + '\n'); 67 | esc-w-q 68 | ---- 69 | 70 | and build a new Docker image 71 | 72 | ---- 73 | $ docker build -t 9stepsawesome/mynode:v2 . 74 | Sending build context to Docker daemon 5.12kB 75 | Step 1/7 : FROM node:8 76 | ---> ed145ef978c4 77 | Step 2/7 : MAINTAINER Burr Sutter "burrsutter@gmail.com" 78 | ---> Using cache 79 | ---> 16e077cca62b 80 | Step 3/7 : EXPOSE 8000 81 | ---> Using cache 82 | ---> 53d9c47ace0d 83 | Step 4/7 : WORKDIR /usr/src/ 84 | ---> Using cache 85 | ---> 5e74464b9671 86 | Step 5/7 : COPY hello-http.js /usr/src 87 | ---> 308423270e08 88 | Step 6/7 : COPY package.json /usr/src 89 | ---> d13548c1332b 90 | Step 7/7 : CMD ["/bin/bash", "-c", "npm start" ] 91 | ---> Running in cbf11794596f 92 | Removing intermediate container cbf11794596f 93 | ---> 73011d539094 94 | Successfully built 73011d539094 95 | Successfully tagged 9stepsawesome/mynode:v2 96 | ---- 97 | 98 | and check out the image 99 | 100 | ---- 101 | $ docker images | grep 9steps 102 | 9stepsawesome/mynode v2 73011d539094 6 seconds ago 673MB 103 | 9stepsawesome/myboot v2 d0c16bffe5f0 38 minutes ago 638MB 104 | 9stepsawesome/myboot v1 f66e4bb1f1cf About an hour ago 638MB 105 | 9stepsawesome/mynode v1 26d9e4e9f3b1 2 hours ago 673MB 106 | ---- 107 | 108 | Run the image to see if you built it correctly 109 | ---- 110 | $ docker run -it -p 8000:8000 9stepsawesome/mynode:v2 111 | $ curl $(minikube --profile 9steps ip):8000 112 | Node Bonjour on bad6fd627ea2 0 113 | ---- 114 | 115 | Now, there is a 2nd deployment yaml for mynodeNew 116 | ---- 117 | apiVersion: extensions/v1beta1 118 | kind: Deployment 119 | metadata: 120 | labels: 121 | app: mynodenew 122 | name: mynodenew 123 | spec: 124 | replicas: 1 125 | selector: 126 | matchLabels: 127 | app: mynodenew 128 | template: 129 | metadata: 130 | labels: 131 | app: mynodenew 132 | spec: 133 | containers: 134 | - name: mynodenew 135 | image: 9stepsawesome/mynode:v2 136 | ports: 137 | - containerPort: 8000 138 | ---- 139 | 140 | ---- 141 | $ cd ../.. 142 | $ kubectl create -f kubefiles/mynode-deployment-new.yml 143 | ---- 144 | 145 | You now have the new pod as well as the old ones 146 | 147 | ---- 148 | $ kubectl get pods 149 | NAME READY STATUS RESTARTS AGE 150 | mynode-68b9b9ffcc-jv4fd 1/1 Running 0 23m 151 | mynode-68b9b9ffcc-vq9k5 1/1 Running 0 23m 152 | mynodenew-5fc946f544-q9ch2 1/1 Running 0 25s 153 | mynodenew-6bddcb55b5-wctmd 1/1 Running 0 25s 154 | ---- 155 | 156 | Yet your client/user is still seeing the old one only 157 | 158 | ---- 159 | $ curl $(minikube ip):$(kubectl get service/mynode -o jsonpath="{.spec.ports[*].nodePort}") 160 | Node Hello on mynode-668959c78d-j66hl 102 161 | ---- 162 | 163 | You can tell the new pod carries the new code with an exec 164 | 165 | ---- 166 | $ kubectl exec -it mynodenew-5fc946f544-q9ch2 /bin/bash 167 | root@mynodenew-5fc946f544-q9ch2:/usr/src# curl localhost:8000 168 | Bonjour from Node.js! 0 on mynodenew-5fc946f544-q9ch2 169 | $ exit 170 | ---- 171 | 172 | 173 | Now update the single Service to point to the new pod and go GREEN 174 | 175 | ---- 176 | $ kubectl patch svc/mynode -p '{"spec":{"selector":{"app":"mynodenew"}}}' 177 | Node Hello on mynode-668959c78d-69mgw 907 178 | Node Bonjour on mynodenew-6bddcb55b5-jvwfk 0 179 | Node Bonjour on mynodenew-6bddcb55b5-jvwfk 1 180 | Node Bonjour on mynodenew-6bddcb55b5-jvwfk 2 181 | Node Bonjour on mynodenew-6bddcb55b5-wctmd 1 182 | ---- 183 | 184 | You have just flipped all users to Bonjour (GREEN) and if you wish to flip back 185 | 186 | ---- 187 | $ kubectl patch svc/mynode -p '{"spec":{"selector":{"app":"mynode"}}}' 188 | Node Bonjour on mynodenew-6bddcb55b5-wctmd 8 189 | Node Hello on mynode-668959c78d-j66hl 957 190 | Node Hello on mynode-668959c78d-69mgw 908 191 | Node Hello on mynode-668959c78d-69mgw 909 192 | ---- 193 | 194 | Note: Our deployment yaml did not have a live & ready probe, things worked out OK here because we waited until long after mynodenew was up and running before flipping the service selector. 195 | 196 | Clean up 197 | ---- 198 | $ kubectl delete deployment mynode 199 | $ kubectl delete deployment mynodenew 200 | ---- 201 | 202 | == Built-In Canary 203 | 204 | https://martinfowler.com/bliki/CanaryRelease.html[Description of Canary] 205 | 206 | There are at least two types of deployments that some folks consider "canary deployments" in Kubernetes. The first is simply the rolling update strategy with the health check (liveness probe), if the liveness check fails, it knows to undo the deployment. 207 | 208 | Switching back to focusing on myboot and myspace 209 | ---- 210 | $ kubectl config set-context --current --namespace=myspace 211 | $ kubectl get pods 212 | kubectl get pods 213 | NAME READY STATUS RESTARTS AGE 214 | myboot-859cbbfb98-4rvl8 1/1 Running 0 55m 215 | myboot-859cbbfb98-rwgp5 1/1 Running 0 55m 216 | ---- 217 | 218 | Make sure myboot has 2 replicas 219 | ---- 220 | $ kubectl scale deployment/myboot --replicas=2 221 | ---- 222 | 223 | and let's attempt to put some really bad code into production 224 | 225 | Go into hello/springboot/MyRESTController.java and add a System.exit(1) into the /health logic 226 | ---- 227 | @RequestMapping(method = RequestMethod.GET, value = "/health") 228 | public ResponseEntity health() { 229 | System.exit(1); 230 | return ResponseEntity.status(HttpStatus.OK) 231 | .body("I am fine, thank you\n"); 232 | } 233 | ---- 234 | 235 | Obviously this sort of thing would never pass through your robust code reviews and automated QA but let's assume it does. 236 | 237 | Build the code 238 | ---- 239 | $ mvn clean package 240 | ---- 241 | 242 | Build the docker image for v3 243 | ---- 244 | $ docker build -t 9stepsawesome/myboot:v3 . 245 | ---- 246 | 247 | Terminal 1: Start a poller 248 | ---- 249 | while true 250 | do 251 | curl $(minikube -p 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}" -n myspace) 252 | sleep .3; 253 | done 254 | ---- 255 | 256 | Terminal 2: Watch pods 257 | ---- 258 | $ kubectl get pods -w 259 | ---- 260 | 261 | Terminal 3: Watch events 262 | ---- 263 | $ kubectl get events --sort-by=.metadata.creationTimestamp 264 | ---- 265 | 266 | Terminal 4: rollout the v3 update 267 | ---- 268 | $ kubectl set image deployment/myboot myboot=9stepsawesome/myboot:v3 269 | ---- 270 | 271 | and watch the fireworks 272 | 273 | 274 | ---- 275 | $ kubectl get pods -w 276 | myboot-5d7fb559dd-qh6fl 0/1 Error 1 11m 277 | myboot-859cbbfb98-rwgp5 0/1 Terminating 0 6h 278 | myboot-859cbbfb98-rwgp5 0/1 Terminating 0 6h 279 | myboot-5d7fb559dd-qh6fl 0/1 CrashLoopBackOff 1 11m 280 | myboot-859cbbfb98-rwgp5 0/1 Terminating 0 6h 281 | ---- 282 | 283 | Look at your Events 284 | ---- 285 | $ kubectl get events -w 286 | 6s Warning Unhealthy pod/myboot-64db5994f6-s24j5 Readiness probe failed: Get http://172.17.0.6:8080/health: net/http: request canceled (Client.Timeout exceeded while awaiting headers) 287 | 6s Warning Unhealthy pod/myboot-64db5994f6-h8g2t Readiness probe failed: Get http://172.17.0.7:8080/health: net/http: request canceled (Client.Timeout exceeded while awaiting headers) 288 | 5s Warning Unhealthy 289 | ---- 290 | 291 | And yet your polling client, stays with the old code & old pod 292 | ---- 293 | Aloha from Spring Boot! 133 on myboot-859cbbfb98-4rvl8 294 | Aloha from Spring Boot! 134 on myboot-859cbbfb98-4rvl8 295 | ---- 296 | 297 | If you watch a while, the CrashLoopBackOff will continue and the restart count will increment. 298 | 299 | Now, go fix the MyRESTController and also change from Hello to Aloha 300 | 301 | No more System.exit() 302 | ---- 303 | @RequestMapping(method = RequestMethod.GET, value = "/health") 304 | public ResponseEntity health() { 305 | return ResponseEntity.status(HttpStatus.OK) 306 | .body("I am fine, thank you\n"); 307 | } 308 | ---- 309 | And change the greeting response to something you recognize. 310 | 311 | Save 312 | 313 | ---- 314 | $ mvn clean package 315 | 316 | $ docker build -t 9stepsawesome/myboot:v3 . 317 | ---- 318 | 319 | and now just wait for the "control loop" to self-correct 320 | 321 | == Manual Canary with multiple Deployments 322 | 323 | Go back to v1 324 | ---- 325 | $ kubectl set image deployment/myboot myboot=9stepsawesome/myboot:v1 326 | ---- 327 | 328 | Next, we will use a 2nd Deployment like we did with Blue/Green. 329 | 330 | ---- 331 | $ kubectl create -f kubefiles/myboot-deployment-canary.yml 332 | ---- 333 | 334 | And you can see a new pod being born 335 | ---- 336 | $ kubectl get pods 337 | ---- 338 | 339 | And this is the v3 one 340 | ---- 341 | $ kubectl get pods -l app=mybootcanary 342 | $ kubectl exec -it mybootcanary-6ddc5d8d48-ptdjv curl localhost:8080/ 343 | ---- 344 | 345 | Now we add a label to both v1 and v3 Deployments PodTemplate, causing new pods to be born 346 | ---- 347 | $ kubectl patch deployment/myboot -p '{"spec":{"template":{"metadata":{"labels":{"newstuff":"withCanary"}}}}}' 348 | $ kubectl patch deployment/mybootcanary -p '{"spec":{"template":{"metadata":{"labels":{"newstuff":"withCanary"}}}}}' 349 | ---- 350 | 351 | Tweak the Service selector for this new label 352 | ---- 353 | $ kubectl patch service/myboot -p '{"spec":{"selector":{"newstuff":"withCanary","app": null}}}' 354 | ---- 355 | 356 | You should see approximately 30% canary mixed in with previous deployment 357 | ---- 358 | Hello from Spring Boot! 23 on myboot-d6c8464-ncpn8 359 | Hello from Spring Boot! 22 on myboot-d6c8464-qnxd8 360 | Aloha from Spring Boot! 83 on mybootcanary-74d99754f4-tx6pj 361 | Hello from Spring Boot! 24 on myboot-d6c8464-ncpn8 362 | ---- 363 | 364 | You can then manipulate the percentages via the replicas associated with each deployment 365 | 20% Aloha (Canary) 366 | ---- 367 | $ kubectl scale deployment/myboot --replicas=4 368 | $ kubectl scale deployment/mybootcanary --replicas=1 369 | ---- 370 | 371 | The challenge with this model is that you have to have the right pod count to get the right mix. If you want a 1% canary, you need 99 of the non-canary pods. 372 | 373 | == Istio Cometh 374 | 375 | The concept of the Canary rollout gets a lot smarter and more interesting with Istio. You also get the concept of dark launches which allows you to push a change into the production environment, send traffic to the new pod(s) yet no responses are actual sent back to the end-user/client. 376 | 377 | See https://bit.ly/istio-tutorial[bit.ly/istio-tutorial] 378 | -------------------------------------------------------------------------------- /9_databases.adoc: -------------------------------------------------------------------------------- 1 | = Step 9: Databases 2 | Burr Sutter 3 | 4 | ifndef::codedir[:codedir: code] 5 | ifndef::imagesdir[:imagesdir: images] 6 | 7 | == Postgres via Deployments 8 | 9 | 1. PersistentVolume 10 | 2. PersistentVolumeClaim 11 | 3. Deployment 12 | 4. Service 13 | 14 | [source,bash] 15 | ---- 16 | $ minikube --profile 9steps docker-env 17 | # or 18 | $ minishift docker-env 19 | 20 | $ eval $(minikube --profile 9steps docker-env) 21 | ---- 22 | 23 | Pre-pull the docker image 24 | 25 | [source,bash] 26 | ---- 27 | $ docker pull postgres:10.5 28 | ---- 29 | 30 | Check to see if you have any preconfigured PVs 31 | 32 | [source,bash] 33 | ---- 34 | $ kubectl get pv --all-namespaces 35 | ---- 36 | 37 | Create the PV, PVC, Deployment and Service 38 | 39 | [source,bash] 40 | ---- 41 | $ kubectl create -f kubefiles/postgres-pv.yml 42 | $ kubectl get pv/postgres-pv 43 | NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE 44 | postgres-pv 2Gi RWO Retain Available mystorage 14s 45 | 46 | $ kubectl create -f kubefiles/postgres-pvc.yml 47 | $ kubectl get pv/postgres-pv 48 | NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE 49 | postgres-pv 2Gi RWO Retain Bound myspace/postgres-pvc mystorage 49s 50 | 51 | $ kubectl get pvc/postgres-pvc 52 | NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE 53 | postgres-pvc Bound postgres-pv 2Gi RWO mystorage 27s 54 | 55 | $ kubectl create -f kubefiles/postgres-deployment.yml 56 | $ kubectl get pods 57 | NAME READY STATUS RESTARTS AGE 58 | postgres-bf856c974-9hdrp 1/1 Running 0 5s 59 | 60 | $ kubectl create -f kubefiles/postgres-service.yml 61 | ---- 62 | 63 | Now, make the Postgres port 5432 available at localhost 64 | 65 | [source,bash] 66 | ---- 67 | $ kubectl port-forward 5433:5432 68 | ---- 69 | 70 | Access with pgAdmin https://www.postgresql.org/ftp/pgadmin/pgadmin4/v3.4 footnote:[Admin login details can be found in the kubefiles/postgres-depoloyment.yml] 71 | 72 | .pgAdmin Add Server 73 | image::pgadmin_add_server.png[pgAdmin Add Server] 74 | 75 | .pgAdmin Add Server 2 76 | image::pgadmin_add_server2.png[pgAdmin Add Server 2] 77 | 78 | 79 | Select Tools -> Query Tool and write and execute the following 80 | 81 | [source,sql] 82 | ---- 83 | CREATE ROLE myuser WITH LOGIN PASSWORD 'mypassword'; 84 | ---- 85 | ---- 86 | ALTER ROLE myuser CREATEDB; 87 | ---- 88 | ---- 89 | CREATE DATABASE mydb; 90 | ---- 91 | ---- 92 | ALTER DATABASE mydb owner to "myuser"; 93 | ---- 94 | 95 | Note: You can also run psql CLI from within the Postgres Pod 96 | ---- 97 | $ kubectl exec -it postgres-bf856c974-xqtf8 /bin/bash 98 | # replacing postgres-bf856c974-xqtf8 with your pod id 99 | $ psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" 100 | 101 | postgresdb-# \l 102 | ---- 103 | 104 | psql tips: 105 | 106 | ---- 107 | \l (list databases) 108 | \c newDatabase (switch database) 109 | \dt (list tables) 110 | \q quit 111 | ---- 112 | 113 | 114 | Run the Spring Boot + Postgres app on localhost. This is using Hibernate's ability to generate database schema from the object model 115 | 116 | [source,bash] 117 | ---- 118 | $ cd hellodata/boot_postgres 119 | 120 | $ mvn spring-boot:run -Dspring-boot.run.arguments=--spring.config.location=src/main/resources/application-local.properties 121 | ---- 122 | 123 | There are 2 application.properties files, one for localhost testing and one for "production" when it runs inside of Kubernetes/OpenShift 124 | 125 | Check pgAdmin 126 | 127 | .pgAdmin Tables 128 | image::pgadmin_schema_creation.png[pgAdmin Tables] 129 | 130 | 131 | Add a test question by POST'ing the testquestion.json file 132 | 133 | [source,bash] 134 | ---- 135 | $ curl -X POST http://localhost:8080/questions -d @testquestion.json --header "Content-Type: application/json" 136 | ---- 137 | 138 | Query for the questions in the database via the REST API 139 | 140 | [source,bash] 141 | ---- 142 | $ curl http://localhost:8080/questions 143 | ---- 144 | 145 | .pgAdmin Questions 146 | image::pgadmin_query_questions1.png[pgAdmin Questions] 147 | 148 | 149 | Now, let's run the Spring Boot app as a Pod 150 | 151 | [source,bash] 152 | ---- 153 | $ mvn clean package -DskipTests 154 | $ docker build -t 9stepsawesome/mybootdata:v1 . 155 | $ cd ../../ 156 | $ kubectl create -f kubefiles/mybootdata-deployment.yml 157 | $ kubectl create -f kubefiles/mybootdata-service.yml 158 | $ kubectl get service/mybootdata -o jsonpath="{.spec.ports[*].nodePort}" 159 | $ QAHOSTPORT=$(minikube --profile 9steps ip):$(kubectl get service/mybootdata -o jsonpath="{.spec.ports[*].nodePort}") 160 | $ curl $QAHOSTPORT/questions 161 | $ cd hellodata/boot_postgres 162 | $ curl -X POST $QAHOSTPORT/questions -d @anotherquestion.json --header "Content-Type: application/json" 163 | $ curl $QAHOSTPORT/questions 164 | ---- 165 | 166 | .pgAdmin Questions 167 | image::pgadmin_query_questions.png[pgAdmin Questions] 168 | 169 | .Browser Questions 170 | image::chrome_rest_api.png[Browser Questions] 171 | 172 | Let's see if the data sticks around 173 | ---- 174 | $ kubectl delete pod -l app=postgres 175 | ---- 176 | 177 | and after the pod gets recreated, check your /questions endpoint 178 | ---- 179 | $ curl $QAHOSTPORT/questions 180 | {"content":[{"createdAt":"2019-10-08T17:41:50.869+0000","updatedAt":"2019-10-08T17:41:50.869+0000","id":1000,"title":"How to walk my dog?","description":"I need to walk my dog on a regular basis, the dog is about 20 pounds and has rather short legs. She is always eager to go but often I am too busy or distracted. What are some strategies for insuring the dog gets walked more often?"},{"createdAt":"2019-10-08T21:46:49.825+0000","updatedAt":"2019-10-08T21:46:49.825+0000","id":1051,"title":"How to shave my yak?","description":"My boss has asked for daily TPS reports, how can I convince him this is simply yak shaving?"}],"pageable":{"sort":{"sorted":false,"unsorted":true},"pageSize":20,"pageNumber":0,"offset":0,"paged":true,"unpaged":false},"last":true,"totalPages":1,"totalElements":2,"first":true,"sort":{"sorted":false,"unsorted":true},"numberOfElements":2,"size":20,"number":0} 181 | ---- 182 | 183 | make sure to restart the port forwarding 184 | 185 | ---- 186 | $ kubectl port-forward postgres-645fbb48f-hml79 5433:5432 187 | ---- 188 | 189 | 190 | == Sharing the in-container data with the host MacOS or Windows 191 | 192 | https://github.com/kubernetes/minikube/blob/master/docs/persistent_volumes.md 193 | 194 | 195 | To share the in-VM directory with the host OS 196 | 197 | minikube automatically shares /Users with the VM 198 | 199 | ---- 200 | minikube --profile 11steps ssh 201 | cd /Users 202 | ---- 203 | 204 | minishift does not automatically share a folder with the VM, so for equivalent functionality 205 | 206 | ---- 207 | minishift hostfolder add -t sshfs --source /Users --target /Users Users 208 | ---- 209 | 210 | Note: The Postgres image will not start by default on /Users/ due to permissions problems 211 | 212 | 213 | -------------------------------------------------------------------------------- /9_debugging.adoc: -------------------------------------------------------------------------------- 1 | = Step 9: Debugging 2 | Burr Sutter 3 | 4 | == Java 5 | 6 | https://blog.christianposta.com/kubernetes/java-remote-debug-for-applications-running-in-kubernetes/ 7 | 8 | 9 | https://hub.docker.com/r/fabric8/java-jboss-openjdk8-jdk/ 10 | 11 | https://maven.fabric8.io/#fabric8:debug 12 | 13 | https://code.visualstudio.com/docs/java/java-kubernetes 14 | 15 | https://code.visualstudio.com/blogs/2017/09/28/java-debug 16 | 17 | For springboot 18 | 19 | Create a directory hello/springboot/src/main/fabric8 20 | 21 | Create a file called service.yml 22 | 23 | ---- 24 | apiVersion: v1 25 | kind: Service 26 | spec: 27 | type: NodePort 28 | ---- 29 | 30 | and Create a file called deployment.yml 31 | 32 | ---- 33 | apiVersion: extensions/v1beta1 34 | kind: Deployment 35 | metadata: 36 | namespace: myspace 37 | ---- 38 | 39 | mvn io.fabric8:fabric8-maven-plugin:3.5.40:setup 40 | 41 | mvn fabric8:debug 42 | 43 | ---- 44 | ... 45 | [INFO] Using namespace: default 46 | Trying internal type for name:Service 47 | Trying internal type for name:Deployment 48 | [INFO] Updating a Service from kubernetes.yml 49 | [INFO] Updated Service: target/fabric8/applyJson/default/service-boot-demo-1.json 50 | [INFO] F8: Enabling debug on Deployment boot-demo 51 | [WARNING] The client is using resource type 'deployments' with unstable version 'v1beta1' 52 | [INFO] F8: Waiting for debug pod with selector LabelSelector(matchExpressions=[], matchLabels={app=boot-demo, provider=fabric8, group=com.burrsutter}, additionalProperties={}) and environment variables {JAVA_DEBUG_SUSPEND=false, JAVA_ENABLE_DEBUG=true} 53 | Trying internal type for name:Pod 54 | [INFO] F8:[W] boot-demo-bd7bcfd87-smthv status: Pending 55 | [INFO] F8:[W] boot-demo-55d8cc66fd-qpws4 status: Running Ready 56 | [INFO] F8:[W] boot-demo-bd7bcfd87-smthv status: Pending 57 | [INFO] F8:[W] boot-demo-55d8cc66fd-qpws4 status: Running 58 | [INFO] F8:[W] boot-demo-bd7bcfd87-smthv status: Running Ready 59 | [INFO] F8:[W] boot-demo-55d8cc66fd-qpws4 status: Running 60 | [INFO] F8:[W] boot-demo-55d8cc66fd-qpws4 status: Running 61 | [INFO] F8: Port forwarding to port 5005 on pod boot-demo-bd7bcfd87-smthv using command /Users/burrsutter/minikube_0.28.1/bin/kubectl 62 | [INFO] F8:[W] boot-demo-55d8cc66fd-qpws4 status: Running : Pod Deleted 63 | ---- 64 | 65 | Visual Studio Code 66 | 67 | Debug - Add Configuration 68 | 69 | which creates a Launch.json file, then comment it like so 70 | ---- 71 | { 72 | // Use IntelliSense to learn about possible attributes. 73 | // Hover to view descriptions of existing attributes. 74 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 75 | "version": "0.2.0", 76 | "configurations": [ 77 | // { 78 | // "type": "java", 79 | // "name": "Debug (Launch)", 80 | // "request": "launch", 81 | // "cwd": "${workspaceFolder}", 82 | // "console": "internalConsole", 83 | // "stopOnEntry": false, 84 | // "mainClass": "", 85 | // "args": "" 86 | // }, 87 | // { 88 | // "type": "java", 89 | // "name": "Debug (Launch)-HellobootApplication", 90 | // "request": "launch", 91 | // "cwd": "${workspaceFolder}", 92 | // "console": "internalConsole", 93 | // "stopOnEntry": false, 94 | // "mainClass": "com.burrsutter.HellobootApplication", 95 | // "args": "", 96 | // "projectName": "boot-demo" 97 | // }, 98 | { 99 | "type": "java", 100 | "name": "Debug (Attach)", 101 | "request": "attach", 102 | "hostName": "localhost", 103 | "port": "5005" 104 | } 105 | ] 106 | } 107 | ---- 108 | 109 | with port 5005 110 | 111 | 112 | 113 | == Node.js 114 | https://developers.redhat.com/blog/2018/05/15/debug-your-node-js-application-on-openshift-with-chrome-devtools/ 115 | 116 | -------------------------------------------------------------------------------- /config/other.properties: -------------------------------------------------------------------------------- 1 | DBCONN=jdbc:sqlserver://123.123.123.123:1443;user=MyUserName;password=*****; 2 | MSGBROKER=tcp://localhost:61616?jms.useAsyncSend=true -------------------------------------------------------------------------------- /config/some.properties: -------------------------------------------------------------------------------- 1 | GREETING=jambo 2 | LOVE=Amour -------------------------------------------------------------------------------- /hello/go/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi8/ubi-minimal 2 | EXPOSE 8000 3 | COPY myrest /usr/bin 4 | CMD /bin/sh -c '/usr/bin/myrest' 5 | 6 | -------------------------------------------------------------------------------- /hello/go/myrest: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burrsutter/9stepsawesome/4974e4fe11b438a489a947a339ec5b105273111f/hello/go/myrest -------------------------------------------------------------------------------- /hello/go/myrest.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | // "time" 8 | ) 9 | 10 | func main() { 11 | 12 | //api := mux.NewRouter() 13 | http.HandleFunc("/", HelloHandler) 14 | //http.Handle("/hello", api) 15 | 16 | fmt.Println("Listening on localhost:8000") 17 | http.ListenAndServe(":8000", nil) 18 | } 19 | 20 | func HelloHandler(w http.ResponseWriter, r *http.Request) { 21 | w.WriteHeader(http.StatusOK) 22 | hostname, err := os.Hostname() 23 | if err != nil { 24 | fmt.Println("unable to get hostname") 25 | } 26 | 27 | // fmt.Fprintf(w, "Hello from Go! %s on %s\n", time.Now(), hostname) 28 | fmt.Fprintf(w, "Go Hello on %s\n", hostname) 29 | } 30 | -------------------------------------------------------------------------------- /hello/go/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | Download and install go 3 | https://golang.org/dl/ 4 | 5 | go build myrest.go 6 | 7 | then run the compiled executable 8 | ./myrest 9 | 10 | curl localhost:8000/hello 11 | 12 | ctrl-c 13 | 14 | Note: go compiles to native and if you have been using a Mac/Windows 15 | you likely need to recompile the binary 16 | 17 | env GOOS=linux GOARCH=amd64 go build myrest.go 18 | 19 | docker build -t burr/mygo:v1 . 20 | 21 | docker run -it -p 8000:8000 burr/mygo:v1 22 | 23 | Thank you to Jesus R who figured this out for me! 24 | https://github.com/jmrodri/go-demo 25 | 26 | 27 | -------------------------------------------------------------------------------- /hello/microprofile/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.burrsutter 4 | swarm-demo 5 | WildFly Swarm Example 6 | 1.0.0-SNAPSHOT 7 | war 8 | 9 | 10 | 2017.6.1 11 | 1.8 12 | 1.8 13 | false 14 | UTF-8 15 | 16 | 17 | 18 | 19 | 20 | org.wildfly.swarm 21 | bom-all 22 | ${version.wildfly.swarm} 23 | import 24 | pom 25 | 26 | 27 | 28 | 29 | 30 | demo 31 | 32 | 33 | org.wildfly.swarm 34 | wildfly-swarm-plugin 35 | ${version.wildfly.swarm} 36 | 37 | 38 | 39 | 40 | package 41 | 42 | 43 | 44 | 45 | 46 | io.fabric8 47 | fabric8-maven-plugin 48 | 3.3.5 49 | 50 | 51 | fmp 52 | 53 | resource 54 | helm 55 | build 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | javax 67 | javaee-api 68 | 7.0 69 | provided 70 | 71 | 72 | 73 | org.wildfly.swarm 74 | jaxrs 75 | 76 | 77 | -------------------------------------------------------------------------------- /hello/microprofile/readme.txt: -------------------------------------------------------------------------------- 1 | Initial pom.xml created by 2 | wildfly-swarm.io/generator 3 | 4 | Assumes use of CDK Minishift 5 | https://developers.redhat.com/products/cdk/download/ 6 | 7 | mvn clean compile package 8 | 9 | java -jar target/demo-swarm.jar 10 | or 11 | mvn wildfly-swarm:run 12 | 13 | http://localhost:8080/hello 14 | ctrl-c 15 | 16 | minishift ip 17 | minishift oc-env 18 | 19 | oc login 192.168.99.101:8443 20 | admin 21 | admin 22 | 23 | (note: the admin user is created by the CDK version of Minishift only) 24 | 25 | oc new-project swarmdemo 26 | 27 | Note: mvn io.fabric8:fabric8-maven-plugin:3.3.5:setup 28 | was executed to instrument the pom.xml with the F8 Maven plugin 29 | 30 | mvn fabric8:deploy 31 | 32 | http://swarm-demo-swarmdemo.192.168.99.101.nip.io/hello 33 | -------------------------------------------------------------------------------- /hello/microprofile/src/main/java/com/burrsutter/swarmdemo/rest/HelloWorldEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.burrsutter.swarmdemo.rest; 2 | 3 | 4 | import javax.ws.rs.Path; 5 | import javax.ws.rs.core.Response; 6 | import javax.ws.rs.GET; 7 | import javax.ws.rs.Produces; 8 | 9 | 10 | @Path("/hello") 11 | public class HelloWorldEndpoint { 12 | final String hostname = System.getenv().getOrDefault("HOSTNAME", "unknown"); 13 | 14 | @GET 15 | @Produces("text/plain") 16 | public Response doGet() { 17 | return Response.ok("Hello from WildFly Swarm! " + new java.util.Date() + " on " + hostname).build(); 18 | } 19 | } -------------------------------------------------------------------------------- /hello/nodejs/.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nodeshift/centos7-s2i-nodejs:10.x 2 | 3 | LABEL maintainer="Burr Sutter \"burrsutter@gmail.com\"" 4 | 5 | EXPOSE 8000 6 | 7 | WORKDIR /opt/app-root/src 8 | 9 | CMD ["npm", "start"] 10 | -------------------------------------------------------------------------------- /hello/nodejs/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Node Sample", 3 | "dockerFile": "Dockerfile", 4 | "appPort": "8000", 5 | "extensions": [ 6 | // "afractal.node-essentials", 7 | "visualstudioexptteam.vscodeintellicode", 8 | "ms-vscode.node-debug2" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /hello/nodejs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nodeshift/centos7-s2i-nodejs:10.x 2 | 3 | LABEL maintainer="Burr Sutter \"burrsutter@gmail.com\"" 4 | 5 | EXPOSE 8000 6 | 7 | WORKDIR /opt/app-root/src 8 | 9 | COPY hello-http.js . 10 | COPY package.json . 11 | 12 | CMD ["npm", "start"] 13 | -------------------------------------------------------------------------------- /hello/nodejs/hello-http.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const http = require('http'); 3 | let cnt = 0; 4 | 5 | http.createServer((req, res) => 6 | { 7 | // don't increment the counter if the favicon.ico is being requested 8 | if (req.url.toLowerCase() === '/favicon.ico') { 9 | res.writeHead(200, { 'Content-Type': 'image/x-icon' }); 10 | res.end(); 11 | console.log('favicon requested'); 12 | return; 13 | } 14 | 15 | res.end(`Node Bonjour on ${os.hostname()} ${cnt++} \n`); 16 | } 17 | 18 | ).listen(8000); 19 | 20 | console.log(`Server running at http://localhost:8000/`); 21 | 22 | -------------------------------------------------------------------------------- /hello/nodejs/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /hello/nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello", 3 | "version": "1.0.0", 4 | "description": "Hello microservice running on NodeJS", 5 | "main": "hello-http.js", 6 | "scripts": { 7 | "start": "node hello-http.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/burrsutter/9stepsawesome/tree/master/hello/nodejs" 12 | }, 13 | "author": "burrsutter@gmail.com", 14 | "license": "Apache-2.0" 15 | } 16 | -------------------------------------------------------------------------------- /hello/nodejs/readme.txt: -------------------------------------------------------------------------------- 1 | Test it plain 2 | node -v 3 | v8.11.3 4 | npm -v 5 | v8.11.3 6 | 7 | 8 | npm start 9 | curl localhost:8000 10 | 11 | Test it in minishift or minikube's Docker 12 | minishift docker-env 13 | minikube docker-env 14 | 15 | 16 | docker build -f Dockerfile -t dev.local/burrsutter/mynode:v1 . 17 | or 18 | docker build -f Dockerfile.openshift -t dev.local/burrsutter/mynode:v1 . 19 | 20 | docker login docker.io 21 | docker images | grep mynode 22 | docker tag $1 docker.io/burrsutter/mynode:v1 23 | docker push docker.io/burrsutter/mynode:v1 24 | or 25 | docker login quay.io 26 | docker images | grep mynode 27 | docker tag $1 quay.io/burrsutter/mynode:v1 28 | docker push quay.io/burrsutter/mynode:v1 29 | 30 | 31 | 32 | to test via Docker: 33 | docker run --rm -d -p 8000:8000 dev.local/burrsutter/mynode:v1 34 | 35 | docker ps | grep mynode 36 | 37 | docker stop 08efa083696b 38 | 39 | 40 | deploy it into minikube/minishift 41 | 42 | kubectl create -f kubefiles/mynode-deployment.yml -------------------------------------------------------------------------------- /hello/python/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:2 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY requirements.txt ./ 6 | 7 | RUN pip install --no-cache-dir -r requirements.txt 8 | 9 | COPY . . 10 | 11 | EXPOSE 8000 12 | 13 | CMD [ "python", "./app.py" ] -------------------------------------------------------------------------------- /hello/python/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flask import Flask 4 | app = Flask(__name__) 5 | 6 | @app.route("/") 7 | def main(): 8 | return "Python Hello on " + os.getenv('HOSTNAME', "unknown") + "\n" 9 | 10 | if __name__ == "__main__": 11 | app.run(host='0.0.0.0',port='8000') 12 | 13 | -------------------------------------------------------------------------------- /hello/python/readme.txt: -------------------------------------------------------------------------------- 1 | https://www.python.org/ftp/python/2.7.15/python-2.7.15-macosx10.9.pkg 2 | 3 | python --version 4 | Python 2.7.15 5 | 6 | pip --version 7 | pip 19.0.3 8 | 9 | pip install --no-cache-dir -r requirements.txt 10 | 11 | python app.py 12 | 13 | curl localhost:8000 14 | ctrl-c 15 | 16 | docker build -t burrsutter/flask_web_app . 17 | 18 | docker run -it -p 8000:8000 --rm burrsutter/flask_web_app 19 | 20 | curl localhost:8000 -------------------------------------------------------------------------------- /hello/python/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.0.2 -------------------------------------------------------------------------------- /hello/quarkus/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !target/*-runner -------------------------------------------------------------------------------- /hello/quarkus/buildNativeLinux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export GRAALVM_HOME=~/tools/graalvm-ce-19.1.1/Contents/Home/ 4 | 5 | mvn package -Pnative -Dnative-image.docker-build=true -DskipTests -------------------------------------------------------------------------------- /hello/quarkus/buildNativeMac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export GRAALVM_HOME=~/tools/graalvm-ce-19.1.1/Contents/Home/ 4 | 5 | # Mac Native 6 | mvn package -Pnative 7 | 8 | -------------------------------------------------------------------------------- /hello/quarkus/build_push_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IMAGE_VER=quarkus-demo:2.0.0 4 | 5 | docker build -f Dockerfile -t dev.local/burrsutter/$IMAGE_VER . 6 | docker login docker.io 7 | docker tag dev.local/burrsutter/$IMAGE_VER docker.io/burrsutter/$IMAGE_VER 8 | docker push docker.io/burrsutter/$IMAGE_VER 9 | -------------------------------------------------------------------------------- /hello/quarkus/build_push_quay.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IMAGE_VER=quarkus-demo:2.0.0 4 | 5 | docker build -f kubefiles/Dockerfile -t dev.local/burrsutter/$IMAGE_VER . 6 | docker login quay.io 7 | docker tag dev.local/burrsutter/$IMAGE_VER quay.io/burrsutter/$IMAGE_VER 8 | docker push quay.io/burrsutter/$IMAGE_VER 9 | -------------------------------------------------------------------------------- /hello/quarkus/dockerbuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build -f kubefiles/Dockerfile -t dev.local/burrsutter/quarkus-demo:2.0.0 . -------------------------------------------------------------------------------- /hello/quarkus/dockerbuild_openshift.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build -f kubefiles/Dockerfile.openshift -t dev.local/burrsutter/quarkus-demo:2.0.0 . -------------------------------------------------------------------------------- /hello/quarkus/dockerpush_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # use docker images | grep quarkus to get the image ID for $1 4 | 5 | docker login docker.io 6 | 7 | docker tag $1 docker.io/burrsutter/quarkus-demo:2.0.0 8 | 9 | docker push docker.io/burrsutter/quarkus-demo:2.0.0 10 | 11 | -------------------------------------------------------------------------------- /hello/quarkus/dockerpush_quay.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # use docker images | grep quarkus to get the image ID for $1 4 | 5 | docker login quay.io 6 | 7 | docker tag $1 quay.io/burrsutter/quarkus-demo:2.0.0 8 | 9 | docker push quay.io/burrsutter/quarkus-demo:2.0.0 10 | 11 | echo 'quay.io marks repositories as private by default' 12 | echo 'to update https://screencast.com/t/uAooYnghlW' -------------------------------------------------------------------------------- /hello/quarkus/kubefiles/Deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: myquarkus 6 | name: myquarkus 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: myquarkus 12 | template: 13 | metadata: 14 | labels: 15 | app: myquarkus 16 | spec: 17 | containers: 18 | - name: myquarkus 19 | image: dev.local/burrsutter/quarkus-demo:2.0.0 20 | ports: 21 | - containerPort: 8080 22 | resources: 23 | requests: 24 | memory: "50Mi" 25 | cpu: "250m" # 1/4 core 26 | limits: 27 | memory: "50Mi" 28 | cpu: "250m" 29 | livenessProbe: 30 | httpGet: 31 | port: 8080 32 | path: / 33 | initialDelaySeconds: 1 34 | periodSeconds: 5 35 | timeoutSeconds: 2 36 | readinessProbe: 37 | httpGet: 38 | path: /healthz 39 | port: 8080 40 | initialDelaySeconds: 1 41 | periodSeconds: 3 42 | 43 | -------------------------------------------------------------------------------- /hello/quarkus/kubefiles/Deployment_quay.yml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: myquarkus 6 | name: myquarkus 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: myquarkus 12 | template: 13 | metadata: 14 | labels: 15 | app: myquarkus 16 | spec: 17 | containers: 18 | - name: myquarkus 19 | image: quay.io/burrsutter/quarkus-demo:2.0.0 20 | ports: 21 | - containerPort: 8080 22 | resources: 23 | requests: 24 | memory: "50Mi" 25 | cpu: "250m" # 1/4 core 26 | limits: 27 | memory: "50Mi" 28 | cpu: "250m" 29 | livenessProbe: 30 | httpGet: 31 | port: 8080 32 | path: / 33 | initialDelaySeconds: 1 34 | periodSeconds: 5 35 | timeoutSeconds: 2 36 | readinessProbe: 37 | httpGet: 38 | path: /healthz 39 | port: 8080 40 | initialDelaySeconds: 1 41 | periodSeconds: 3 42 | 43 | -------------------------------------------------------------------------------- /hello/quarkus/kubefiles/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi8/ubi-minimal 2 | WORKDIR /work/ 3 | COPY target/*-runner /work/application 4 | RUN chmod 775 /work 5 | EXPOSE 8080 6 | CMD ["./application", "-Xmx8m", "-Xmn8m", "-Xms8m"] -------------------------------------------------------------------------------- /hello/quarkus/kubefiles/Dockerfile.openshift: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi8/ubi-minimal 2 | WORKDIR /work/ 3 | RUN chgrp -R 0 /work && \ 4 | chmod -R g=u /work 5 | COPY target/*-runner /work/application 6 | EXPOSE 8080 7 | USER 1001 8 | ENTRYPOINT [ "./application", "-Xmx8m", "-Xmn8m", "-Xms8m" ] 9 | -------------------------------------------------------------------------------- /hello/quarkus/kubefiles/Service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: myquarkus 5 | labels: 6 | app: myquarkus 7 | spec: 8 | ports: 9 | - name: http 10 | port: 8080 11 | selector: 12 | app: myquarkus 13 | type: LoadBalancer -------------------------------------------------------------------------------- /hello/quarkus/poller.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while true 4 | do 5 | curl $(minikube -p 9steps ip):$(kubectl get service/myquarkus -o jsonpath="{.spec.ports[*].nodePort}") 6 | sleep .2; 7 | done 8 | 9 | -------------------------------------------------------------------------------- /hello/quarkus/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.redhat.developer.demo 6 | quarkus-demo 7 | 2.0.0 8 | 9 | UTF-8 10 | 2.22.0 11 | 0.22.0 12 | 1.8 13 | UTF-8 14 | 1.8 15 | 16 | 17 | 18 | 19 | io.quarkus 20 | quarkus-bom 21 | ${quarkus.version} 22 | pom 23 | import 24 | 25 | 26 | 27 | 28 | 29 | io.quarkus 30 | quarkus-resteasy 31 | 32 | 33 | io.quarkus 34 | quarkus-junit5 35 | test 36 | 37 | 38 | io.rest-assured 39 | rest-assured 40 | test 41 | 42 | 43 | 44 | 45 | 46 | io.quarkus 47 | quarkus-maven-plugin 48 | ${quarkus.version} 49 | 50 | 51 | 52 | build 53 | 54 | 55 | 56 | 57 | 58 | maven-surefire-plugin 59 | ${surefire-plugin.version} 60 | 61 | 62 | org.jboss.logmanager.LogManager 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | native 71 | 72 | 73 | native 74 | 75 | 76 | 77 | 78 | 79 | io.quarkus 80 | quarkus-maven-plugin 81 | ${quarkus.version} 82 | 83 | 84 | 85 | native-image 86 | 87 | 88 | true 89 | 90 | 91 | 92 | 93 | 94 | maven-failsafe-plugin 95 | ${surefire-plugin.version} 96 | 97 | 98 | 99 | integration-test 100 | verify 101 | 102 | 103 | 104 | ${project.build.directory}/${project.build.finalName}-runner 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /hello/quarkus/quarkus.log: -------------------------------------------------------------------------------- 1 | 2019-03-03 11:55:45,802 burr quarkus-0.0.1-runner[78832] INFO [io.quarkus] (main) Quarkus 1.0.0.Alpha1-SNAPSHOT started in 0.008s. Listening on: http://localhost:8080 2 | 2019-03-03 11:55:45,804 burr quarkus-0.0.1-runner[78832] INFO [io.quarkus] (main) Installed features: [cdi, resteasy] 3 | 2019-03-03 11:55:54,755 burr quarkus-0.0.1-runner[78832] INFO [io.quarkus] (main) Quarkus stopped in 0.000s 4 | 2019-03-03 11:56:03,885 burr quarkus-0.0.1-runner[78872] INFO [io.quarkus] (main) Quarkus 1.0.0.Alpha1-SNAPSHOT started in 0.010s. Listening on: http://localhost:8080 5 | 2019-03-03 11:56:03,886 burr quarkus-0.0.1-runner[78872] INFO [io.quarkus] (main) Installed features: [cdi, resteasy] 6 | 2019-03-03 11:56:51,692 burr quarkus-0.0.1-runner[78872] INFO [io.quarkus] (main) Quarkus stopped in 0.011s 7 | 2019-03-03 11:56:55,493 burr quarkus-0.0.1-runner[78982] INFO [io.quarkus] (main) Quarkus 1.0.0.Alpha1-SNAPSHOT started in 0.008s. Listening on: http://localhost:8080 8 | 2019-03-03 11:56:55,494 burr quarkus-0.0.1-runner[78982] INFO [io.quarkus] (main) Installed features: [cdi, resteasy] 9 | 2019-03-03 11:58:28,082 burr quarkus-0.0.1-runner[78982] INFO [io.quarkus] (main) Quarkus stopped in 0.000s 10 | 2019-03-09 13:00:26,616 burr quarkus-0.0.1-runner.jar[96562] DEBUG [org.xnio] (main) XNIO version 3.6.5.Final 11 | 2019-03-09 13:00:26,666 burr quarkus-0.0.1-runner.jar[96562] DEBUG [org.xni.nio] (main) XNIO NIO Implementation Version 3.6.5.Final 12 | 2019-03-09 13:00:26,786 burr quarkus-0.0.1-runner.jar[96562] DEBUG [org.jbo.threads] (main) JBoss Threads version 3.0.0.Alpha4 13 | 2019-03-09 13:05:18,555 burr quarkus-0.0.1-runner.jar[96818] DEBUG [org.xnio] (main) XNIO version 3.6.5.Final 14 | 2019-03-09 13:05:18,606 burr quarkus-0.0.1-runner.jar[96818] DEBUG [org.xni.nio] (main) XNIO NIO Implementation Version 3.6.5.Final 15 | 2019-03-09 13:05:18,725 burr quarkus-0.0.1-runner.jar[96818] DEBUG [org.jbo.threads] (main) JBoss Threads version 3.0.0.Alpha4 16 | 2019-03-09 13:05:18,792 burr quarkus-0.0.1-runner.jar[96818] INFO [io.quarkus] (main) Quarkus 1.0.0.Alpha1-SNAPSHOT started in 0.846s. Listening on: http://127.0.0.1:8080 17 | 2019-03-09 13:05:18,795 burr quarkus-0.0.1-runner.jar[96818] INFO [io.quarkus] (main) Installed features: [cdi, resteasy] 18 | 2019-03-09 13:05:28,004 burr quarkus-0.0.1-runner.jar[96818] INFO [io.quarkus] (main) Quarkus stopped in 0.008s 19 | 2019-03-12 11:36:22,841 burr quarkus-0.0.1-runner.jar[19144] DEBUG [org.xnio] (main) XNIO version 3.6.5.Final 20 | 2019-03-12 11:36:22,892 burr quarkus-0.0.1-runner.jar[19144] DEBUG [org.xni.nio] (main) XNIO NIO Implementation Version 3.6.5.Final 21 | 2019-03-12 11:36:23,011 burr quarkus-0.0.1-runner.jar[19144] DEBUG [org.jbo.threads] (main) JBoss Threads version 3.0.0.Alpha4 22 | 2019-03-12 11:36:23,087 burr quarkus-0.0.1-runner.jar[19144] INFO [io.quarkus] (main) Quarkus 0.11.0 started in 0.653s. Listening on: http://127.0.0.1:8080 23 | 2019-03-12 11:36:23,089 burr quarkus-0.0.1-runner.jar[19144] INFO [io.quarkus] (main) Installed features: [cdi, resteasy] 24 | 2019-03-12 11:36:31,212 burr quarkus-0.0.1-runner.jar[19144] INFO [io.quarkus] (main) Quarkus stopped in 0.007s 25 | 2019-03-12 11:46:27,414 burr quarkus-0.0.1-runner[19954] INFO [io.quarkus] (main) Quarkus 0.11.0 started in 0.011s. Listening on: http://127.0.0.1:8080 26 | 2019-03-12 11:46:27,417 burr quarkus-0.0.1-runner[19954] INFO [io.quarkus] (main) Installed features: [cdi, resteasy] 27 | 2019-03-12 11:46:34,621 burr quarkus-0.0.1-runner[19954] INFO [io.quarkus] (main) Quarkus stopped in 0.000s 28 | 2019-04-19 09:10:58,607 burr quarkus-demo-1.0.0-runner.jar[83274] DEBUG [org.xnio] (main) XNIO version 3.7.0.Final 29 | 2019-04-19 09:10:58,646 burr quarkus-demo-1.0.0-runner.jar[83274] DEBUG [org.xni.nio] (main) XNIO NIO Implementation Version 3.7.0.Final 30 | 2019-04-19 09:10:58,756 burr quarkus-demo-1.0.0-runner.jar[83274] DEBUG [org.jbo.threads] (main) JBoss Threads version 3.0.0.Alpha4 31 | 2019-04-19 09:10:58,823 burr quarkus-demo-1.0.0-runner.jar[83274] INFO [io.quarkus] (main) Quarkus 0.12.0 started in 0.683s. Listening on: http://[::]:8080 32 | 2019-04-19 09:10:58,825 burr quarkus-demo-1.0.0-runner.jar[83274] INFO [io.quarkus] (main) Installed features: [cdi, resteasy] 33 | 2019-04-19 09:11:08,696 burr quarkus-demo-1.0.0-runner.jar[83274] INFO [io.quarkus] (main) Quarkus stopped in 0.007s 34 | -------------------------------------------------------------------------------- /hello/quarkus/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | mvn compile quarkus:dev 3 | curl localhost:8080 4 | ctrl-c 5 | 6 | mvn clean package 7 | 8 | ./buildNativeLinux.sh 9 | 10 | ./dockerbuild.sh 11 | 12 | 13 | kubectl apply -f kubefiles/Deployment.yml 14 | OR 15 | kubectl apply -f kubefiles/Deployment_quay.yml 16 | 17 | kubectl apply -f kubefiles/Service.yml 18 | 19 | ./poller.sh 20 | 21 | 22 | -------------------------------------------------------------------------------- /hello/quarkus/src/main/java/com/redhat/developer/demo/GreetingEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.redhat.developer.demo; 2 | 3 | 4 | import javax.ws.rs.GET; 5 | import javax.ws.rs.Path; 6 | 7 | import javax.ws.rs.Produces; 8 | import javax.ws.rs.core.MediaType; 9 | 10 | 11 | @Path("/") 12 | public class GreetingEndpoint { 13 | 14 | private String prefix = "Supersonic Subatomic Java with Quarkus"; 15 | 16 | private String HOSTNAME = 17 | System.getenv().getOrDefault("HOSTNAME", "unknown"); 18 | 19 | private int count = 0; 20 | 21 | @GET 22 | @Produces(MediaType.TEXT_PLAIN) 23 | public String greet() { 24 | count++; 25 | return prefix + " " + HOSTNAME + ":" + count + "\n"; 26 | } 27 | 28 | @GET 29 | @Path("/healthz") 30 | @Produces(MediaType.TEXT_PLAIN) 31 | public String health() { 32 | return "OK"; 33 | } 34 | 35 | @GET 36 | @Path("/myresources") 37 | public String getSystemResources() { 38 | long memory = Runtime.getRuntime().maxMemory(); 39 | int cores = Runtime.getRuntime().availableProcessors(); 40 | System.out.println("/myresources " + HOSTNAME); 41 | return 42 | " Memory: " + (memory / 1024 / 1024) + 43 | " Cores: " + cores + "\n"; 44 | } 45 | 46 | @GET 47 | @Path("/consume") 48 | public String consumeSome() { 49 | System.out.println("/consume " + HOSTNAME); 50 | 51 | Runtime rt = Runtime.getRuntime(); 52 | StringBuilder sb = new StringBuilder(); 53 | long maxMemory = rt.maxMemory(); 54 | long usedMemory = 0; 55 | // while usedMemory is less than 80% of Max 56 | while (((float) usedMemory / maxMemory) < 0.80) { 57 | sb.append(System.nanoTime() + sb.toString()); 58 | usedMemory = rt.totalMemory(); 59 | } 60 | String msg = "Allocated about 80% (" + humanReadableByteCount(usedMemory, false) + ") of the max allowed JVM memory size (" 61 | + humanReadableByteCount(maxMemory, false) + ")"; 62 | System.out.println(msg); 63 | return msg + "\n"; 64 | } 65 | 66 | public static String humanReadableByteCount(long bytes, boolean si) { 67 | int unit = si ? 1000 : 1024; 68 | if (bytes < unit) 69 | return bytes + " B"; 70 | int exp = (int) (Math.log(bytes) / Math.log(unit)); 71 | String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); 72 | return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /hello/springboot/.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8u151 2 | ENV JAVA_APP_JAR boot-demo-0.0.1.jar 3 | 4 | ## Ensure maven is installed 5 | RUN apt-get update -y && apt-get install maven -y 6 | 7 | WORKDIR /app/ 8 | EXPOSE 8080 9 | CMD java -XX:+PrintFlagsFinal -XX:+PrintGCDetails $JAVA_OPTIONS -jar $JAVA_APP_JAR 10 | -------------------------------------------------------------------------------- /hello/springboot/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Spring Boot Sample", 3 | "dockerFile": "Dockerfile", 4 | "appPort": "8080", 5 | "extensions": [ 6 | "vscjava.vscode-java-pack", 7 | "redhat.vscode-xml", 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /hello/springboot/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8u151 2 | ENV JAVA_APP_JAR boot-demo-1.0.0.jar 3 | WORKDIR /app/ 4 | COPY target/$JAVA_APP_JAR . 5 | EXPOSE 8080 6 | CMD java $JAVA_OPTIONS -jar $JAVA_APP_JAR 7 | -------------------------------------------------------------------------------- /hello/springboot/Dockerfile.openshift: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift 2 | WORKDIR /work/ 3 | ENV JAVA_APP_JAR boot-demo-1.0.0.jar 4 | # the following is not needed on this Red Hat created image 5 | # RUN chgrp -R 0 /work && \ 6 | # chmod -R g=u /work 7 | COPY target/$JAVA_APP_JAR . 8 | EXPOSE 8080 9 | USER 1001 10 | CMD java $JAVA_OPTIONS -jar $JAVA_APP_JAR 11 | -------------------------------------------------------------------------------- /hello/springboot/Dockerfile_Java11: -------------------------------------------------------------------------------- 1 | FROM openjdk:11 2 | ENV JAVA_APP_JAR boot-demo-1.0.0.jar 3 | WORKDIR /app/ 4 | COPY target/$JAVA_APP_JAR . 5 | EXPOSE 8080 6 | CMD java -jar $JAVA_APP_JAR 7 | -------------------------------------------------------------------------------- /hello/springboot/Dockerfile_Memory: -------------------------------------------------------------------------------- 1 | FROM openjdk:8u151 2 | ENV JAVA_APP_JAR boot-demo-1.0.0.jar 3 | WORKDIR /app/ 4 | COPY target/$JAVA_APP_JAR . 5 | EXPOSE 8080 6 | CMD java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:+PrintFlagsFinal -XX:+PrintGCDetails $JAVA_OPTIONS -jar $JAVA_APP_JAR 7 | -------------------------------------------------------------------------------- /hello/springboot/Dockerfile_Memory2: -------------------------------------------------------------------------------- 1 | FROM openjdk:8 2 | ENV JAVA_APP_JAR boot-demo-1.0.0.jar 3 | WORKDIR /app/ 4 | COPY target/$JAVA_APP_JAR . 5 | EXPOSE 8080 6 | CMD java -Xmx112M -XX:+PrintFlagsFinal -XX:+PrintGCDetails $JAVA_OPTIONS -jar $JAVA_APP_JAR 7 | -------------------------------------------------------------------------------- /hello/springboot/build_push_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IMAGE_VER=boot-demo:1.0.0 4 | 5 | docker build -f Dockerfile -t dev.local/burrsutter/$IMAGE_VER . 6 | docker login docker.io 7 | docker tag dev.local/burrsutter/$IMAGE_VER docker.io/burrsutter/$IMAGE_VER 8 | docker push docker.io/burrsutter/$IMAGE_VER 9 | -------------------------------------------------------------------------------- /hello/springboot/build_push_quay.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IMAGE_VER=boot-demo:1.0.0 4 | 5 | docker build -f Dockerfile -t dev.local/burrsutter/$IMAGE_VER . 6 | docker login quay.io 7 | docker tag dev.local/burrsutter/$IMAGE_VER quay.io/burrsutter/$IMAGE_VER 8 | docker push quay.io/burrsutter/$IMAGE_VER 9 | -------------------------------------------------------------------------------- /hello/springboot/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | com.burrsutter 5 | boot-demo 6 | 1.0.0 7 | jar 8 | 9 | helloboot 10 | Demo project for Spring Boot 11 | 12 | 13 | org.springframework.boot 14 | spring-boot-starter-parent 15 | 1.5.3.RELEASE 16 | 17 | 18 | 19 | 20 | UTF-8 21 | UTF-8 22 | 1.8 23 | 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-devtools 34 | runtime 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-test 39 | test 40 | 41 | 42 | 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-maven-plugin 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /hello/springboot/readme.txt: -------------------------------------------------------------------------------- 1 | Initial pom.xml created by start.spring.io 2 | 3 | mvn clean compile package 4 | 5 | java -jar target/boot-demo-1.0.0.jar 6 | or 7 | mvn spring-boot:run 8 | curl http://localhost:8080/ 9 | ctrl-c 10 | 11 | Manual Deployment 12 | 13 | export IMAGE_VER=boot-demo:1.0.0 14 | 15 | docker build -f Dockerfile -t dev.local/burrsutter/$IMAGE_VER . 16 | docker login docker.io 17 | docker tag dev.local/burrsutter/$IMAGE_VER docker.io/burrsutter/$IMAGE_VER 18 | docker push docker.io/burrsutter/$IMAGE_VER 19 | 20 | or 21 | 22 | docker build -f Dockerfile -t dev.local/burrsutter/$IMAGE_VER . 23 | docker login quay.io 24 | docker tag dev.local/burrsutter/$IMAGE_VER quay.io/burrsutter/$IMAGE_VER 25 | docker push quay.io/burrsutter/$IMAGE_VER 26 | 27 | or 28 | docker build -f Dockerfile.openshift -t dev.local/burrsutter/$IMAGE_VER . 29 | -------------------------------------------------------------------------------- /hello/springboot/src/main/java/com/burrsutter/HellobootApplication.java: -------------------------------------------------------------------------------- 1 | package com.burrsutter; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class HellobootApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(HellobootApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /hello/springboot/src/main/java/com/burrsutter/MyRESTController.java: -------------------------------------------------------------------------------- 1 | package com.burrsutter; 2 | 3 | import org.springframework.web.bind.annotation.RestController; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestMethod; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.core.env.Environment; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | @RestController 13 | public class MyRESTController { 14 | @Autowired 15 | private Environment environment; 16 | 17 | final String hostname = System.getenv().getOrDefault("HOSTNAME", "unknown"); 18 | String greeting; 19 | 20 | private int count = 0; // simple counter to see lifecycle 21 | 22 | RestTemplate restTemplate = new RestTemplate(); 23 | 24 | @RequestMapping("/") 25 | public String sayHello() { 26 | greeting = environment.getProperty("GREETING","Hej"); 27 | count++; 28 | System.out.println(greeting + " from " + hostname + " " + count); 29 | return greeting + " from Spring Boot! " + count + " on " + hostname + "\n"; 30 | } 31 | 32 | @RequestMapping("/sysresources") 33 | public String getSystemResources() { 34 | long memory = Runtime.getRuntime().maxMemory(); 35 | int cores = Runtime.getRuntime().availableProcessors(); 36 | System.out.println("/sysresources " + hostname); 37 | return 38 | " Memory: " + (memory / 1024 / 1024) + 39 | " Cores: " + cores + "\n"; 40 | } 41 | 42 | @RequestMapping("/consume") 43 | public String consumeSome() { 44 | System.out.println("/consume " + hostname); 45 | 46 | Runtime rt = Runtime.getRuntime(); 47 | StringBuilder sb = new StringBuilder(); 48 | long maxMemory = rt.maxMemory(); 49 | long usedMemory = 0; 50 | // while usedMemory is less than 80% of Max 51 | while (((float) usedMemory / maxMemory) < 0.80) { 52 | sb.append(System.nanoTime() + sb.toString()); 53 | usedMemory = rt.totalMemory(); 54 | } 55 | String msg = "Allocated about 80% (" + humanReadableByteCount(usedMemory, false) + ") of the max allowed JVM memory size (" 56 | + humanReadableByteCount(maxMemory, false) + ")"; 57 | System.out.println(msg); 58 | return msg + "\n"; 59 | } 60 | 61 | @RequestMapping(method = RequestMethod.GET, value = "/health") 62 | public ResponseEntity health() { 63 | // if (count++ < 5) { 64 | // return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("Bad"); 65 | // } else { 66 | return ResponseEntity.status(HttpStatus.OK) 67 | .body("I am fine, thank you\n"); 68 | // } 69 | } 70 | 71 | @RequestMapping("/configure") 72 | public String configure() { 73 | String databaseConn = environment.getProperty("DBCONN","Default"); 74 | String msgBroker = environment.getProperty("MSGBROKER","Default"); 75 | greeting = environment.getProperty("GREETING","Default"); 76 | String love = environment.getProperty("LOVE","Default"); 77 | return "Configuration for : " + hostname + "\n" 78 | + "databaseConn=" + databaseConn + "\n" 79 | + "msgBroker=" + msgBroker + "\n" 80 | + "greeting=" + greeting + "\n" 81 | + "love=" + love + "\n"; 82 | } 83 | 84 | @RequestMapping("/callinganother") 85 | public String callinganother() { 86 | 87 | // ..svc.cluster.local 88 | String url = "http://mynode.yourspace.svc.cluster.local:8000/"; 89 | 90 | ResponseEntity response 91 | = restTemplate.getForEntity(url, String.class); 92 | 93 | String responseBody = response.getBody(); 94 | System.out.println(responseBody); 95 | 96 | return responseBody; 97 | } 98 | 99 | public static String humanReadableByteCount(long bytes, boolean si) { 100 | int unit = si ? 1000 : 1024; 101 | if (bytes < unit) 102 | return bytes + " B"; 103 | int exp = (int) (Math.log(bytes) / Math.log(unit)); 104 | String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); 105 | return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /hello/springbootjib/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.burrsutter 7 | boot-demo 8 | 0.0.1 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 2.0.4.RELEASE 14 | 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-web 20 | 21 | 22 | 23 | 24 | 1.8 25 | 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-maven-plugin 32 | 33 | 34 | 35 | com.google.cloud.tools 36 | jib-maven-plugin 37 | 1.6.1 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /hello/springbootjib/readme.txt: -------------------------------------------------------------------------------- 1 | This example builds a linux container image, without a Dockerfile, via jib 2 | 3 | https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#example 4 | 5 | 6 | eval $(minishift docker-env) 7 | 8 | mvn compile jib:dockerBuild -Dimage=9stepsawesome/myboot:v1 9 | 10 | docker run -it -p 8080:8080 9stepsawesome/myboot:v1 11 | 12 | curl $(minishift ip):8080 13 | 14 | -------------------------------------------------------------------------------- /hello/springbootjib/src/main/java/com/burrsutter/HellobootApplication.java: -------------------------------------------------------------------------------- 1 | package com.burrsutter; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class HellobootApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(HellobootApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /hello/springbootjib/src/main/java/com/burrsutter/MyRESTController.java: -------------------------------------------------------------------------------- 1 | package com.burrsutter; 2 | 3 | import org.springframework.web.bind.annotation.RestController; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestMethod; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.core.env.Environment; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | @RestController 13 | public class MyRESTController { 14 | @Autowired 15 | private Environment environment; 16 | 17 | final String hostname = System.getenv().getOrDefault("HOSTNAME", "unknown"); 18 | String greeting = "Aloha"; 19 | 20 | private int count = 0; // simple counter to see lifecycle 21 | 22 | RestTemplate restTemplate = new RestTemplate(); 23 | 24 | @RequestMapping("/") 25 | public String sayHello() { 26 | greeting = environment.getProperty("GREETING","Merhaba"); 27 | System.out.println(greeting + " from " + hostname); 28 | return greeting + " from Spring Boot! " + count++ + " on " + hostname + "\n"; 29 | } 30 | 31 | @RequestMapping("/sysresources") 32 | public String getSystemResources() { 33 | long memory = Runtime.getRuntime().maxMemory(); 34 | int cores = Runtime.getRuntime().availableProcessors(); 35 | System.out.println("/sysresources " + hostname); 36 | return 37 | " Memory: " + (memory / 1024 / 1024) + 38 | " Cores: " + cores + "\n"; 39 | } 40 | 41 | @RequestMapping("/consume") 42 | public String consumeSome() { 43 | System.out.println("/consume " + hostname); 44 | 45 | Runtime rt = Runtime.getRuntime(); 46 | StringBuilder sb = new StringBuilder(); 47 | long maxMemory = rt.maxMemory(); 48 | long usedMemory = 0; 49 | // while usedMemory is less than 80% of Max 50 | while (((float) usedMemory / maxMemory) < 0.80) { 51 | sb.append(System.nanoTime() + sb.toString()); 52 | usedMemory = rt.totalMemory(); 53 | } 54 | String msg = "Allocated about 80% (" + humanReadableByteCount(usedMemory, false) + ") of the max allowed JVM memory size (" 55 | + humanReadableByteCount(maxMemory, false) + ")"; 56 | System.out.println(msg); 57 | return msg + "\n"; 58 | } 59 | 60 | @RequestMapping(method = RequestMethod.GET, value = "/health") 61 | public ResponseEntity health() { 62 | // if (count++ < 5) { 63 | // return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("Bad"); 64 | // } else { 65 | return ResponseEntity.status(HttpStatus.OK) 66 | .body("I am fine, thank you\n"); 67 | // } 68 | } 69 | 70 | @RequestMapping("/configure") 71 | public String configure() { 72 | String databaseConn = environment.getProperty("DBCONN","Default"); 73 | String msgBroker = environment.getProperty("MSGBROKER","Default"); 74 | greeting = environment.getProperty("GREETING","Default"); 75 | String love = environment.getProperty("LOVE","Default"); 76 | return "Configuration for : " + hostname + "\n" 77 | + "databaseConn=" + databaseConn + "\n" 78 | + "msgBroker=" + msgBroker + "\n" 79 | + "greeting=" + greeting + "\n" 80 | + "love=" + love + "\n"; 81 | } 82 | 83 | @RequestMapping("/callinganother") 84 | public String callinganother() { 85 | 86 | // ..svc.cluster.local 87 | String url = "http://mynode.yourspace.svc.cluster.local:8000/"; 88 | 89 | ResponseEntity response 90 | = restTemplate.getForEntity(url, String.class); 91 | 92 | String responseBody = response.getBody(); 93 | System.out.println(responseBody); 94 | 95 | return responseBody; 96 | } 97 | 98 | public static String humanReadableByteCount(long bytes, boolean si) { 99 | int unit = si ? 1000 : 1024; 100 | if (bytes < unit) 101 | return bytes + " B"; 102 | int exp = (int) (Math.log(bytes) / Math.log(unit)); 103 | String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); 104 | return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); 105 | } 106 | 107 | } -------------------------------------------------------------------------------- /hello/springbootjshift/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.burrsutter 6 | boot-demo 7 | 0.0.1 8 | 9 | 10 | org.springframework.boot 11 | spring-boot-starter-parent 12 | 2.0.4.RELEASE 13 | 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-web 19 | 20 | 21 | 22 | 23 | 1.8 24 | 25 | 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-maven-plugin 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | kubernetes 41 | 42 | 43 | 44 | io.jshift 45 | k8s-maven-plugin 46 | 0.1.0 47 | 48 | 49 | 50 | 51 | 52 | 53 | spring-boot-sample 54 | 55 | 56 | 57 | 58 | 59 | 60 | spring-boot 61 | 62 | 63 | 64 | always 65 | 66 | 67 | 68 | 69 | 70 | jshift-expose 71 | 72 | 73 | 74 | NodePort 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | resource 84 | build 85 | helm 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | openshift 95 | 96 | 97 | 98 | io.jshift 99 | oc-maven-plugin 100 | 0.1.0 101 | 102 | 103 | 104 | 105 | 106 | 107 | spring-boot-sample 108 | 109 | 110 | 111 | 112 | 113 | 114 | spring-boot 115 | 116 | 117 | 118 | always 119 | 120 | 121 | 122 | 123 | 124 | jshift-expose 125 | 126 | 127 | 128 | NodePort 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | resource 138 | build 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /hello/springbootjshift/readme.adoc: -------------------------------------------------------------------------------- 1 | = jshift 2 | 3 | http://jshift.io;[jshift.io] 4 | 5 | This example builds a linux container image, without a Dockerfile. And it generates the Kubernetes manifests and deploys your app into minikube 6 | 7 | === Connect to the minikube docker daemon 8 | ---- 9 | $ eval $(minikube --profile 9steps docker-env) 10 | ---- 11 | 12 | === Generates the Kube manifests 13 | ---- 14 | $ mvn clean k8s:resource -Pkubernetes 15 | $ ls target/classes/META-INF/jshift 16 | $ cat target/classes/META-INF/jshift/kubernetes.yml 17 | ---- 18 | 19 | === Build artifacts 20 | ---- 21 | $ mvn package k8s:build -Pkubernetes 22 | $ ls target/boot-demo-0.0.1.jar 23 | $ java -jar target/boot-demo-0.0.1.jar 24 | $ curl localhost:8080 25 | $ docker images | grep boot-demo 26 | ---- 27 | 28 | === Deploy to minikube 29 | ---- 30 | $ mvn k8s:deploy -Pkubernetes 31 | ---- 32 | 33 | === Talk to it 34 | ---- 35 | $ IP=$(minikube -p 9steps ip) 36 | $ NODEPORT=$(kubectl get service/boot-demo -o jsonpath="{.spec.ports[*].nodePort}") 37 | 38 | $ curl $IP:$NODEPORT 39 | ---- 40 | 41 | === Clean up 42 | ---- 43 | $ mvn k8s:undeploy -Pkubernetes 44 | ---- -------------------------------------------------------------------------------- /hello/springbootjshift/src/main/java/com/burrsutter/HellobootApplication.java: -------------------------------------------------------------------------------- 1 | package com.burrsutter; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class HellobootApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(HellobootApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /hello/springbootjshift/src/main/java/com/burrsutter/MyRESTController.java: -------------------------------------------------------------------------------- 1 | package com.burrsutter; 2 | 3 | import org.springframework.web.bind.annotation.RestController; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestMethod; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.core.env.Environment; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | @RestController 13 | public class MyRESTController { 14 | @Autowired 15 | private Environment environment; 16 | 17 | final String hostname = System.getenv().getOrDefault("HOSTNAME", "unknown"); 18 | String greeting = "Aloha"; 19 | 20 | private int count = 0; // simple counter to see lifecycle 21 | 22 | RestTemplate restTemplate = new RestTemplate(); 23 | 24 | @RequestMapping("/") 25 | public String sayHello() { 26 | greeting = environment.getProperty("GREETING","Merhaba"); 27 | System.out.println(greeting + " from " + hostname); 28 | return greeting + " from Spring Boot! " + count++ + " on " + hostname + "\n"; 29 | } 30 | 31 | @RequestMapping("/sysresources") 32 | public String getSystemResources() { 33 | long memory = Runtime.getRuntime().maxMemory(); 34 | int cores = Runtime.getRuntime().availableProcessors(); 35 | System.out.println("/sysresources " + hostname); 36 | return 37 | " Memory: " + (memory / 1024 / 1024) + 38 | " Cores: " + cores + "\n"; 39 | } 40 | 41 | @RequestMapping("/consume") 42 | public String consumeSome() { 43 | System.out.println("/consume " + hostname); 44 | 45 | Runtime rt = Runtime.getRuntime(); 46 | StringBuilder sb = new StringBuilder(); 47 | long maxMemory = rt.maxMemory(); 48 | long usedMemory = 0; 49 | // while usedMemory is less than 80% of Max 50 | while (((float) usedMemory / maxMemory) < 0.80) { 51 | sb.append(System.nanoTime() + sb.toString()); 52 | usedMemory = rt.totalMemory(); 53 | } 54 | String msg = "Allocated about 80% (" + humanReadableByteCount(usedMemory, false) + ") of the max allowed JVM memory size (" 55 | + humanReadableByteCount(maxMemory, false) + ")"; 56 | System.out.println(msg); 57 | return msg + "\n"; 58 | } 59 | 60 | @RequestMapping(method = RequestMethod.GET, value = "/health") 61 | public ResponseEntity health() { 62 | // if (count++ < 5) { 63 | // return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("Bad"); 64 | // } else { 65 | return ResponseEntity.status(HttpStatus.OK) 66 | .body("I am fine, thank you\n"); 67 | // } 68 | } 69 | 70 | @RequestMapping("/configure") 71 | public String configure() { 72 | String databaseConn = environment.getProperty("DBCONN","Default"); 73 | String msgBroker = environment.getProperty("MSGBROKER","Default"); 74 | greeting = environment.getProperty("GREETING","Default"); 75 | String love = environment.getProperty("LOVE","Default"); 76 | return "Configuration for : " + hostname + "\n" 77 | + "databaseConn=" + databaseConn + "\n" 78 | + "msgBroker=" + msgBroker + "\n" 79 | + "greeting=" + greeting + "\n" 80 | + "love=" + love + "\n"; 81 | } 82 | 83 | @RequestMapping("/callinganother") 84 | public String callinganother() { 85 | 86 | // ..svc.cluster.local 87 | String url = "http://mynode.yourspace.svc.cluster.local:8000/"; 88 | 89 | ResponseEntity response 90 | = restTemplate.getForEntity(url, String.class); 91 | 92 | String responseBody = response.getBody(); 93 | System.out.println(responseBody); 94 | 95 | return responseBody; 96 | } 97 | 98 | public static String humanReadableByteCount(long bytes, boolean si) { 99 | int unit = si ? 1000 : 1024; 100 | if (bytes < unit) 101 | return bytes + " B"; 102 | int exp = (int) (Math.log(bytes) / Math.log(unit)); 103 | String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); 104 | return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); 105 | } 106 | 107 | } -------------------------------------------------------------------------------- /hello/vertx/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.burrsutter 4 | vertx-demo 5 | 1.0-SNAPSHOT 6 | 7 | 3.4.2 8 | com.burrsutter.MainVerticle 9 | 1.0.7 10 | 1.8 11 | UTF-8 12 | 1.8 13 | 14 | 15 | 16 | 17 | io.vertx 18 | vertx-dependencies 19 | ${vertx.version} 20 | pom 21 | import 22 | 23 | 24 | 25 | 26 | 27 | io.vertx 28 | vertx-core 29 | 30 | 31 | io.vertx 32 | vertx-web 33 | 34 | 35 | 36 | 37 | 38 | io.fabric8 39 | vertx-maven-plugin 40 | ${fabric8-vertx-maven-plugin.version} 41 | 42 | 43 | vmp 44 | 45 | initialize 46 | package 47 | 48 | 49 | 50 | 51 | true 52 | 53 | 54 | 55 | io.fabric8 56 | fabric8-maven-plugin 57 | 3.3.5 58 | 59 | 60 | fmp 61 | 62 | resource 63 | helm 64 | build 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /hello/vertx/readme.txt: -------------------------------------------------------------------------------- 1 | Initial pom.xml created by 2 | mvn io.fabric8:vertx-maven-plugin:1.0.7:setup -DvertxVersion=3.4.2 -Ddependencies=web 3 | 4 | Assumes use of CDK Minishift 5 | https://developers.redhat.com/products/cdk/download/ 6 | 7 | mvn clean compile package 8 | 9 | java -jar target/vertx-demo-1.0-SNAPSHOT.jar 10 | or 11 | mvn vertx:run 12 | http://localhost:8080/hello 13 | ctrl-c 14 | 15 | minishift ip 16 | minishift oc-env 17 | 18 | oc login 192.168.99.101:8443 19 | admin 20 | admin 21 | 22 | (note: the admin user is created by the CDK version of Minishift only) 23 | 24 | oc new-project vertxdemo 25 | 26 | Note: mvn io.fabric8:fabric8-maven-plugin:3.3.5:setup 27 | was executed to instrument the pom.xml with the F8 Maven plugin 28 | 29 | mvn fabric8:deploy 30 | 31 | http://vertx-demo-vertxdemo.192.168.99.101.nip.io/hello 32 | 33 | 34 | -------------------------------------------------------------------------------- /hello/vertx/src/main/java/com/burrsutter/MainVerticle.java: -------------------------------------------------------------------------------- 1 | package com.burrsutter; 2 | 3 | import io.vertx.core.AbstractVerticle; 4 | import io.vertx.ext.web.Router; 5 | public class MainVerticle extends AbstractVerticle { 6 | final String hostname = System.getenv().getOrDefault("HOSTNAME", "unknown"); 7 | @Override 8 | public void start() { 9 | Router router = Router.router(vertx); 10 | router.get("/hello").handler(request -> { 11 | request.response().end("Hello from Vert.x! " + new java.util.Date() + " on " + hostname); 12 | }); 13 | 14 | vertx.createHttpServer().requestHandler(router::accept) 15 | .listen(8080); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /hellodata/boot_postgres/.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8 2 | ENV JAVA_APP_JAR boot-postgres-0.0.1.jar 3 | WORKDIR /app/ 4 | 5 | RUN apt-get update && apt-get install -y maven 6 | 7 | EXPOSE 8080 8 | CMD java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap $JAVA_OPTIONS -jar $JAVA_APP_JAR 9 | -------------------------------------------------------------------------------- /hellodata/boot_postgres/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Spring Boot Postgres Sample", 3 | "dockerFile": "Dockerfile", 4 | "appPort": "8080", 5 | "extensions": [ 6 | "vscjava.vscode-java-pack", 7 | "redhat.vscode-xml", 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /hellodata/boot_postgres/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /hellodata/boot_postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8 2 | ENV JAVA_APP_JAR boot-postgres-0.0.1.jar 3 | WORKDIR /app/ 4 | COPY target/$JAVA_APP_JAR . 5 | EXPOSE 8080 6 | CMD java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap $JAVA_OPTIONS -jar $JAVA_APP_JAR 7 | -------------------------------------------------------------------------------- /hellodata/boot_postgres/anotherquestion.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "How to shave my yak?", 3 | "description": "My boss has asked for daily TPS reports, how can I convince him this is simply yak shaving?" 4 | } -------------------------------------------------------------------------------- /hellodata/boot_postgres/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /hellodata/boot_postgres/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /hellodata/boot_postgres/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.burrsutter 7 | boot-postgres 8 | 0.0.1 9 | jar 10 | 11 | boot-postgres 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.2.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-actuator 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-data-jpa 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-thymeleaf 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-web 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-devtools 48 | runtime 49 | 50 | 51 | org.postgresql 52 | postgresql 53 | runtime 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-test 58 | test 59 | 60 | 61 | 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-maven-plugin 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /hellodata/boot_postgres/readme.adoc: -------------------------------------------------------------------------------- 1 | CREATE ROLE myuser WITH LOGIN PASSWORD 'mypassword'; 2 | ALTER ROLE myuser CREATEDB; 3 | CREATE DATABASE mydb; 4 | 5 | SELECT * FROM pg_catalog.pg_tables; 6 | 7 | start.spring.io 8 | Artifact: boot_progress 9 | Deps: Web, JPA, Actuator, postgresql, Thymeleaf, DevTools 10 | 11 | Based on 12 | https://www.callicoder.com/spring-boot-jpa-hibernate-postgresql-restful-crud-api-example/ 13 | 14 | mvn spring-boot:run 15 | 16 | curl -X POST http://localhost:8080/questions -d @testquestion.json --header "Content-Type: application/json" 17 | 18 | curl http://localhost:8080/questions 19 | 20 | curl http://localhost:8080/questions/1000 21 | 22 | curl -X DELETE http://localhost:8080/questions/1000 23 | 24 | 25 | -------------------------------------------------------------------------------- /hellodata/boot_postgres/src/main/java/com/example/boot_postgres/BootPostgresApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.boot_postgres; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 6 | 7 | @SpringBootApplication 8 | @EnableJpaAuditing 9 | public class BootPostgresApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(BootPostgresApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /hellodata/boot_postgres/src/main/java/com/example/boot_postgres/controller/AnswerController.java: -------------------------------------------------------------------------------- 1 | package com.example.boot_postgres.controller; 2 | 3 | import com.example.boot_postgres.repository.AnswerRepository; 4 | import com.example.boot_postgres.repository.QuestionRepository; 5 | import com.example.boot_postgres.exception.ResourceNotFoundException; 6 | import com.example.boot_postgres.model.Answer; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.PutMapping; 12 | import org.springframework.web.bind.annotation.DeleteMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.RestController; 16 | import org.springframework.http.ResponseEntity; 17 | 18 | import javax.validation.Valid; 19 | import java.util.List; 20 | 21 | @RestController 22 | public class AnswerController { 23 | @Autowired 24 | private AnswerRepository answerRepository; 25 | 26 | @Autowired 27 | private QuestionRepository questionRepository; 28 | 29 | @GetMapping("/questions/{questionId}/answers") 30 | public List getAnswersByQuestionId(@PathVariable Long questionId) { 31 | return answerRepository.findByQuestionId(questionId); 32 | } 33 | 34 | @PostMapping("/questions/{questionId/answers") 35 | public Answer addAnswer(@PathVariable Long questionId, 36 | @Valid @RequestBody Answer answer) { 37 | return questionRepository.findById(questionId) 38 | .map(question -> { 39 | answer.setQuestion(question); 40 | return answerRepository.save(answer); 41 | }).orElseThrow(() -> new ResourceNotFoundException("Question not found for id " + questionId)); 42 | } 43 | 44 | @PutMapping("/questions/{questionId}/answers/{answerId}") 45 | public Answer updateAnswer(@PathVariable Long questionId, 46 | @PathVariable Long answerId, 47 | @Valid @RequestBody Answer answerRequest) { 48 | if(!questionRepository.existsById(questionId)) { 49 | throw new ResourceNotFoundException("Question not found for id " + questionId); 50 | } 51 | return answerRepository.findById(answerId) 52 | .map(answer -> { 53 | answer.setText(answerRequest.getText()); 54 | return answerRepository.save(answer); 55 | }).orElseThrow(()-> new ResourceNotFoundException("Answer not found for id " + answerId)); 56 | } 57 | 58 | @DeleteMapping("/questions/{questionId}/answers/{answerId}") 59 | public ResponseEntity deleteAnswer(@PathVariable Long questionId, 60 | @PathVariable Long answerId) { 61 | if(!questionRepository.existsById(questionId)) { 62 | throw new ResourceNotFoundException("Question not found with id " + questionId); 63 | } 64 | 65 | return answerRepository.findById(answerId) 66 | .map(answer -> { 67 | answerRepository.delete(answer); 68 | return ResponseEntity.ok().build(); 69 | }).orElseThrow(() -> new ResourceNotFoundException("Answer not found with id " + answerId)); 70 | 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /hellodata/boot_postgres/src/main/java/com/example/boot_postgres/controller/QuestionController.java: -------------------------------------------------------------------------------- 1 | package com.example.boot_postgres.controller; 2 | 3 | import com.example.boot_postgres.repository.QuestionRepository; 4 | import com.example.boot_postgres.model.Question; 5 | import com.example.boot_postgres.exception.ResourceNotFoundException; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.PutMapping; 12 | import org.springframework.web.bind.annotation.DeleteMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RestController; 15 | import org.springframework.data.domain.Page; 16 | import org.springframework.data.domain.Pageable; 17 | import org.springframework.http.ResponseEntity; 18 | 19 | import javax.validation.Valid; 20 | 21 | @RestController 22 | public class QuestionController { 23 | 24 | @Autowired 25 | private QuestionRepository questionRepository; 26 | 27 | @GetMapping("/questions") 28 | public Page getQuestions(Pageable pageable) { 29 | return questionRepository.findAll(pageable); 30 | } 31 | 32 | @GetMapping("/questions/{questionId}") 33 | public Question getQuestion(@PathVariable Long questionId) { 34 | return questionRepository.findById(questionId) 35 | .map(question -> { 36 | return question; 37 | }).orElseThrow(() -> new ResourceNotFoundException("YOUR Question not found with id " + questionId)); 38 | 39 | } 40 | @PostMapping("/questions") 41 | public Question createQuestion(@Valid @RequestBody Question question) { 42 | return questionRepository.save(question); 43 | } 44 | 45 | @PutMapping("/questions/{questionId}") 46 | public Question updateQuestion(@PathVariable Long questionId, 47 | @Valid @RequestBody Question questionRequest) { 48 | return questionRepository.findById(questionId) 49 | .map(question -> { 50 | question.setTitle(questionRequest.getTitle()); 51 | question.setDescription(questionRequest.getDescription()); 52 | return questionRepository.save(question); 53 | }).orElseThrow(() -> new ResourceNotFoundException("Question not found with id " + questionId)); 54 | } 55 | 56 | @DeleteMapping("/questions/{questionId}") 57 | public ResponseEntity deleteQuestion(@PathVariable Long questionId) { 58 | return questionRepository.findById(questionId) 59 | .map(question -> { 60 | questionRepository.delete(question); 61 | return ResponseEntity.ok().build(); 62 | }).orElseThrow(() -> new ResourceNotFoundException("Question not found with id " + questionId)); 63 | } 64 | } -------------------------------------------------------------------------------- /hellodata/boot_postgres/src/main/java/com/example/boot_postgres/exception/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.example.boot_postgres.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class ResourceNotFoundException extends RuntimeException { 8 | public ResourceNotFoundException(String message) { 9 | super(message); 10 | } 11 | 12 | public ResourceNotFoundException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | } -------------------------------------------------------------------------------- /hellodata/boot_postgres/src/main/java/com/example/boot_postgres/model/Answer.java: -------------------------------------------------------------------------------- 1 | package com.example.boot_postgres.model; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.Id; 6 | import javax.persistence.JoinColumn; 7 | import javax.persistence.ManyToOne; 8 | import javax.persistence.FetchType; 9 | import javax.persistence.Column; 10 | import javax.persistence.SequenceGenerator; 11 | import javax.persistence.Table; 12 | 13 | import com.fasterxml.jackson.annotation.JsonIgnore; 14 | 15 | import org.hibernate.annotations.OnDelete; 16 | import org.hibernate.annotations.OnDeleteAction; 17 | 18 | @Entity 19 | @Table(name = "answers") 20 | public class Answer extends AuditModel { 21 | @Id 22 | @GeneratedValue(generator = "answer_generator") 23 | @SequenceGenerator( 24 | name = "answer_generator", 25 | sequenceName = "answer_sequence", 26 | initialValue = 1000 27 | ) 28 | private Long id; 29 | 30 | @Column(columnDefinition = "text") 31 | private String text; 32 | 33 | @ManyToOne(fetch = FetchType.LAZY) 34 | @JoinColumn(name = "question_id", nullable = false) 35 | @OnDelete(action = OnDeleteAction.CASCADE) 36 | @JsonIgnore 37 | private Question question; 38 | 39 | public Long getId() { 40 | return id; 41 | } 42 | 43 | public void setId(Long id) { 44 | this.id = id; 45 | } 46 | 47 | public String getText() { 48 | return text; 49 | } 50 | 51 | public void setText(String text) { 52 | this.text = text; 53 | } 54 | 55 | public Question getQuestion() { 56 | return question; 57 | } 58 | 59 | public void setQuestion(Question question) { 60 | this.question = question; 61 | } 62 | } -------------------------------------------------------------------------------- /hellodata/boot_postgres/src/main/java/com/example/boot_postgres/model/AuditModel.java: -------------------------------------------------------------------------------- 1 | package com.example.boot_postgres.model; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | import javax.persistence.EntityListeners; 7 | import javax.persistence.MappedSuperclass; 8 | import javax.persistence.Temporal; 9 | import javax.persistence.TemporalType; 10 | import javax.persistence.Column; 11 | 12 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 13 | 14 | import org.springframework.data.annotation.CreatedDate; 15 | import org.springframework.data.annotation.LastModifiedDate; 16 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 17 | 18 | @MappedSuperclass 19 | @EntityListeners(AuditingEntityListener.class) 20 | @JsonIgnoreProperties( 21 | value = {"createdAt", "updatedAt"}, 22 | allowGetters = true 23 | ) 24 | public abstract class AuditModel implements Serializable { 25 | @Temporal(TemporalType.TIMESTAMP) 26 | @Column(name="created_at", nullable=false, updatable=false) 27 | @CreatedDate 28 | private Date createdAt; 29 | 30 | @Temporal(TemporalType.TIMESTAMP) 31 | @Column(name ="updated_at", nullable=false) 32 | @LastModifiedDate 33 | private Date updatedAt; 34 | 35 | public Date getCreatedAt() { 36 | return createdAt; 37 | } 38 | public void setCreatedAt(Date createdAt) { 39 | this.createdAt = createdAt; 40 | } 41 | public Date getUpdatedAt() { 42 | return updatedAt; 43 | } 44 | public void setUpdatedAt(Date updatedAt) { 45 | this.updatedAt = updatedAt; 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /hellodata/boot_postgres/src/main/java/com/example/boot_postgres/model/Question.java: -------------------------------------------------------------------------------- 1 | package com.example.boot_postgres.model; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.Id; 7 | import javax.persistence.SequenceGenerator; 8 | import javax.persistence.Table; 9 | import javax.validation.constraints.NotBlank; 10 | import javax.validation.constraints.Size;; 11 | 12 | @Entity 13 | @Table(name="questions") 14 | public class Question extends AuditModel { 15 | @Id 16 | @GeneratedValue(generator = "question_generator") 17 | @SequenceGenerator( 18 | name = "question_generator", 19 | sequenceName = "question_sequence", 20 | initialValue = 1000 21 | ) 22 | private Long id; 23 | 24 | @NotBlank 25 | @Size(min =3, max = 100) 26 | private String title; 27 | 28 | @Column(columnDefinition = "text") 29 | private String description; 30 | 31 | public Long getId() { 32 | return id; 33 | } 34 | 35 | public void setId(Long id) { 36 | this.id = id; 37 | } 38 | 39 | public String getTitle() { 40 | return title; 41 | } 42 | 43 | public void setTitle(String title) { 44 | this.title = title; 45 | } 46 | 47 | public String getDescription() { 48 | return description; 49 | } 50 | 51 | public void setDescription(String description) { 52 | this.description = description; 53 | } 54 | } -------------------------------------------------------------------------------- /hellodata/boot_postgres/src/main/java/com/example/boot_postgres/repository/AnswerRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.boot_postgres.repository; 2 | 3 | import com.example.boot_postgres.model.Answer; 4 | 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.List; 9 | 10 | @Repository 11 | public interface AnswerRepository extends JpaRepository { 12 | List findByQuestionId(Long questionId); 13 | } -------------------------------------------------------------------------------- /hellodata/boot_postgres/src/main/java/com/example/boot_postgres/repository/QuestionRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.boot_postgres.repository; 2 | 3 | import com.example.boot_postgres.model.Question; 4 | 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | @Repository 9 | public interface QuestionRepository extends JpaRepository { 10 | 11 | } -------------------------------------------------------------------------------- /hellodata/boot_postgres/src/main/resources/application-local-container.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:postgresql://docker.for.mac.localhost:5432/mydb 2 | spring.datasource.username=myuser 3 | spring.datasource.password=mypassword 4 | 5 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect 6 | spring.jpa.hibernate.ddl-auto=update 7 | spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false -------------------------------------------------------------------------------- /hellodata/boot_postgres/src/main/resources/application-local.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:postgresql://localhost:5433/mydb 2 | spring.datasource.username=myuser 3 | spring.datasource.password=mypassword 4 | 5 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect 6 | spring.jpa.hibernate.ddl-auto=update 7 | spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false -------------------------------------------------------------------------------- /hellodata/boot_postgres/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:postgresql://postgres:5432/mydb 2 | spring.datasource.username=myuser 3 | spring.datasource.password=mypassword 4 | 5 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect 6 | spring.jpa.hibernate.ddl-auto=update -------------------------------------------------------------------------------- /hellodata/boot_postgres/src/test/java/com/example/boot_postgres/BootPostgresApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.boot_postgres; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class BootPostgresApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /hellodata/boot_postgres/testquestion.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "How to walk my dog?", 3 | "description": "I need to walk my dog on a regular basis, the dog is about 20 pounds and has rather short legs. She is always eager to go but often I am too busy or distracted. What are some strategies for insuring the dog gets walked more often?" 4 | } -------------------------------------------------------------------------------- /images/chrome_rest_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burrsutter/9stepsawesome/4974e4fe11b438a489a947a339ec5b105273111f/images/chrome_rest_api.png -------------------------------------------------------------------------------- /images/minikube_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burrsutter/9stepsawesome/4974e4fe11b438a489a947a339ec5b105273111f/images/minikube_dashboard.png -------------------------------------------------------------------------------- /images/openshift_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burrsutter/9stepsawesome/4974e4fe11b438a489a947a339ec5b105273111f/images/openshift_dashboard.png -------------------------------------------------------------------------------- /images/pgadmin_add_question.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burrsutter/9stepsawesome/4974e4fe11b438a489a947a339ec5b105273111f/images/pgadmin_add_question.png -------------------------------------------------------------------------------- /images/pgadmin_add_server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burrsutter/9stepsawesome/4974e4fe11b438a489a947a339ec5b105273111f/images/pgadmin_add_server.png -------------------------------------------------------------------------------- /images/pgadmin_add_server2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burrsutter/9stepsawesome/4974e4fe11b438a489a947a339ec5b105273111f/images/pgadmin_add_server2.png -------------------------------------------------------------------------------- /images/pgadmin_query_questions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burrsutter/9stepsawesome/4974e4fe11b438a489a947a339ec5b105273111f/images/pgadmin_query_questions.png -------------------------------------------------------------------------------- /images/pgadmin_query_questions1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burrsutter/9stepsawesome/4974e4fe11b438a489a947a339ec5b105273111f/images/pgadmin_query_questions1.png -------------------------------------------------------------------------------- /images/pgadmin_schema_creation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burrsutter/9stepsawesome/4974e4fe11b438a489a947a339ec5b105273111f/images/pgadmin_schema_creation.png -------------------------------------------------------------------------------- /images/stern_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burrsutter/9stepsawesome/4974e4fe11b438a489a947a339ec5b105273111f/images/stern_output.png -------------------------------------------------------------------------------- /images/virtualbox_ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burrsutter/9stepsawesome/4974e4fe11b438a489a947a339ec5b105273111f/images/virtualbox_ui.png -------------------------------------------------------------------------------- /kubefiles/kafka-strimzi-minikube.yml: -------------------------------------------------------------------------------- 1 | apiVersion: kafka.strimzi.io/v1alpha1 2 | kind: Kafka 3 | metadata: 4 | name: burr-cluster 5 | spec: 6 | kafka: 7 | replicas: 3 8 | listeners: 9 | external: 10 | type: nodeport 11 | storage: 12 | type: ephemeral 13 | zookeeper: 14 | replicas: 3 15 | storage: 16 | type: ephemeral 17 | entityOperator: 18 | topicOperator: {} -------------------------------------------------------------------------------- /kubefiles/my-service-8080.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: myservice 5 | labels: 6 | app: mystuff 7 | spec: 8 | ports: 9 | - name: http 10 | port: 8080 11 | selector: 12 | run: myhellojava 13 | type: LoadBalancer 14 | -------------------------------------------------------------------------------- /kubefiles/my-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: my-service 5 | labels: 6 | app: mystuff 7 | spec: 8 | ports: 9 | - name: http 10 | port: 8000 11 | selector: 12 | inservice: mypods 13 | type: LoadBalancer -------------------------------------------------------------------------------- /kubefiles/myboot-deployment-canary.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: mybootcanary 6 | name: mybootcanary 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: mybootcanary 12 | template: 13 | metadata: 14 | labels: 15 | app: mybootcanary 16 | spec: 17 | containers: 18 | - name: mybootcanary 19 | image: 9stepsawesome/myboot:v3 20 | ports: 21 | - containerPort: 8080 22 | envFrom: 23 | - configMapRef: 24 | name: my-config 25 | resources: 26 | requests: 27 | memory: "300Mi" 28 | cpu: "250m" # 1/4 core 29 | limits: 30 | memory: "400Mi" 31 | cpu: "1000m" # 1 core 32 | livenessProbe: 33 | httpGet: 34 | port: http 35 | path: / 36 | initialDelaySeconds: 10 37 | periodSeconds: 5 38 | timeoutSeconds: 2 39 | readinessProbe: 40 | httpGet: 41 | path: /health 42 | port: 8080 43 | initialDelaySeconds: 10 44 | periodSeconds: 3 45 | 46 | -------------------------------------------------------------------------------- /kubefiles/myboot-deployment-configuration-secret.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: myboot 6 | name: myboot 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: myboot 12 | template: 13 | metadata: 14 | labels: 15 | app: myboot 16 | spec: 17 | containers: 18 | - name: myboot 19 | image: 9stepsawesome/myboot:v1 20 | ports: 21 | - containerPort: 8080 22 | envFrom: 23 | - configMapRef: 24 | name: my-config 25 | volumeMounts: 26 | - name: mysecretvolume 27 | mountPath: /mystuff/secretstuff 28 | readOnly: true 29 | resources: 30 | requests: 31 | memory: "300Mi" 32 | cpu: "250m" # 1/4 core 33 | limits: 34 | memory: "400Mi" 35 | cpu: "1000m" # 1 core 36 | volumes: 37 | - name: mysecretvolume 38 | secret: 39 | secretName: mysecret 40 | -------------------------------------------------------------------------------- /kubefiles/myboot-deployment-configuration.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: myboot 6 | name: myboot 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: myboot 12 | template: 13 | metadata: 14 | labels: 15 | app: myboot 16 | spec: 17 | containers: 18 | - name: myboot 19 | image: 9stepsawesome/myboot:v1 20 | ports: 21 | - containerPort: 8080 22 | envFrom: 23 | - configMapRef: 24 | name: my-config 25 | resources: 26 | requests: 27 | memory: "300Mi" 28 | cpu: "250m" # 1/4 core 29 | limits: 30 | memory: "400Mi" 31 | cpu: "1000m" # 1 core 32 | 33 | -------------------------------------------------------------------------------- /kubefiles/myboot-deployment-liveready.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: myboot 6 | name: myboot 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: myboot 12 | template: 13 | metadata: 14 | labels: 15 | app: myboot 16 | spec: 17 | containers: 18 | - name: myboot 19 | image: 9stepsawesome/myboot:v1 20 | ports: 21 | - containerPort: 8080 22 | resources: 23 | requests: 24 | memory: "300Mi" 25 | cpu: "250m" # 1/4 core 26 | limits: 27 | memory: "400Mi" 28 | cpu: "1000m" # 1 core 29 | livenessProbe: 30 | httpGet: 31 | port: 8080 32 | path: / 33 | initialDelaySeconds: 10 34 | periodSeconds: 5 35 | timeoutSeconds: 2 36 | readinessProbe: 37 | httpGet: 38 | path: /health 39 | port: 8080 40 | initialDelaySeconds: 10 41 | periodSeconds: 3 42 | 43 | -------------------------------------------------------------------------------- /kubefiles/myboot-deployment-quay.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: myboot 6 | name: myboot 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: myboot 12 | template: 13 | metadata: 14 | labels: 15 | app: myboot 16 | spec: 17 | containers: 18 | - name: myboot 19 | image: quay.io/burrsutter/myboot:v1 20 | ports: 21 | - containerPort: 8080 22 | 23 | -------------------------------------------------------------------------------- /kubefiles/myboot-deployment-resources.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: myboot 6 | name: myboot 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: myboot 12 | template: 13 | metadata: 14 | labels: 15 | app: myboot 16 | spec: 17 | containers: 18 | - name: myboot 19 | image: 9stepsawesome/myboot:v1 20 | ports: 21 | - containerPort: 8080 22 | resources: 23 | requests: 24 | memory: "300Mi" 25 | cpu: "250m" # 1/4 core 26 | limits: 27 | memory: "400Mi" 28 | cpu: "1000m" # 1 core 29 | 30 | -------------------------------------------------------------------------------- /kubefiles/myboot-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: myboot 6 | name: myboot 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: myboot 12 | template: 13 | metadata: 14 | labels: 15 | app: myboot 16 | spec: 17 | containers: 18 | - name: myboot 19 | image: 9stepsawesome/myboot:v1 20 | ports: 21 | - containerPort: 8080 22 | 23 | -------------------------------------------------------------------------------- /kubefiles/myboot-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: myboot 5 | labels: 6 | app: myboot 7 | spec: 8 | ports: 9 | - name: http 10 | port: 8080 11 | selector: 12 | app: myboot 13 | type: LoadBalancer -------------------------------------------------------------------------------- /kubefiles/mybootdata-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: mybootdata 6 | name: mybootdata 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: mybootdata 12 | template: 13 | metadata: 14 | labels: 15 | app: mybootdata 16 | spec: 17 | containers: 18 | - name: mybootdata 19 | image: 9stepsawesome/mybootdata:v1 20 | ports: 21 | - containerPort: 8080 22 | # envFrom: 23 | # - configMapRef: 24 | # name: my-config 25 | resources: 26 | requests: 27 | memory: "300Mi" 28 | cpu: "250m" # 1/4 core 29 | limits: 30 | memory: "400Mi" 31 | cpu: "1000m" # 1 core 32 | livenessProbe: 33 | httpGet: 34 | port: 8080 35 | path: /questions 36 | initialDelaySeconds: 10 37 | periodSeconds: 5 38 | timeoutSeconds: 2 39 | readinessProbe: 40 | httpGet: 41 | path: /actuator/health 42 | port: 8080 43 | initialDelaySeconds: 10 44 | periodSeconds: 3 45 | 46 | -------------------------------------------------------------------------------- /kubefiles/mybootdata-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: mybootdata 5 | labels: 6 | app: mybootdata 7 | spec: 8 | ports: 9 | - name: http 10 | port: 8080 11 | selector: 12 | app: mybootdata 13 | type: LoadBalancer -------------------------------------------------------------------------------- /kubefiles/mynode-deployment-new.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: mynodenew 6 | name: mynodenew 7 | spec: 8 | replicas: 2 9 | selector: 10 | matchLabels: 11 | app: mynodenew 12 | template: 13 | metadata: 14 | labels: 15 | app: mynodenew 16 | spec: 17 | containers: 18 | - name: mynodenew 19 | image: 9stepsawesome/mynode:v2 20 | ports: 21 | - containerPort: 8000 22 | 23 | -------------------------------------------------------------------------------- /kubefiles/mynode-deployment-quay.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: mynode 6 | name: mynode 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: mynode 12 | template: 13 | metadata: 14 | labels: 15 | app: mynode 16 | spec: 17 | containers: 18 | - name: mynode 19 | image: quay.io/burrsutter/mynode:v1 20 | ports: 21 | - containerPort: 8000 22 | 23 | -------------------------------------------------------------------------------- /kubefiles/mynode-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: mynode 6 | name: mynode 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: mynode 12 | template: 13 | metadata: 14 | labels: 15 | app: mynode 16 | spec: 17 | containers: 18 | - name: mynode 19 | image: 9stepsawesome/mynode:v1 20 | ports: 21 | - containerPort: 8000 22 | 23 | -------------------------------------------------------------------------------- /kubefiles/mynode-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: mynode 5 | labels: 6 | app: mynode 7 | spec: 8 | ports: 9 | - name: http 10 | port: 8000 11 | selector: 12 | app: mynode 13 | type: LoadBalancer -------------------------------------------------------------------------------- /kubefiles/myspace-namespace.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: myspace -------------------------------------------------------------------------------- /kubefiles/postgres-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: postgres 5 | spec: 6 | replicas: 1 7 | template: 8 | metadata: 9 | labels: 10 | app: postgres 11 | spec: 12 | containers: 13 | - name: postgres 14 | image: postgres:10.5 15 | imagePullPolicy: "IfNotPresent" 16 | env: 17 | - name: POSTGRES_DB 18 | value: postgresdb 19 | - name: POSTGRES_USER 20 | value: admin 21 | - name: POSTGRES_PASSWORD 22 | value: adminS3cret 23 | ports: 24 | - containerPort: 5432 25 | name: postgres 26 | volumeMounts: 27 | # mountPath within the container 28 | - name: postgres-pvc 29 | mountPath: "/var/lib/postgresql/data/:Z" 30 | volumes: 31 | # mapped to the PVC 32 | - name: postgres-pvc 33 | persistentVolumeClaim: 34 | claimName: postgres-pvc -------------------------------------------------------------------------------- /kubefiles/postgres-pv.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolume 3 | metadata: 4 | name: postgres-pv 5 | labels: 6 | type: local 7 | spec: 8 | storageClassName: mystorage 9 | accessModes: 10 | - ReadWriteOnce 11 | capacity: 12 | storage: 2Gi 13 | hostPath: 14 | path: "/data/mypostgresdata/" -------------------------------------------------------------------------------- /kubefiles/postgres-pvc.yml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: postgres-pvc 5 | labels: 6 | app: postgres 7 | spec: 8 | storageClassName: mystorage 9 | accessModes: 10 | - ReadWriteOnce 11 | resources: 12 | requests: 13 | storage: 1Gi 14 | -------------------------------------------------------------------------------- /kubefiles/postgres-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: postgres 5 | labels: 6 | app: postgres 7 | visualize: "true" 8 | spec: 9 | ports: 10 | # the port that this service should serve on 11 | - port: 5432 12 | selector: 13 | app: postgres -------------------------------------------------------------------------------- /kubefiles/yourspace-namespace.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: yourspace -------------------------------------------------------------------------------- /minikube_dashboard.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | kubectl patch -n kube-system service/kubernetes-dashboard -p '{"spec":{"type":"NodePort"}}' 4 | 5 | open http://$(minikube -p 9steps ip):$(kubectl get svc kubernetes-dashboard -n kube-system -o 'jsonpath={.spec.ports[0].nodePort}') -------------------------------------------------------------------------------- /pizzas/cheese-pizza.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: mykubernetes.burrsutter.com/v1beta2 2 | kind: Pizza 3 | metadata: 4 | name: burrcheese 5 | spec: 6 | toppings: 7 | - mozzarella 8 | sauce: regular 9 | -------------------------------------------------------------------------------- /pizzas/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | kubectl -n mystuff delete configmap pizza-controller 4 | 5 | kubectl -n mystuff delete -f webhook-py.yaml 6 | 7 | kubectl -n mystuff delete -f pizza-controller.yaml -------------------------------------------------------------------------------- /pizzas/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | kubectl -n mystuff apply -f pizza-controller.yaml 4 | 5 | kubectl -n mystuff create configmap pizza-controller --from-file=sync.py 6 | 7 | kubectl -n mystuff apply -f webhook-py.yaml 8 | -------------------------------------------------------------------------------- /pizzas/meat-lovers.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: mykubernetes.burrsutter.com/v1beta2 2 | kind: Pizza 3 | metadata: 4 | name: burrmeats 5 | spec: 6 | toppings: 7 | - mozzarella 8 | - pepperoni 9 | - sausage 10 | - bacon 11 | sauce: extra 12 | -------------------------------------------------------------------------------- /pizzas/pizza-controller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: metacontroller.k8s.io/v1alpha1 2 | kind: CompositeController 3 | metadata: 4 | name: pizza-controller 5 | spec: 6 | generateSelector: true 7 | parentResource: 8 | apiVersion: mykubernetes.burrsutter.com/v1beta2 9 | resource: pizzas 10 | childResources: 11 | - apiVersion: v1 12 | resource: pods 13 | updateStrategy: 14 | method: Recreate 15 | hooks: 16 | sync: 17 | webhook: 18 | url: http://pizza-controller.pizzahat:8080/sync -------------------------------------------------------------------------------- /pizzas/pizza-crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: pizzas.mykubernetes.burrsutter.com 5 | labels: 6 | app: pizzamaker 7 | mylabel: stuff 8 | spec: 9 | group: mykubernetes.burrsutter.com 10 | scope: Namespaced 11 | version: v1beta2 12 | names: 13 | kind: Pizza 14 | listKind: PizzaList 15 | plural: pizzas 16 | singular: pizza 17 | shortNames: 18 | - pz 19 | validation: 20 | openAPIV3Schema: 21 | type: object 22 | properties: 23 | spec: 24 | type: object 25 | properties: 26 | toppings: 27 | type: array 28 | sauce: 29 | type: string 30 | -------------------------------------------------------------------------------- /pizzas/setnamespace.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | kubectl config set-context --current --namespace=$1 -------------------------------------------------------------------------------- /pizzas/sync.py: -------------------------------------------------------------------------------- 1 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer 2 | import json 3 | import logging 4 | 5 | class Controller(BaseHTTPRequestHandler): 6 | def sync(self, parent, children): 7 | 8 | # Compute status based on observed state. 9 | desired_status = { 10 | "pods": 1 11 | } 12 | 13 | # Collect the specifications 14 | name = parent["metadata"]["name"] 15 | sauce = parent.get("spec", {}).get("sauce") 16 | toppings = parent.get("spec",{}).get("toppings") 17 | print("\n\nsauce: %s" % sauce) 18 | print("toppings: %s" % toppings) 19 | for topping in toppings: 20 | print(topping) 21 | 22 | stuff = ' ' + sauce + ' ' + ' '.join(toppings) + ' of ' + name 23 | 24 | print stuff 25 | 26 | # Generate the desired child object(s) 27 | 28 | desired_pods = [ 29 | { 30 | "apiVersion": "v1", 31 | "kind": "Pod", 32 | "metadata": { 33 | "name": name 34 | }, 35 | "spec": { 36 | "restartPolicy": "OnFailure", 37 | "containers": [ 38 | { 39 | "name": "pizza", 40 | "image": "busybox", 41 | "command": ["echo", "requested pizza: %s" % stuff] 42 | } 43 | ] 44 | } 45 | } 46 | ] 47 | 48 | return {"status": desired_status, "children": desired_pods} 49 | 50 | def do_POST(self): 51 | # Serve the sync() function as a JSON webhook. 52 | 53 | observed = json.loads(self.rfile.read(int(self.headers.getheader("content-length")))) 54 | desired = self.sync(observed["parent"], observed["children"]) 55 | 56 | self.send_response(200) 57 | self.send_header("Content-type", "application/json") 58 | self.end_headers() 59 | self.wfile.write(json.dumps(desired)) 60 | 61 | HTTPServer(("", 8080), Controller).serve_forever() -------------------------------------------------------------------------------- /pizzas/test-pizza.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": { 3 | "metadata": { 4 | "name": "burrcheese" 5 | }, 6 | "spec": { 7 | "toppings": [ 8 | "mozzarella", 9 | "pepperoni" 10 | ], 11 | "sauce": "regular" 12 | } 13 | }, 14 | "children": { 15 | "Pod.v1": "x" 16 | } 17 | } -------------------------------------------------------------------------------- /pizzas/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curl -X POST -H "Content-Type: application/json" -d @test-pizza.json localhost:8080 -------------------------------------------------------------------------------- /pizzas/veggie-lovers.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: mykubernetes.burrsutter.com/v1beta2 2 | kind: Pizza 3 | metadata: 4 | name: burrveggie2 5 | spec: 6 | toppings: 7 | - mozzarella 8 | - black olives 9 | sauce: extra 10 | -------------------------------------------------------------------------------- /pizzas/webhook-py.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: pizza-controller 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: pizza-controller 10 | template: 11 | metadata: 12 | labels: 13 | app: pizza-controller 14 | spec: 15 | containers: 16 | - name: controller 17 | image: python:2.7 18 | command: ["python", "/hooks/sync.py"] 19 | volumeMounts: 20 | - name: hooks 21 | mountPath: /hooks 22 | volumes: 23 | - name: hooks 24 | configMap: 25 | name: pizza-controller 26 | --- 27 | apiVersion: v1 28 | kind: Service 29 | metadata: 30 | name: pizza-controller 31 | spec: 32 | selector: 33 | app: pizza-controller 34 | ports: 35 | - port: 8080 -------------------------------------------------------------------------------- /poll_myboot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while true 4 | do 5 | curl $(minikube -p 9steps ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}" -n myspace) 6 | sleep .2; 7 | done 8 | 9 | -------------------------------------------------------------------------------- /poll_myboot_shift.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while true 4 | do 5 | curl $(minishift ip):$(kubectl get service/myboot -o jsonpath="{.spec.ports[*].nodePort}" -n myspace) 6 | sleep .5; 7 | done 8 | 9 | -------------------------------------------------------------------------------- /poll_mynode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while true 4 | do 5 | curl $(minikube ip):$(kubectl get service/mynode -o jsonpath="{.spec.ports[*].nodePort}" -n yourspace) 6 | sleep .5; 7 | done 8 | 9 | -------------------------------------------------------------------------------- /poll_mynode_shift.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while true 4 | do 5 | curl $(minishift ip):$(kubectl get service/mynode -o jsonpath="{.spec.ports[*].nodePort}" -n yourspace) 6 | sleep .5; 7 | done 8 | 9 | -------------------------------------------------------------------------------- /readme.adoc: -------------------------------------------------------------------------------- 1 | = Main Readme 2 | 3 | A new version of this tutorial is being maintained at https://dn.dev/kube-tutorial 4 | 5 | Slides for 9 Steps to Awesome with Kubernetes at http://bit.ly/9stepsawesome[bit.ly/9stepsawesome] 6 | 7 | === Follow Steps 1 through 9 Below 8 | 9 | include::1_installation_started.adoc[Step 1: Installation & Getting Started] 10 | 11 | include::2_building_running.adoc[Step 2: Building Images & Running Containers] 12 | 13 | include::3_logs.adoc[Step 3: Logs] 14 | 15 | include::4_kubectl_exec.adoc[Step 4: exec Magic] 16 | 17 | include::5_configuration.adoc[Step 5: Configuration] 18 | 19 | include::6_discovery.adoc[Step 6: Service Discovery & Load-balancing] 20 | 21 | include::7_live_ready.adoc[Step 7: Live and Ready] 22 | 23 | include::8_deployment_techniques.adoc[Step 8: Deployment Techniques: Blue/Green] 24 | 25 | include::9_databases.adoc[Step 9: Databases] 26 | -------------------------------------------------------------------------------- /setcontext.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | kubectl config set-context minikube --namespace=myspace -------------------------------------------------------------------------------- /start_minikube.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | minikube --profile 9steps config set memory 6144 4 | minikube --profile 9steps config set cpus 2 5 | minikube --profile 9steps config set vm-driver virtualbox #hyperkit 6 | # kubernetes version only applies to minikube 7 | minikube --profile 9steps config set kubernetes-version v1.16.0 8 | # minishift addon enable admin-user 9 | # minishift addon enable anyuid 10 | minikube start --profile 9steps -------------------------------------------------------------------------------- /start_minishift.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | minishift --profile 9stepsMS2 config set memory 6144 4 | minishift --profile 9stepsMS2 config set cpus 2 5 | minishift --profile 9stepsMS2 config set vm-driver virtualbox #hyperkit 6 | # kubernetes version only applies to minikube 7 | # minikube --profile 9steps config set kubernetes-version v1.14.0 8 | minishift --profile 9stepsMS2 addon enable admin-user 9 | minishift --profile 9stepsMS2 addon enable anyuid 10 | minishift start --profile 9stepsMS2 --vm-driver virtualbox --memory 6144 --cpus 2 --addon enable=admin-user --addon enable=anyuid 11 | 12 | # oc adm policy add-cluster-role-to-user cluster-admin admin --------------------------------------------------------------------------------