├── .DS_Store ├── 0_Prereq_tasks └── 0_1_Building_Environment.md ├── 1_Cloud_Native_Patterns ├── 1_1_App_Patterns.md ├── 1_2_Sidecar_pattern.md ├── 1_3_Ambassador_Pattern.md └── 1_4_Adapter_Pattern.md ├── 2_Advanced_K8S_API ├── 2_1_Advanced_API_Pod.md └── 2_2_Advanced_API_Deployment.md ├── 3_Custom_Monitoring └── 3_1_Kubernetes_Metrics_architecture.md ├── 4_Native_K8S_Automation └── 4_1_Admission_Controllers.md ├── 5_K8S_Extensability └── 5_1_CRD.md ├── LICENSE ├── README.md └── Resources ├── Dockerfiles └── Dockerfile └── Manifests ├── adapter-ex.yaml ├── ambassador-ex.yaml ├── base-deploy.yaml ├── hpa-simple-hpa.yaml ├── hpa-simple.yaml ├── liveready-pod.yaml ├── redis-config-pod.yaml ├── secret-mysql.yaml ├── security-pod.yaml └── sidecar-ex.yaml /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evillgenius75/k8s_appdev_adv/9d2791e73dd3905449d39e22bd6b18048e96c611/.DS_Store -------------------------------------------------------------------------------- /0_Prereq_tasks/0_1_Building_Environment.md: -------------------------------------------------------------------------------- 1 | # Build The Infrastructure Needed for Today's Exercises 2 | 3 | ## Required Tools 4 | 5 | ### Cloud Shell 6 | The entire lab can be completed in bash flavored [Cloud Shell](https://docs.microsoft.com/en-us/azure/cloud-shell/overview). If you're not familiar with Cloud Shell, or how to set it up, you are probably in the wrong lab. 7 | 8 | ### Local Machine (Optional) 9 | If cloud shell timeouts get your ire up, feel free to install these tools and run the lab locally: 10 | * Bash 11 | * Docker 12 | * Azure CLI 13 | * Visual Studio Code 14 | * Helm 15 | * Kubernetes CLI (kubectl) 16 | * git tools 17 | * jq - tool for querying json objects in bash - [link](https://stedolan.github.io/jq/) 18 | 19 | > **NOTE:** 20 | > Windows users beware! You might want to use ~~powershell~~ bash flavored cloud shell as some of the tools are difficult to get running on Win10. 21 | 22 | ## AKS Deployment 23 | For this lab, we will install a vanilla 3-node AKS cluster that is RBAC enabled. 24 | 25 | ```console 26 | GROUP_NAME=k8s_appdev_adv 27 | AKS_NAME= 28 | az group create -n $GROUP_NAME -l 29 | az aks create -n $AKS_NAME -g $GROUP_NAME -k 1.11.6 --generate-ssh-keys 30 | ``` 31 | Once your instance of AKS is turned up, use azure cli to pull down a kubeconfig context that will give you access to the api. 32 | ```console 33 | az aks get-credentials -n $AKS_NAME -g $GROUP_NAME 34 | ``` 35 | Validate that you can call the AKS api and that the AKS nodes are ready by using kubectl get to check the status of the nodes. 36 | ```console 37 | kubectl get nodes 38 | ``` 39 | With your eyeballs, check the ready status of each node in the response which should look something like this: 40 | ```output 41 | NAME STATUS ROLES AGE VERSION 42 | aks-nodepool1-42628860-0 Ready agent 3h v1.11.6 43 | aks-nodepool1-42628860-1 Ready agent 3h v1.11.6 44 | aks-nodepool1-42628860-2 Ready agent 3h v1.11.6 45 | ``` 46 | That will do it, now you are "Ready", pun intended, for the sultry sounds of Eddie V's voice. 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /1_Cloud_Native_Patterns/1_1_App_Patterns.md: -------------------------------------------------------------------------------- 1 | # Application Patterns for Containers in Kubernetes 2 | 3 | ## Sidecar Pattern 4 | The Sidecar design pattern is a form of a single-node, multiple containers application pattern. The Sidecar pattern advocates the usage of an additional container or containers which extend or enhance the main container. Let's use an example of a web-server container that is based on legacy code and orginally serves non-secure HTTP traffic. To add SSL capabilities a sidecar container can be added to extend the functionality of the original service. Some of the benefits of this pattern include the following: 5 | * Separation of concerns: Let each container take care of their core concern. The Web server container serves web pages while the sidecar container processes server logs. This helps when diagnosing issues as each container has seperate logs and health checks which afford clear seperation of concerns. 6 | * Single responsibility principle: A container has primarily got one reason for change. In other words, each container does one thing well. Based on this principle, one can have separate teams work on different containers as their concerns are clearly segregated. 7 | * Cohesiveness/Reusability: With sidecar containers for processing logs, this container can as well be reused at other places in the application. 8 | 9 | ## Ambassador Pattern 10 | The Ambassador pattern is another form of single-node, multiple containers application patterns. This pattern advocates usage of additional containers as proxies to the external group/cluster of servers. The primary goal is to simplify access of external servers using the ambassador container. This container can be grouped as a logical atomic unit such that the application container can invoke this ambassador/proxy container which can, then, invoke the cluster of servers. The following is primary advantage of using ambassador container: 11 | 12 | * Let the ambassador container take care of connecting to clustered servers while the Application container connects to the ambassador container using the “localhost” paradigm. 13 | * The Above enables using a standalone server (and not cluster of servers) in the development environment. Application container opens the connection to the server on “localhost” and then finds the proxy without any service discovery. 14 | 15 | ## Adapter Pattern 16 | The Adapter pattern is yet another form of single-node, multiple containers application patterns. It advocates usage of additional containers to present a simplified, homogenized view of an application running within a container. These containers can be called as adapter container. The main container communicates with the adapter container through localhost or a shared local volume. This is unlike ambassador container which simply has access to external servers. 17 | 18 | A concrete example of the adapter pattern is adapters that ensure all containers in a system have the same monitoring interface. -------------------------------------------------------------------------------- /1_Cloud_Native_Patterns/1_2_Sidecar_pattern.md: -------------------------------------------------------------------------------- 1 | # Exercise 1.2 - Deploy a Sidecar Container to enable HTTPS 2 | 3 | ## Main application 4 | For our example main application use an echo service that outputs some Lorem text (evillgenius75/echo-server) 5 | ## Sidecar container 6 | To add https support I will use Nginx ssl proxy (evillgenius/tls-sidecar) container 7 | ## Deployment 8 | ### TLS/SSL keys 9 | First we need to generate TLS certificate keys and add them to Kubernetes secrets: 10 | ```console 11 | openssl req -x509 -newkey rsa:2048 -keyout tls.key -out tls.crt -nodes -subj '/CN=echo-server' 12 | ``` 13 | 14 | Adding TLS files to Kubernetes secrets 15 | 16 | ```console 17 | kubectl create secret tls echo-tls --cert=tls.crt --key=tls.key 18 | ``` 19 | 20 | ### Kubernetes sidecar deployment 21 | The following configuration defines the main application containers, “echo” and nginx container “nginx”. Both containers run in the same pod and share pod resources and serve as an example implementation of the sidecar pattern. 22 | 23 | ```yaml 24 | apiVersion: apps/v1beta2 25 | kind: Deployment 26 | metadata: 27 | name: sidecar-ex 28 | labels: 29 | app: sidecar-ex 30 | proxy: nginx 31 | spec: 32 | replicas: 1 33 | selector: 34 | matchLabels: 35 | app: sidecar-ex 36 | template: 37 | metadata: 38 | labels: 39 | app: sidecar-ex 40 | spec: 41 | containers: 42 | - name: echo 43 | image: evillgenius/echo-server:vb1 44 | ports: 45 | - containerPort: 8080 46 | - name: nginx 47 | image: evillgenius/tls-sidecar:vb1 48 | volumeMounts: 49 | - name: ssl-keys 50 | readOnly: true 51 | mountPath: "/etc/nginx/ssl" 52 | ports: 53 | - containerPort: 443 54 | volumes: 55 | - name: ssl-keys 56 | secret: 57 | secretName: echo-tls 58 | ``` 59 | 60 | 61 | Save this file to sidecar-ex.yaml and create deployment Kubernetes object: 62 | 63 | ```console 64 | kubectl create -f sidecar-ex.yaml 65 | ``` 66 | 67 | Wait for pods to be Ready: 68 | ```console 69 | kubectl get pods 70 | ``` 71 | 72 | ```output 73 | NAME READY STATUS RESTARTS AGE 74 | sidecar-ex-686bbff8d7-42mcn 2/2 Running 0 1m 75 | ``` 76 | ## Testing 77 | For testing, setup a port forwarding rule. This will map a local port 8043 to the ssl sidecar on port 443: 78 | 79 | ```console 80 | kubectl port-forward 8043:443 & 81 | ``` 82 | 83 | First lets validate that application does not respond on http and then it does respond on https requests 84 | 85 | 86 | ### Using http 87 | ```console 88 | curl -k http://127.0.0.1:8043/lorem 89 | ``` 90 | 91 | ```output 92 | 93 | 400 The plain HTTP request was sent to HTTPS port 94 | 95 |

400 Bad Request

96 |
The plain HTTP request was sent to HTTPS port
97 |
nginx/1.11.13
98 | 99 | 100 | ``` 101 | ### Using https 102 | ```console 103 | curl -k https://127.0.0.1:8043/lorem 104 | ``` 105 | 106 | ```output 107 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent a scelerisque nisl. Sed placerat finibus ante, non iaculis dui. Etiam viverra, ex sit amet scelerisque lacinia, est nulla egestas lectus, sit amet ullamcorper nisi nisl vitae orci. Fusce et dui facilisis, luctus mauris sed, consequat arcu. Duis porttitor libero id neque dapibus, eget aliquet turpis accumsan. Praesent lectus mauris, tempor eu gravida at, hendrerit nec massa. Vestibulum sed luctus est. Nunc rutrum risus at nisl dictum volutpat. Pellentesque auctor massa tortor, consequat tincidunt tortor bibendum a. Nullam mattis eros eu risus volutpat, id euismod elit facilisis. Vestibulum quis eros a magna accumsan scelerisque vel vel metus. Curabitur at magna cursus, vulputate velit eu, sodales dolor. Duis cursus, tortor in vehicula tempor, quam nisi dictum arcu, eu congue mauris felis quis quam. Vivamus vitae risus venenatis, tincidunt eros ac, posuere purus. 108 | 109 | Nunc placerat feugiat hendrerit. Proin ipsum ex, tincidunt sed ante et, pretium ullamcorper mauris. Pellentesque vel dolor sed dolor euismod vestibulum. Cras facilisis non lacus nec tincidunt. Duis pharetra lacinia risus, nec sollicitudin odio. Sed et iaculis sapien. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed ultricies rhoncus nunc, a consequat neque pulvinar id. Phasellus in tincidunt tellus, in blandit sem. Integer interdum vehicula lacus at tincidunt. Donec feugiat ultricies risus, et pellentesque orci porta mattis. Morbi ut nisl ut metus ornare egestas. Donec eget orci vitae justo hendrerit faucibus. Morbi et feugiat velit. Integer at iaculis tellus. Aenean dolor lacus, blandit a tempus eu, malesuada ac massa. 110 | 111 | Sed suscipit ac mi vitae tincidunt. Vestibulum auctor ... 112 | ``` 113 | 114 | Find and kill the portforward kubectl processes. 115 | ```console 116 | ps | grep kubectl 117 | kill 118 | ``` 119 | 120 | Great! We have got expected output through https connection. 121 | 122 | 123 | -------------------------------------------------------------------------------- /1_Cloud_Native_Patterns/1_3_Ambassador_Pattern.md: -------------------------------------------------------------------------------- 1 | # Exercise 1.3 - Deploy an Ambassador Container 2 | 3 | We need to connect to an external resource, however we want to be able to 4 | dynamically switch our external connection without changing the application 5 | container. 6 | 7 | User request => application => ambassador => external service 8 | 9 | ## Main application 10 | For our example main application use a Nodejs web service that connects to the external resource via the ambassador, which is essentially a proxy. 11 | ## Ambassador container 12 | This container is a simple NodeJS web client that returns a random external API endpoint when called. 13 | ## Deployment 14 | ### Kubernetes ambassador deployment 15 | The following configuration defines the main application container “app-container” and the ambassador container “ambassador-container”. Both containers run in the same pod and share pod resources including localhost network. 16 | 17 | ```yaml 18 | apiVersion: apps/v1beta2 19 | kind: Deployment 20 | metadata: 21 | name: ambassador 22 | labels: 23 | app: ambassador-ex 24 | spec: 25 | replicas: 1 26 | selector: 27 | matchLabels: 28 | app: ambassador-ex 29 | template: 30 | metadata: 31 | labels: 32 | app: ambassador-ex 33 | spec: 34 | containers: 35 | - name: app-container 36 | image: evillgenius/ambassador-example-app:1.0.0 37 | ports: 38 | - containerPort: 3000 39 | - name: ambassador-container 40 | image: evillgenius/ambassador-example-ambassador:1.0.0 41 | ports: 42 | - containerPort: 8080 43 | --- 44 | kind: Service 45 | apiVersion: v1 46 | metadata: 47 | name: ambassador-svc 48 | spec: 49 | # Select which pods can be used to handle traffic to this service 50 | # based on their labels 51 | selector: 52 | app: ambassador-ex 53 | 54 | # Expose the service on a static port on each node 55 | # so that we can access the service from outside the cluster 56 | type: LoadBalancer 57 | 58 | # Ports exposed by the service. 59 | ports: 60 | # `port` is the port for the service. 61 | # `targetPort` is the port for the pods, 62 | # it has to match what's defined in the pod YAML. 63 | - port: 80 64 | targetPort: 3000 65 | ``` 66 | 67 | 68 | Save this file to ambassador-ex.yaml and create deployment Kubernetes object: 69 | 70 | ```console 71 | kubectl create -f ambassador-ex.yaml 72 | ``` 73 | 74 | Wait for pods to be running: 75 | ```console 76 | kubectl get pods 77 | ``` 78 | 79 | ```output 80 | NAME READY STATUS RESTARTS AGE 81 | ambassador-686bbff8d7-42mcn 2/2 Running 0 1m 82 | ``` 83 | ## Testing 84 | The manifest included a service of type LoadBalancer which exposes the app-container port 3000 on port 80 of an Azure Load Balancer. Determine the external IP address of the Load Balancer by: 85 | 86 | ```console 87 | kubectl get svc 88 | ``` 89 | 90 | ```output 91 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 92 | ambassador-svc LoadBalancer 172.23.186.163 40.117.238.238 80:30429/TCP 5m 93 | kubernetes ClusterIP 172.23.0.1 443/TCP 17d 94 | ``` 95 | In the example above the output shows the ambassador-svc EXTERNAL-IP is 40.117.238.238 and is listening on port 80. 96 | >**Note:** Your External IP address will be different so be sure to use your resulting IP address for the test 97 | 98 | Now open your browser or from the command line you can curl the public IP 99 | 100 | ```console 101 | curl 40.117.238.238 102 | ``` 103 | 104 | Run the command a few times and the output will randomly change 105 | 106 | **EXAMPLE OUTPUT** 107 | 108 | ```output 109 | eddie@Azure:~$ curl 40.117.238.238 110 | {"page":2,"per_page":3,"total":12,"total_pages":4,"data":[{"id":4,"first_name":"Eve",>"last_name":"Holt","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/marcoramires/128.jpg"},{"id":5,>"first_name":"Charles","last_name":"Morris","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/stephenmoon/128.jpg"},{"id":6,>"first_name":"Tracey","last_name":"Ramos","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/bigmancho/128.jpg"}]} 111 | ``` 112 | ```output 113 | eddie@Azure:~$ curl 40.117.238.238 114 | { 115 | "userId": 1, 116 | "id": 1, 117 | "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", 118 | "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" 119 | } 120 | ``` 121 | Great! We have got expected output through the Ambassador proxy. 122 | -------------------------------------------------------------------------------- /1_Cloud_Native_Patterns/1_4_Adapter_Pattern.md: -------------------------------------------------------------------------------- 1 | # Exercise 1.4 - Deploy an Adapter Container 2 | 3 | ## Main application 4 | It defines a main application container which writes the current date and system usage information to a log file every five seconds. 5 | ## Adapter container 6 | The adapter container reads what the application has written and reformats it into a structure that a hypothetical monitoring service requires. 7 | 8 | ## Deployment 9 | ### Kubernetes adapter deployment 10 | This application writes system usage information (`top`) to a status file every five seconds. This sidecar container takes the output format of the application(the current date and system usage information), simplifies and reformats it for the monitoring service to come and collect. In this example, our monitoring service requires status files to have the date, then memory usage, then CPU percentage each on a new line. Our adapter container will inspect the contents of the app's top file, reformat it, and write the correctly formatted output to the status file. 11 | 12 | 13 | ```yaml 14 | apiVersion: apps/v1beta2 15 | kind: Deployment 16 | metadata: 17 | name: adapter 18 | labels: 19 | app: adapter-ex 20 | spec: 21 | replicas: 1 22 | selector: 23 | matchLabels: 24 | app: adapter-ex 25 | template: 26 | metadata: 27 | labels: 28 | app: adapter-ex 29 | spec: 30 | # Create a volume called 'shared-logs' that the 31 | # app and adapter share. 32 | volumes: 33 | - name: shared-logs 34 | emptyDir: {} 35 | containers: 36 | # Main application container 37 | - name: app-container 38 | image: alpine 39 | command: ["/bin/sh"] 40 | args: ["-c", "while true; do date > /var/log/top.txt && top -n 1 -b >> /var/log/top.txt; sleep 5;done"] 41 | # Mount the pod's shared log file into the app 42 | # container. The app writes logs here. 43 | volumeMounts: 44 | - name: shared-logs 45 | mountPath: /var/log 46 | 47 | # Adapter container 48 | - name: adapter-container 49 | image: alpine 50 | command: ["/bin/sh"] 51 | 52 | # A long command doing a simple thing: read the `top.txt` file that the 53 | # application wrote to and adapt it to fit the status file format. 54 | # Get the date from the first line, write to `status.txt` output file. 55 | # Get the first memory usage number, write to `status.txt`. 56 | # Get the first CPU usage percentage, write to `status.txt`. 57 | 58 | args: ["-c", "while true; do (cat /var/log/top.txt | head -1 > /var/log/status.txt) && (cat /var/log/top.txt | head -2 | tail -1 | grep 59 | -o -E '\\d+\\w' | head -1 >> /var/log/status.txt) && (cat /var/log/top.txt | head -3 | tail -1 | grep 60 | -o -E '\\d+%' | head -1 >> /var/log/status.txt); sleep 5; done"] 61 | 62 | 63 | # Mount the pod's shared log file into the adapter container. 64 | volumeMounts: 65 | - name: shared-logs 66 | mountPath: /var/log 67 | ``` 68 | 69 | 70 | Save this file to adapter-ex.yaml and create deployment Kubernetes object: 71 | 72 | ```console 73 | kubectl create -f adapter-ex.yaml 74 | ``` 75 | 76 | Wait for pods to be running: 77 | ```console 78 | kubectl get pods 79 | ``` 80 | 81 | ```output 82 | NAME READY STATUS RESTARTS AGE 83 | adapter-5469c8c6f9-cdcpp 2/2 Running 0 5s 84 | ``` 85 | 86 | >**NOTE:** The Pod name in the output above is an example. Please use your exact pod name in the steps below 87 | 88 | ## Testing 89 | Once the pod is running: 90 | 91 | ### Connect to the application pod 92 | ```console 93 | kubectl exec -c app-container -it sh 94 | ``` 95 | ### Take a look at what the application is writing 96 | ```ouput 97 | cat /var/log/top.txt 98 | ``` 99 | ### Take a look at what the adapter has reformatted it to 100 | ```output 101 | cat /var/log/status.txt 102 | ``` 103 | ### Exit the Pod 104 | ```output 105 | exit 106 | ``` 107 | 108 | Now the logging system can receive the log in the format it expects the data to be in and the original application did not have to be modified in any way. 109 | 110 | ## Clean Up 111 | 112 | Now delete the Adapter deployment 113 | 114 | ```console 115 | kubectl delete -f adapter-ex.yaml 116 | ``` 117 | -------------------------------------------------------------------------------- /2_Advanced_K8S_API/2_1_Advanced_API_Pod.md: -------------------------------------------------------------------------------- 1 | # Using Advanced POD API features 2 | 3 | ## Liveness and Readiness Probes 4 | Many containers, especially web services, won't have an exit code that accurately tells Kubernetes whether the container was successful. Most web servers won't terminate at all! How do you tell Kubernetes about your container's health? You define container probes. 5 | Container probes are small processes that run periodically. The result of this process determines Kubernetes' view of the container's state—the result of the probe is one of Success, Failed, or Unknown. 6 | You will most often use container probes through Liveness and Readiness probes. Liveness probes are responsible for determining if a container is running or when it needs to be restarted. Readiness probes indicate that a container is ready to accept traffic. Once all of its containers indicate they are ready to accept traffic, the pod containing them can accept requests. 7 | There are three ways to implement these probes. One way is to use HTTP requests, which look for a successful status code in response to making a request to a defined endpoint. Another method is to use TCP sockets, which returns a failed status if the TCP connection cannot be established. The final, most flexible, way is to define a custom command, whose exit code determines whether the check is successful. 8 | 9 | ### Readiness Probes 10 | In this example, we've got a simple web server that starts serving traffic after some delay while the application starts up. If we didn't configure our readiness probe, Kubernetes would either start sending traffic to the container before we're ready, or it would mark the pod as unhealthy and never send it traffic. 11 | 12 | #### Exercise 2.1 - Readiness Probe 13 | Let's start with a readiness probe that makes a request to http:// 14 | localhost:8000/ after five seconds, and only looks for one failure before it marks the pod as unhealthy. 15 | 16 | ```yaml 17 | kind: Pod 18 | apiVersion: v1 19 | metadata: 20 | name: liveness-readiness-pod 21 | spec: 22 | containers: 23 | - name: server 24 | image: python:2.7-alpine 25 | # Check that a container is ready to handle requests. 26 | readinessProbe: 27 | # After thirty seconds, check for a 200 response on localhost:8000/ 28 | # and check four times before thinking we've failed. 29 | initialDelaySeconds: 5 # Update this to 30 30 | failureThreshold: 1 # Update this to 4 31 | httpGet: 32 | path: / 33 | port: 8000 34 | # This container starts a simple web server after 45 seconds. 35 | env: 36 | - name: DELAY_START 37 | value: "45" 38 | command: ["/bin/sh"] 39 | args: ["-c", "echo 'Sleeping...'; sleep $(DELAY_START); echo 'Starting server...'; python -m SimpleHTTPServer"] 40 | ``` 41 | Save this file to liveready-pod.yaml and create pod Kubernetes object: 42 | 43 | ```console 44 | kubectl create -f liveready-pod.yaml 45 | ``` 46 | 47 | Wait for pods to be Ready: 48 | ```console 49 | kubectl get pods 50 | ``` 51 | 52 | ```output 53 | NAME READY STATUS RESTARTS AGE 54 | liveness-readiness-pod 1/1 Running 0 19s 55 | ``` 56 | ##### Testing 57 | 58 | Describe the pod to see the events. Kubernetes thinks the pod is unhealthy. In reality, we just have an application that takes a little while to boot up! 59 | 60 | ```console 61 | kubectl describe pod liveness-readiness-pod 62 | ``` 63 | 64 | ```output 65 | .... 66 | Events: 67 | Type Reason Age From Message 68 | ---- ------ ---- ---- ------- 69 | Normal Scheduled 13m default-scheduler Successfully assigned default/liveness-readiness-pod to aks-agentpool-22325591-2 70 | Normal Pulling 13m kubelet, aks-agentpool-22325591-2 pulling image "python:2.7-alpine" 71 | Normal Pulled 13m kubelet, aks-agentpool-22325591-2 Successfully pulled image "python:2.7-alpine" 72 | Normal Created 13m kubelet, aks-agentpool-22325591-2 Created container 73 | Normal Started 13m kubelet, aks-agentpool-22325591-2 Started container 74 | Warning Unhealthy 12m (x4 over 13m) kubelet, aks-agentpool-22325591-2 Readiness probe failed: Get http://172.22.0.37:8000/: dial tcp 172.22.0.37:8000: connect: connection refused 75 | ``` 76 | 77 | ### Liveness Probes 78 | Another useful way to customize when Kubernetes restarts your applications is through liveness probes. Kubernetes will execute a container's liveness probe periodically to check that the container is running correctly. If the probe reports failure, the container is restarted. Otherwise, Kubernetes leaves the container as-is. 79 | 80 | #### Lab 2.2 Liveness and Readiness Probes combined 81 | Let's build off the previous example and add a liveness probe for our web server container. Instead of using an httpGet request to probe the container, this time we'll use a command whose exit code indicates whether the container is dead or alive. 82 | 83 | ```yaml 84 | kind: Pod 85 | apiVersion: v1 86 | metadata: 87 | name: liveness-readiness-pod 88 | spec: 89 | containers: 90 | - name: server 91 | image: python:2.7-alpine 92 | 93 | # Check that a container is ready to handle requests. 94 | readinessProbe: 95 | # After thirty seconds, check for a 200 response on localhost:8000/ 96 | # and check four times before thinking we've failed. 97 | initialDelaySeconds: 30 # Update this to 30 98 | failureThreshold: 4 # Update this to 4 99 | httpGet: 100 | path: / 101 | port: 8000 102 | 103 | # Check that a container is still alive and running 104 | livenessProbe: 105 | # After sixty seconds, start checking periodically whether we have 106 | # and index.html file in our web server's directory. 107 | # Wait five seconds before repeating the check. 108 | # This file will never exist, so our container gets restarted. 109 | initialDelaySeconds: 60 110 | periodSeconds: 5 111 | exec: 112 | command: ["ls", "index.html"] 113 | 114 | # This container starts a simple web server after 45 seconds. 115 | env: 116 | - name: DELAY_START 117 | value: "45" 118 | command: ["/bin/sh"] 119 | args: ["-c", "echo 'Sleeping...'; sleep $(DELAY_START); echo 'Starting server...'; python -m SimpleHTTPServer"] 120 | ``` 121 | Now, delete and recreate the new pod. Then, inspect its event log just as above. We'll see that our changes to the readiness probe have worked, but then the liveness probe finds that the container doesn't have the required file so the container will be killed and restarted. 122 | 123 | ```console 124 | kubectl delete pod liveness-readiness-pod 125 | kubectl apply -f liveready-pod.yaml 126 | ``` 127 | 128 | ## Pod Security 129 | A little-used, but powerful feature of Kubernetes pods are security context, an object that configures roles and privileges for containers. A security context can be defined at the pod level or at the container level. If the container doesn't declare its own security context, it will inherit from the parent pod. 130 | 131 | Security contexts generally configure two fields: the user ID that should be used to run the pod or container, and the group ID that should be used for filesystem access. These options are useful when modifying files in a volume that is mounted and shared between containers, or in a persistent volume that's shared between pods. We'll cover volumes in detail in a coming chapter, but for now think of them like a regular directory on your computer's filesystem. 132 | 133 | To illustrate, let's create a pod with a container that writes the current date to a file in a mounted volume every five seconds. 134 | 135 | ### Lab 2.3 - Pod Security 136 | Create a pod that defines the runAsUser and the fsGroup ID for all of the containers in the pod 137 | 138 | ```yaml 139 | kind: Pod 140 | apiVersion: v1 141 | metadata: 142 | name: security-context-pod 143 | spec: 144 | securityContext: 145 | # User ID that containers in this pod should use 146 | runAsUser: 45 147 | # Group ID for filesystem access 148 | fsGroup: 231 149 | volumes: 150 | - name: simple-directory 151 | emptyDir: {} 152 | containers: 153 | - name: example-container 154 | image: alpine 155 | command: ["/bin/sh"] 156 | args: ["-c", "while true; do date >> /etc/directory/file.txt; sleep 5; done"] 157 | volumeMounts: 158 | - name: simple-directory 159 | mountPath: /etc/directory 160 | ``` 161 | Save this file to security-pod.yaml and create pod Kubernetes object: 162 | 163 | ```console 164 | kubectl create -f security-pod.yaml 165 | ``` 166 | 167 | Wait for pods to be Ready: 168 | ```console 169 | kubectl get pods 170 | ``` 171 | 172 | ```output 173 | NAME READY STATUS RESTARTS AGE 174 | security-context-pod 1/1 Running 0 19s 175 | ``` 176 | 177 | #### Testing 178 | You will need to exec into the pod with an `ls` of the directory and see the properties of the file where the user is UID45 and the FileSystem Group is 213 179 | ```console 180 | kubectl exec security-context-pod -- ls -l /etc/directory 181 | ``` 182 | 183 | ```output 184 | total 4 185 | -rw-r--r-- 1 45 231 58 Feb 3 19:07 file.txt 186 | ``` 187 | 188 | ## Configuration through ConfigMaps and Secrets 189 | 190 | ### ConfigMaps 191 | 192 | Many applications require configuration via some combination of config files, command line arguments, and environment variables. These configuration artifacts should be decoupled from image content in order to keep containerized applications portable. The ConfigMap API resource provides mechanisms to inject containers with configuration data while keeping containers agnostic of Kubernetes. ConfigMaps can be used to store fine-grained information like individual properties or coarse-grained information like entire config files or JSON blobs. 193 | 194 | The ConfigMap API resource holds key-value pairs of configuration data that can be consumed in pods or used to store configuration data for system components such as controllers. ConfigMap is similar to Secrets, but designed to more conveniently support working with strings that do not contain sensitive information. 195 | 196 | There are 2 methods in which a pod can consume a ConfigMap. 197 | 1. Using a ConfigMap through a volume: 198 | * Once created, add the ConfigMap as a volume in your pod's specification. Then, mount that volume into the container's filesystem. Each property name in the ConfigMap will become a new file in the mounted directory, and the contents of each file will be the value specified in the ConfigMap. 199 | 2. Using a ConfigMap through environment variables: 200 | * Another useful way of consuming a ConfigMap you have created in your Kubernetes cluster is by injecting its data as environment variables into your container. 201 | 202 | ```yaml 203 | kind: Pod 204 | apiVersion: v1 205 | metadata: 206 | name: pod-env-var 207 | spec: 208 | containers: 209 | - name: env-var-configmap 210 | image: nginx:1.7.9 211 | envFrom: 212 | - configMapRef: 213 | name: example-configmap 214 | ``` 215 | 216 | 217 | >**Note:** ConfigMaps are not intended to act as a replacement for a properties file. ConfigMaps are intended to act as a reference to multiple properties files. 218 | 219 | ConfigMaps must be created before they are consumed in pods unless they are marked as optional. References to ConfigMaps that do not exist will prevent the pod from starting. Controllers may be written to tolerate missing configuration data; consult individual components configured via ConfigMap on a case-by-case basis. 220 | 221 | When a ConfigMap already being consumed in a volume is updated, projected keys are eventually updated as well. Kubelet is checking whether the mounted ConfigMap is fresh on every periodic sync. However, it is using its local ttl-based cache for getting the current value of the ConfigMap. As a result, the total delay from the moment when the ConfigMap is updated to the moment when new keys are projected to the pod can be as long as kubelet sync period + ttl of ConfigMaps cache in kubelet. 222 | >**Note:** A container using a ConfigMap as a subPath volume will not receive ConfigMap updates. 223 | 224 | References via configMapKeyRef to keys that do not exist in a named ConfigMap will prevent the pod from starting. 225 | 226 | ConfigMaps used to populate environment variables via envFrom that have keys that are considered invalid environment variable names will have those keys skipped. The pod will be allowed to start. There will be an event whose reason is InvalidVariableNames and the message will contain the list of invalid keys that were skipped. 227 | 228 | #### Lab 2.4 - ConfigMap for Redis Cache 229 | 230 | Let's us a look at a real-world example: configuring redis using ConfigMap. We want to inject redis with the recommended configuration for using redis as a cache. 231 | 232 | First create a file called redis-config: 233 | ```console 234 | mkdir config 235 | cd config 236 | cat > redis-config << EOF 237 | maxmemory 2mb 238 | maxmemory-policy allkeys-lru 239 | EOF 240 | ``` 241 | 242 | Create a ConfigMap from the file: 243 | ```console 244 | kubectl create configmap redis-config --from-file=redis-config 245 | ``` 246 | 247 | ```console 248 | kubectl describe configmap redis-config 249 | ``` 250 | 251 | ```output 252 | Name: redis-config 253 | Namespace: default 254 | Labels: 255 | Annotations: 256 | 257 | Data 258 | ==== 259 | redis-config: 260 | ---- 261 | maxmemory 2mb 262 | maxmemory-policy allkeys-lru 263 | 264 | Events: 265 | ``` 266 | Now we will create a Redis pod that will use the redis-config to configure the redis container. 267 | 268 | ```yaml 269 | apiVersion: v1 270 | kind: Pod 271 | metadata: 272 | name: redis 273 | spec: 274 | containers: 275 | - name: redis 276 | image: kubernetes/redis:v1 277 | env: 278 | - name: MASTER 279 | value: "true" 280 | ports: 281 | - containerPort: 6379 282 | resources: 283 | limits: 284 | cpu: "0.1" 285 | volumeMounts: 286 | - mountPath: /redis-master-data 287 | name: data 288 | - mountPath: /redis-master 289 | name: config 290 | volumes: 291 | - name: data 292 | emptyDir: {} 293 | - name: config 294 | configMap: 295 | name: redis-config 296 | items: 297 | - key: redis-config 298 | path: redis.conf 299 | ``` 300 | Notice that this pod has a ConfigMap volume that places the redis-config key of the example-redis-config ConfigMap into a file called redis.conf. This volume is mounted into the /redis-master directory in the redis container, placing our config file at /redis-master/redis.conf, which is where the image looks for the redis config file for the master. 301 | 302 | Save this file to secret-redis.yaml and create pod Kubernetes object: 303 | 304 | ```console 305 | kubectl create -f secret-redis.yaml 306 | ``` 307 | 308 | Wait for pods to be Ready: 309 | ```console 310 | kubectl get pods 311 | ``` 312 | 313 | ```output 314 | NAME READY STATUS RESTARTS AGE 315 | redis 1/1 Running 0 19s 316 | ``` 317 | 318 | ##### Testing 319 | 320 | Use `kubectl exec` to enter the pod and run the `redis-cli` tool to verify that the configuration was correctly applied: 321 | 322 | ```console 323 | kubectl exec -it redis redis-cli 324 | ``` 325 | 326 | ```output 327 | 127.0.0.1:6379> 328 | ``` 329 | Get the `maxmemory` entry in the running config: 330 | ```console 331 | CONFIG GET maxmemory 332 | ``` 333 | ```output 334 | 1) "maxmemory" 335 | 2) "2097152" 336 | 127.0.0.1:6379> 337 | ``` 338 | Now get the `maxmemory-policy` entry form the running config: 339 | ```console 340 | CONFIG GET maxmemory-policy 341 | ``` 342 | ```output 343 | 1) "maxmemory-policy" 344 | 2) "allkeys-lru" 345 | 127.0.0.1:6379> 346 | ``` 347 | Exit the CLI: 348 | ```console 349 | exit 350 | ``` 351 | ### Secrets 352 | 353 | If you understand creating and consuming ConfigMaps, you also understand how to use Secrets. The primary difference between the two is that Secrets are designed to hold sensitive data—things like keys, tokens, and passwords. 354 | 355 | Kubernetes will avoid writing Secrets to disk—instead using tmpfs volumes on each node, so they can't be left behind on a node. However, etcd, Kubernetes’ configuration key-value store, stores secrets in plaintext. Further, if there are multiple replicas of etcd, data is not necessarily replicated over SSL. Make sure your administrator restricts etcd access before using Secrets. 356 | 357 | Kubernetes will only send Secrets to a node when one of the node's pods explicitly requires it, and removes the Secret if that pod is removed. Plus, a Secret is only ever visible inside the Pod that requested it. 358 | 359 | When adding Secrets via YAML, they must be encoded in base64. base64 is not an encryption method and does not provide any security for what is encoded—it's simply a way of presenting a string in a different format. Do not commit your base64- encoded secrets to source control or share them publicly. 360 | 361 | #### Lab 2.5 - Configure mySQL with a Secret for the DB Password 362 | 363 | First we need to configure the secret which can be done in a few different ways. The quickest way for single string value pairs is using the `--from-literal`flag of the `kubectl create seceret` command. 364 | 365 | ```console 366 | kubectl create secret generic mysql-password --from-literal=password=MySuperSecretPassword 367 | ``` 368 | 369 | Verify the secret was created and the actual password is not visible 370 | 371 | ```console 372 | kubectl describe secret mysql-password 373 | ``` 374 | 375 | ```output 376 | Name: mysql-password 377 | Namespace: default 378 | Labels: 379 | Annotations: 380 | 381 | Type: Opaque 382 | 383 | Data 384 | ==== 385 | password: 21 bytes 386 | ``` 387 | 388 | Now deploy a mySQL pod that consumes the secret as an environment variable: 389 | 390 | ```yaml 391 | apiVersion: v1 392 | kind: Service 393 | metadata: 394 | name: mysql 395 | spec: 396 | ports: 397 | - port: 3306 398 | selector: 399 | app: mysql 400 | clusterIP: None 401 | --- 402 | apiVersion: apps/v1 403 | kind: Deployment 404 | metadata: 405 | name: mysql 406 | spec: 407 | selector: 408 | matchLabels: 409 | app: mysql 410 | template: 411 | metadata: 412 | labels: 413 | app: mysql 414 | spec: 415 | containers: 416 | - image: mysql:5.6 417 | name: mysql 418 | env: 419 | - name: MYSQL_ROOT_PASSWORD 420 | valueFrom: 421 | secretKeyRef: 422 | name: mysql-password 423 | key: password 424 | ports: 425 | - containerPort: 3306 426 | name: mysql 427 | volumeMounts: 428 | - name: mysql-persistent-storage 429 | mountPath: /var/lib/mysql 430 | volumes: 431 | - name: mysql-persistent-storage 432 | emptyDir: {} 433 | ``` 434 | The preceding YAML file creates a service that allows other Pods in the cluster to access the database. The Service option clusterIP: None lets the Service DNS name resolve directly to the Pod’s IP address. 435 | 436 | Save this file to secret-mysql.yaml and create pod Kubernetes object: 437 | 438 | ```console 439 | kubectl create -f secret-mysql.yaml 440 | ``` 441 | 442 | Wait for pods to be Ready: 443 | ```console 444 | kubectl get pods 445 | ``` 446 | 447 | ```output 448 | NAME READY STATUS RESTARTS AGE 449 | mysql-d4b54994b-7drd6 1/1 Running 0 15m 450 | ``` 451 | 452 | ##### Testing 453 | 454 | Run a MySQL client to connect to the server: 455 | 456 | ```console 457 | kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -pMySuperSecretPassword 458 | ``` 459 | 460 | This command creates a new Pod in the cluster running a MySQL client and connects it to the server through the Service. If it connects, you know your stateful MySQL database is up and running. 461 | 462 | ```output 463 | If you don't see a command prompt, try pressing enter. 464 | 465 | mysql> 466 | ``` 467 | 468 | -------------------------------------------------------------------------------- /2_Advanced_K8S_API/2_2_Advanced_API_Deployment.md: -------------------------------------------------------------------------------- 1 | # Using Advanced Deployment API Features 2 | 3 | ## Deployment API 4 | 5 | In Kubernetes, Deployment is the recommended way to deploy applications into Kubernetes and is now the main Object with the older `ReplicationControllers` being deprecated. Below are some of the key benefits of the Kubernetes `Deployment`object. 6 | 7 | * Deploys a ReplicaSet that manages Pod replicas. 8 | * Updates pods (PodTemplateSpec). 9 | * Rollback to older Deployment versions. 10 | * Scale Deployment up or down. 11 | * Pause and resume the Deployment. 12 | * Use the status of the Deployment to determine state of replicas. 13 | * Clean up older ReplicaSets that you don’t need anymore. 14 | * Canary Deployment and Blue/Green Deployment capabilities. 15 | 16 | ### Lab 2.6 - Create a deployment with replicas. 17 | 18 | First create a very basic deployment that will deploy an nginx containers with nginx version 1.7 19 | 20 | ```yaml 21 | apiVersion: apps/v1 22 | kind: Deployment 23 | metadata: 24 | name: nginx-deploy 25 | labels: 26 | app: nginx 27 | spec: 28 | replicas: 1 29 | selector: 30 | matchLabels: 31 | app: nginx 32 | template: 33 | metadata: 34 | labels: 35 | app: nginx 36 | spec: 37 | containers: 38 | - name: nginx 39 | image: nginx:1.7 40 | ports: 41 | - containerPort: 80 42 | ``` 43 | Save this file to base-deploy.yaml and create pod Kubernetes object: 44 | 45 | ```console 46 | kubectl apply -f base-deploy.yaml --record=true 47 | ``` 48 | 49 | Wait for pods to be Ready: 50 | ```console 51 | kubectl get pods -w 52 | ``` 53 | 54 | ```output 55 | NAME READY STATUS RESTARTS AGE 56 | nginx-deploy-6bbdfbd484-7x8kp 1/1 Running 0 45s 57 | ``` 58 | 59 | `ctrl+c to` exit the wait state 60 | 61 | ## Update Strategy 62 | Using deployments gives you the following benefits: 63 | * Seamless replication and declarative system states 64 | * the ability to update your application with minimal downtime 65 | * track multiple versions of your deployment environment. 66 | 67 | When a deployment's configuration changes, for example by updating the image used by its pods, Kubernetes detects this change and executes steps to reconcile the system state with the configuration change. The mechanism by which Kubernetes executes these updates is determined by the deployment's `strategy.type` field. 68 | 69 | ### Recreate 70 | 71 | With the Recreate strategy, all running pods in the deployment are killed, and new pods are created in their place. This does not guarantee zero-downtime, but can be useful in case you are not able to have multiple versions of your application running simultaneously. 72 | 73 | ### Lab 2.7 - Update a deployment with Recreate Strategy and update container. 74 | 75 | Let's edit the deployment so that the update strategy is recreate. 76 | 77 | ```console 78 | kubectl edit deploy nginx-deploy 79 | ``` 80 | This will open a VI editor in console. Replace the lines: 81 | ```yaml 82 | strategy: 83 | rollingUpdate: 84 | maxSurge: 25% 85 | maxUnavailable: 25% 86 | type: RollingUpdate 87 | ``` 88 | with the lines: 89 | ```yaml 90 | strategy: 91 | type: Recreate 92 | ``` 93 | save the file using vi commands and now inspect the deployment to verify the change was made 94 | 95 | ```console 96 | kubectl get deploy nginx-deploy -o yaml 97 | ``` 98 | 99 | Now scale the deployment to 6 replicas 100 | 101 | ```console 102 | kubectl scale deploy nginx-deploy --replicas=6 --record=true 103 | ``` 104 | 105 | ```output 106 | kubectl get pods 107 | NAME READY STATUS RESTARTS AGE 108 | nginx-deploy-6bbdfbd484-8dl98 1/1 Running 0 6s 109 | nginx-deploy-6bbdfbd484-brxqw 1/1 Running 0 4m 110 | nginx-deploy-6bbdfbd484-bzbjc 1/1 Running 0 6s 111 | nginx-deploy-6bbdfbd484-m7mpv 1/1 Running 0 6s 112 | nginx-deploy-6bbdfbd484-s84m9 1/1 Running 0 6s 113 | nginx-deploy-6bbdfbd484-spmq4 1/1 Running 0 6s 114 | ``` 115 | 116 | Now we will patch the deployment to have a new version of nginx from 1.7 to 1.9 117 | 118 | ```console 119 | kubectl set image deploy nginx-deploy nginx=nginx:1.9 --record=true 120 | kubectl get pods -w 121 | ``` 122 | 123 | What you should see is all of the original pods being terminated and then deleted, and rather quickly 6 new pods being created with the new version of nginx. 124 | 125 | #### RollingUpdate 126 | 127 | The preferred and more commonly used strategy is `RollingUpdate`. This gracefully updates pods one at a time to prevent your application from going down. The strategy gradually brings pods with the new configuration online, while killing old pods as the new configuration scales up. 128 | 129 | When updating your deployment with RollingUpdate, there are two useful fields you can configure: 130 | 131 | 1. `maxUnavailable` effectively determines the minimum number of pods you want running in your deployment as it updates. For example, if we have a deployment currently running ten pods and a maxUnavailable value of 4. When an update is triggered, Kubernetes will immediately kill four pods from the old configuration, bringing our total to six. Kubernetes then starts to bring up the new pods, and kills old pods as they come alive. Eventually the deployment will have ten replicas of the new pod, but at no point during the update were there fewer than six pods available. 132 | 133 | 2. `maxSurge` determines the maximum number of pods you want running in your deployment as it updates. In the previous example, if we specified a maxSurge of 3, Kubernetes could immediately create three copies of the new pod, bringing the total to 13, and then begin killing off the old versions. 134 | 135 | ### Lab 2.8 Change the Strategy to RollingUpdate leaving at least 4 pods of the old service available and no more than 10 pods at any given time 136 | 137 | Let's edit the deployment so that the update strategy is recreate. 138 | 139 | ```console 140 | kubectl edit deploy nginx-deploy 141 | ``` 142 | This will open a VI editor in console. Replace the lines: 143 | 144 | ```yaml 145 | strategy: 146 | type: Recreate 147 | ``` 148 | 149 | with the lines: 150 | 151 | ```yaml 152 | strategy: 153 | rollingUpdate: 154 | maxSurge: 4 155 | maxUnavailable: 2 156 | type: RollingUpdate 157 | ``` 158 | 159 | save the file using vi commands and now inspect the deployment to verify the change was made 160 | 161 | ```console 162 | kubetctl get deploy nginx-deploy -o yaml 163 | ``` 164 | 165 | Now we will patch the deployment to have a new version of nginx from 1.9 to 1.10 166 | 167 | ```console 168 | kubectl set image deploy nginx-deploy nginx=nginx:1.10 --record=true 169 | kubectl get pods -w 170 | ``` 171 | 172 | You will see that 4 new pods will be created and 2 old pods will be in tern=minating state and you can watch the rollout of the deployment happen. 173 | 174 | #### Rollbacks 175 | The rollback process in v1 of the Deployment API is now a manual process through the `kubectl rollout` command set. 176 | 177 | ### Lab 2.9 Roll back to nginx version 1.7 178 | 179 | Lets inspect the rollout history and rollback the deployment to use version 1.7 of nginx 180 | 181 | ```console 182 | kubectl rollout history deploy nginx-deploy 183 | ``` 184 | 185 | ```output 186 | deployments "nginx-deploy" 187 | REVISION CHANGE-CAUSE 188 | 1 kubectl scale deploy nginx-deploy --replicas=6 --record=true 189 | 2 kubectl set image deploy nginx-deploy nginx=nginx:1.9 --record=true 190 | 3 kubectl set image deploy nginx-deploy nginx=nginx:1.10 --record=true 191 | ``` 192 | Now review the revision 1 to verify it will revert the image back to 1.7 of nginx 193 | 194 | ```console 195 | kubectl rollout history deploy nginx-deploy --revision=1 196 | ``` 197 | Now rollback to revision 1. 198 | 199 | ```console 200 | kubectl rollout undo deploy nginx-deploy --to-revision=1 201 | ``` 202 | 203 | Now the pod is running nginx version 1.7 again. Verify this by executing describe on the deployment. 204 | 205 | ```command 206 | kubectl describe deploy nginx-deploy 207 | Name: nginx-deploy 208 | Namespace: default 209 | CreationTimestamp: Sun, 10 Feb 2019 21:10:23 +0000 210 | Labels: app=nginx 211 | ... 212 | Containers: 213 | nginx: 214 | Image: nginx:1.7 215 | Port: 80/TCP 216 | ... 217 | ``` 218 | 219 | ## Pod Disruption Budgets 220 | 221 | Pods do not disappear until someone (a person or a controller) destroys them, or there is an unavoidable hardware or system software error. 222 | 223 | We call these unavoidable cases **involuntary** disruptions to an application. Examples are: 224 | 225 | * a hardware failure of the physical machine backing the node 226 | * cluster administrator deletes VM (instance) by mistake 227 | * cloud provider or hypervisor failure makes VM disappear 228 | * a kernel panic 229 | * the node disappears from the cluster due to cluster network partition 230 | * eviction of a pod due to the node being out-of-resources. 231 | 232 | We call other cases **voluntary** disruptions. These include both actions initiated by the application owner and those initiated by a cluster administrator. Typical application owner actions include: 233 | 234 | * deleting the deployment or other controller that manages the pod 235 | * updating a deployment’s pod template causing a restart 236 | * directly deleting a pod (e.g. by accident) 237 | 238 | Cluster Administrator actions include: 239 | 240 | * Draining a node for repair or upgrade. 241 | * Draining a node from a cluster to scale the cluster down (learn about Cluster Autoscaling ). 242 | * Removing a pod from a node to permit something else to fit on that node. 243 | 244 | These actions might be taken directly by the cluster administrator, or by automation run by the cluster administrator, or by your cluster hosting provider. 245 | 246 | An Application Owner can create a PodDisruptionBudget object (PDB) for each application. A PDB limits the number of pods of a replicated application that are down simultaneously from voluntary disruptions. 247 | 248 | Cluster managers and hosting providers should use tools which respect Pod Disruption Budgets by calling the Eviction API instead of directly deleting pods. Examples are the `kubectl drain` command and the `az aks upgrade` command on AKS. 249 | 250 | A PDB specifies the number of replicas that an application can tolerate having, relative to how many it is intended to have. For example, a Deployment which has a `replicas: 5` is supposed to have 5 pods at any given time. If its PDB allows for there to be 4 at a time, then the Eviction API will allow voluntary disruption of one, but not two pods, at a time. 251 | 252 | **PDBs cannot prevent involuntary disruptions from occurring, but they do count against the budget.** 253 | 254 | Pods which are deleted or unavailable due to a rolling upgrade to an application do count against the disruption budget, but controllers (like deployment and stateful-set) are not limited by PDBs when doing rolling upgrades – the handling of failures during application updates is configured in the controller. 255 | 256 | When a pod is evicted using the eviction API, it is gracefully terminated 257 | 258 | ### Lab 2.9 Create a PDB for nginx-deploy that allows 5 pods at a time then drain the node with the most replicas 259 | 260 | First determine which node has the most nginx pods 261 | 262 | ```console 263 | kubectl get pods -o wide 264 | ``` 265 | 266 | ```output 267 | NAME READY STATUS RESTARTS AGE IP NODE 268 | nginx-deploy-6bbdfbd484-7d4v8 1/1 Running 0 3m 10.244.1.19 aks-nodepool1-76410264-2 269 | nginx-deploy-6bbdfbd484-8chs4 1/1 Running 0 3m 10.244.2.20 aks-nodepool1-76410264-0 270 | nginx-deploy-6bbdfbd484-9s9z7 1/1 Running 0 3m 10.244.2.19 aks-nodepool1-76410264-0 271 | nginx-deploy-6bbdfbd484-9xk97 1/1 Running 0 3m 10.244.0.14 aks-nodepool1-76410264-1 272 | nginx-deploy-6bbdfbd484-p69ht 1/1 Running 0 3m 10.244.1.20 aks-nodepool1-76410264-2 273 | nginx-deploy-6bbdfbd484-wt6g6 1/1 Running 0 3m 10.244.2.18 aks-nodepool1-76410264-0 274 | ``` 275 | In the sample output above node0 has 3 of the 6 replicas. This will be the node that will be drained 276 | 277 | Now create the Pod Disruption Budget for the Deployment 278 | 279 | ```yaml 280 | apiVersion: policy/v1beta1 281 | kind: PodDisruptionBudget 282 | metadata: 283 | name: nginx-pdb 284 | spec: 285 | minAvailable: 5 286 | selector: 287 | matchLabels: 288 | app: nginx 289 | ``` 290 | 291 | Save the files a nginx-pdb.yaml and apply it to the cluster 292 | 293 | ```console 294 | kubectl apply -f nginx-pdb.yaml 295 | ``` 296 | 297 | Verify that the disruption budget reflects the new state of the deployment 298 | 299 | ```console 300 | kubectl get pdb nginx-pdb -o yaml 301 | ``` 302 | 303 | ```output 304 | apiVersion: policy/v1beta1 305 | kind: PodDisruptionBudget 306 | ... 307 | spec: 308 | minAvailable: 5 309 | selector: 310 | matchLabels: 311 | app: nginx 312 | status: 313 | currentHealthy: 6 314 | desiredHealthy: 5 315 | disruptedPods: null 316 | disruptionsAllowed: 1 317 | expectedPods: 6 318 | observedGeneration: 1 319 | ``` 320 | 321 | Now drain the node you selected as the one with the most replicas 322 | >**Note:** You may want to open another browser window with http://shell.azure.com and run the following commands in two seperate shells. Run the watch command first and then drain the node you selected 323 | 324 | ```console 325 | kubectl drain --ignore-daemonsets=true 326 | ``` 327 | 328 | Use the watch command to see the updates happen in real time and you will notice that only a sinle container is evicted at a time to preserve the PDB created. 329 | 330 | ```console 331 | watch -d -n 1 kubectl get pods -o wide 332 | ``` 333 | -------------------------------------------------------------------------------- /3_Custom_Monitoring/3_1_Kubernetes_Metrics_architecture.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Metrics Architecture 2 | 3 | ## Metrics Server 4 | 5 | Starting from Kubernetes 1.8, resource usage metrics, such as container CPU and memory usage, are available in Kubernetes through the Metrics API. These metrics can be either accessed directly by user, for example by using kubectl top command, or used by a controller in the cluster, e.g. Horizontal Pod Autoscaler, to make decisions. 6 | 7 | Through the Metrics API you can get the amount of resources currently used by a given node or a given pod. This API doesn’t store the metric values, so it’s not possible for example to get the amount of resources used by a given node 10 minutes ago. 8 | 9 | The API is no different from any other API: 10 | 11 | It is discoverable through the same endpoint as the other Kubernetes APIs under /apis/metrics.k8s.io/ path 12 | it offers the same security, scalability and reliability guarantees. 13 | 14 | Metrics Server is a cluster-wide aggregator of resource usage data. Starting from Kubernetes 1.8 it’s deployed by default in clusters as a Deployment object. 15 | 16 | Metric server collects metrics from the Summary API, exposed by Kubelet on each node. 17 | 18 | The Metrics Server is registered to the main API server through Kubernetes aggregator. 19 | 20 | ## Horizontal Pod Autoscaler 21 | 22 | The Horizontal Pod Autoscaler automatically scales the number of pods in a replication controller, deployment or replica set based on observed CPU utilization (or, with custom metrics support, on some other application-provided metrics). Note that Horizontal Pod Autoscaling does not apply to objects that can’t be scaled, for example, DaemonSets. 23 | 24 | The Horizontal Pod Autoscaler is implemented as a Kubernetes API resource and a controller. The resource determines the behavior of the controller. The controller periodically adjusts the number of replicas in a replication controller or deployment to match the observed average CPU utilization to the target specified by user. 25 | 26 | ## Lab 3.1 Implement an autoscale based on an external metric 27 | 28 | Validate your metrics server is accessable by running: 29 | ```console 30 | kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes" | jq . 31 | ``` 32 | Create a service account for tiller and give it Kubernetes RBAC to do the thing tiller needs to do. First create a file called tillerrbac.yaml and populate it with the below yaml: 33 | 34 | ```yaml 35 | apiVersion: v1 36 | kind: ServiceAccount 37 | metadata: 38 | name: tiller 39 | namespace: kube-system 40 | --- 41 | apiVersion: rbac.authorization.k8s.io/v1 42 | kind: ClusterRoleBinding 43 | metadata: 44 | name: tiller 45 | roleRef: 46 | apiGroup: rbac.authorization.k8s.io 47 | kind: ClusterRole 48 | name: cluster-admin 49 | subjects: 50 | - kind: ServiceAccount 51 | name: tiller 52 | namespace: kube-system 53 | ``` 54 | Now create the above objects using kubectl: 55 | 56 | ```console 57 | kubectl create -f tillerrbac.yaml 58 | ``` 59 | Last, we need to intitialize helm with our aks cluster: 60 | 61 | ```console 62 | helm init --service-account tiller 63 | ``` 64 | 65 | Clone the external metrics adapter project and move into the servicebus example directory: 66 | 67 | ```console 68 | git clone https://github.com/Azure/azure-k8s-metrics-adapter 69 | cd azure-k8s-metrics-adapter/samples/servicebus-queue/ 70 | ``` 71 | 72 | ## Setup Service Bus 73 | Create a service bus in azure: 74 | 75 | ```console 76 | export SERVICEBUS_NS=sb-external-ns- 77 | ``` 78 | ```console 79 | az group create -n sb-external-example -l eastus 80 | ``` 81 | ```console 82 | az servicebus namespace create -n $SERVICEBUS_NS -g sb-external-example 83 | ``` 84 | ```cosnole 85 | az servicebus queue create -n externalq --namespace-name $SERVICEBUS_NS -g sb-external-example 86 | ``` 87 | 88 | Create an auth rule for queue: 89 | 90 | ```console 91 | az servicebus queue authorization-rule create --resource-group sb-external-example --namespace-name $SERVICEBUS_NS --queue-name externalq --name demorule --rights Listen Manage Send 92 | 93 | #save for connection string for later 94 | export SERVICEBUS_CONNECTION_STRING="$(az servicebus queue authorization-rule keys list --resource-group sb-external-example --namespace-name $SERVICEBUS_NS --name demorule --queue-name externalq -o json | jq -r .primaryConnectionString)" 95 | ``` 96 | 97 | > **Note:** this gives full access to the queue for ease of use of demo. You should create more fine grained control for each component of your app. For example the consumer app should only have `Listen` rights and the producer app should only have `Send` rights. 98 | 99 | ## Setup AKS Cluster 100 | 101 | ### Enable Access to Azure Resources 102 | 103 | Run the scripts provided to [configure a Service Principal](https://github.com/Azure/azure-k8s-metrics-adapter/blob/master/README.md#using-azure-ad-application-id-and-secret) with the following environment variables for giving the access to the Service Bus Namespace Insights provider. 104 | 105 | ### Start the producer 106 | Make sure you have cloned this repository and are in the folder `samples/servicebus-queue` for remainder of this walkthrough. 107 | 108 | download the producer app to produce some messages in the queue 109 | ```console 110 | wget -O sb-producer https://ejvlab110533.blob.core.windows.net/ejvlab/producer 111 | ``` 112 | 113 | Run the producer to create a few queue items, then hit `ctl-c` after a few message have been sent to stop it: 114 | 115 | ```console 116 | chmod +x sb-producer 117 | ./sb-producer 500 118 | ``` 119 | 120 | Check the queue has values: 121 | 122 | ```console 123 | az servicebus queue show --resource-group sb-external-example --namespace-name $SERVICEBUS_NS --name externalq -o json | jq .messageCount 124 | ``` 125 | 126 | ### Configure Secret for consumer pod 127 | Create a secret with the connection string (from [previous step](#setup-service-bus)) for the service bus: 128 | 129 | ```console 130 | kubectl create secret generic servicebuskey --from-literal=sb-connection-string=$SERVICEBUS_CONNECTION_STRING 131 | ``` 132 | 133 | ### Deploy Consumer 134 | Deploy the consumer: 135 | 136 | ```console 137 | kubectl apply -f deploy/consumer-deployment.yaml 138 | ``` 139 | 140 | Check that the consumer was able to receive messages: 141 | 142 | ```console 143 | kubectl logs -l app=consumer 144 | ``` 145 | 146 | ```output 147 | connecting to queue: externalq 148 | setting up listener 149 | listening... 150 | received message: the answer is 42 151 | number message left: 6 152 | received message: the answer is 42 153 | number message left: 5 154 | received message: the answer is 42 155 | number message left: 4 156 | received message: the answer is 42 157 | number message left: 3 158 | received message: the answer is 42 159 | number message left: 2 160 | received message: the answer is 42 161 | ``` 162 | 163 | ## Set up Azure Metrics Adapter 164 | 165 | ### Deploy the adapter 166 | 167 | Deploy the adapter: 168 | 169 | Create a Service Principal 170 | ```console 171 | az ad sp create-for-rbac 172 | ``` 173 | 174 | Use the output from the result in the fields of the helm chart below: 175 | ```console 176 | helm install --name sample-release ../../charts/azure-k8s-metrics-adapter --namespace custom-metrics --set azureAuthentication.method=clientSecret --set azureAuthentication.tenantID= --set azureAuthentication.clientID= --set azureAuthentication.clientSecret= --set azureAuthentication.createSecret=true 177 | ``` 178 | 179 | 180 | Check you can hit the external metric endpoint. The resources will be empty as it [is not implemented yet](https://github.com/Azure/azure-k8s-metrics-adapter/issues/3) but you should receive a result. 181 | 182 | ```console 183 | kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1" | jq . 184 | ``` 185 | ```output 186 | { 187 | "kind": "APIResourceList", 188 | "apiVersion": "v1", 189 | "groupVersion": "external.metrics.k8s.io/v1beta1", 190 | "resources": [] 191 | } 192 | ``` 193 | 194 | ### Configure Metric Adapter with metrics 195 | The metric adapter deploys a CRD called ExternalMetric which you can use to configure metrics. To deploy these metric we need to update the Service Bus namespace in the configuration then deploy it: 196 | 197 | ```console 198 | sed -i 's|sb-external-ns|'${SERVICEBUS_NS}'|g' deploy/externalmetric.yaml 199 | ``` 200 | ```console 201 | kubectl apply -f deploy/externalmetric.yaml 202 | ``` 203 | 204 | > **Note:** the ExternalMetric configuration is deployed per namespace. 205 | 206 | You can list of the configured external metrics via: 207 | 208 | ```console 209 | kubectl get aem 210 | ``` 211 | 212 | ### Deploy the HPA 213 | Deploy the HPA: 214 | 215 | ```console 216 | kubectl apply -f deploy/hpa.yaml 217 | ``` 218 | 219 | > **Note:** the `external.metricName` defined on the HPA must match the `metadata.name` on the ExternalMetric declaration, in this case `queuemessages` 220 | 221 | After a few seconds, validate that the HPA is configured. If the `targets` shows `` wait longer and try again. 222 | 223 | ```console 224 | kubectl get hpa consumer-scaler 225 | ``` 226 | 227 | You can also check the queue value returns manually by running: 228 | 229 | ```console 230 | kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/namespaces/default/queuemessages" | jq . 231 | ``` 232 | 233 | ## Scale! 234 | 235 | Put some load on the queue. Note this will add 20,000 message then exit. 236 | 237 | ```console 238 | ./sb-producer 0 > /dev/null & 239 | ``` 240 | 241 | Now check your queue is loaded: 242 | 243 | ```console 244 | az servicebus queue show --resource-group sb-external-example --namespace-name $SERVICEBUS_NS --name externalq -o json | jq .messageCount 245 | ``` 246 | ```output 247 | // should have a good 19,000 or more 248 | 19,858 249 | ``` 250 | 251 | Now watch your HPA pick up the queue count and scal the replicas. This will take 2 or 3 minutes due to the fact that the HPA check happens every 30 seconds and must have a value over target value. It also takes a few seconds for the metrics to be reported to the Azure endpoint that is queried. It will then scale back down after a few minutes as well. 252 | 253 | ```console 254 | kubectl get hpa consumer-scaler -w 255 | ``` 256 | 257 | ```output 258 | NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE 259 | consumer-scaler Deployment/consumer 0/30 1 10 1 1h 260 | consumer-scaler Deployment/consumer 27278/30 1 10 1 1h 261 | consumer-scaler Deployment/consumer 26988/30 1 10 4 1h 262 | consumer-scaler Deployment/consumer 26988/30 1 10 4 1h consumer-scaler Deployment/consumer 26702/30 1 10 4 1h 263 | consumer-scaler Deployment/consumer 26702/30 1 10 4 1h 264 | consumer-scaler Deployment/consumer 25808/30 1 10 4 1h 265 | consumer-scaler Deployment/consumer 25808/30 1 10 4 1h consumer-scaler Deployment/consumer 24784/30 1 10 8 1h consumer-scaler Deployment/consumer 24784/30 1 10 8 1h 266 | consumer-scaler Deployment/consumer 23775/30 1 10 8 1h 267 | consumer-scaler Deployment/consumer 22065/30 1 10 8 1h 268 | consumer-scaler Deployment/consumer 22065/30 1 10 8 1h 269 | consumer-scaler Deployment/consumer 20059/30 1 10 8 1h 270 | consumer-scaler Deployment/consumer 20059/30 1 10 10 1h 271 | ``` 272 | 273 | Once it is scaled up you can check the deployment: 274 | 275 | ```console 276 | kubectl get deployment consumer 277 | ``` 278 | 279 | ```output 280 | NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE 281 | consumer 10 10 10 10 23m 282 | ``` 283 | 284 | And check out the logs for the consumers: 285 | 286 | ```console 287 | kubectl logs -l app=consumer --tail 100 288 | ``` 289 | 290 | ## Clean up 291 | Once the queue is empty (will happen pretty quickly after scaled up to 10) you should see your deployment scale back down. 292 | 293 | Once you are done with this experiment you can delete kubernetes deployments: 294 | 295 | ```console 296 | kubectl delete -f deploy/hpa.yaml 297 | kubectl delete -f deploy/consumer-deployment.yaml 298 | helm delete --purge sample-release 299 | ``` 300 | -------------------------------------------------------------------------------- /4_Native_K8S_Automation/4_1_Admission_Controllers.md: -------------------------------------------------------------------------------- 1 | # Admission Controllers 2 | 3 | An admission controller is a piece of code that intercepts requests to the Kubernetes API server prior to persistence of the object, but after the request is authenticated and authorized. The controllers are compiled into the kube-apiserver binary, and may only be configured by the cluster administrator. 4 | 5 | There are two special controllers: 6 | * MutatingAdmissionWebhook 7 | * ValidatingAdmissionWebhook. 8 | 9 | These execute the mutating and validating (respectively) admission control webhooks which are configured in the API. 10 | 11 | Admission controllers may be “validating”, “mutating”, or both. Mutating controllers may modify the objects they admit, validating controllers may not. 12 | 13 | The admission control process proceeds in two phases. In the first phase, mutating admission controllers are run. In the second phase, validating admission controllers are run. 14 | 15 | > NOTE: A controller instance can participate in both phases. 16 | 17 | If any of the controllers in either phase reject the request, the entire request is rejected immediately and an error is returned to the end-user. 18 | 19 | Finally, in addition to sometimes mutating the object in question, admission controllers may sometimes have side effects, that is, mutate related resources as part of request processing. Incrementing quota usage is the canonical example of why this is necessary. Any such side-effect needs a corresponding reclamation or reconciliation process, as a given admission controller does not know for sure that a given request will pass all of the other admission controllers. 20 | 21 | ### MutatingAdmissionWebhook 22 | This admission controller (as implied by the name) only runs in the mutating phase. It calls any mutating webhooks which match the request. Matching webhooks are called serially - each one may modify the object if it desires. 23 | 24 | If a webhook called by this controller has side effects (for example, decrementing quota) it must have a reconciliation system, as it is not guaranteed that subsequent webhooks or validating admission controllers will permit the request to finish. 25 | 26 | If you disable the MutatingAdmissionWebhook, you must also disable the MutatingWebhookConfiguration object in the ```admissionregistration.k8s.io/v1beta1 group/version``` via the ```--runtime-config``` flag (both are on by default in versions >= 1.9). 27 | 28 | * Use caution when authoring and installing mutating webhooks 29 | * Users may be confused when the objects they try to create are different from what they get back. 30 | * Built in control loops may break when the objects they try to create are different when read back. 31 | * Setting originally unset fields is less likely to cause problems than overwriting fields set in the original request. Avoid doing the latter. 32 | * This is a beta feature. Future versions of Kubernetes may restrict the types of mutations these webhooks can make. 33 | * Future changes to control loops for built-in resources or third-party resources may break webhooks that work well today. Even when the webhook installation API is finalized, not all possible webhook behaviors will be guaranteed to be supported indefinitely. 34 | 35 | ### ValidatingAdmissionWebhook 36 | This admission controller only runs in the validation phase. The webhooks it calls may __not__ mutate the object. (This is different than webhooks called by the MutatingAdmissionWebhook admission controller.) It calls any validating webhooks which match the request. Matching webhooks are called in parallel, and if any of them rejects the request, then the request fails. 37 | 38 | If a webhook called by this has side effects (for example, decrementing quota) it must have a reconciliation system, as it is not guaranteed that subsequent webhooks or other validating admission controllers will permit the request to finish. 39 | 40 | If you disable the ValidatingAdmissionWebhook, you must also disable the ValidatingWebhookConfiguration object in the admissionregistration.k8s.io/v1beta1 group/version via the --runtime-config flag (both are on by default in versions 1.9 and later). 41 | 42 | ## Lab 4.1 - Use a MuatatingAdmissionWebhook to make all services that are of type LoadBalancer to be Internal type 43 | 44 | Clone the following repo for the AdmissionController Helm Chart 45 | 46 | ```console 47 | git clone https://github.com/evillgenius75/internallb-webhook-admission-controller.git 48 | 49 | cd internallb-webhook-admission-controller 50 | ``` 51 | 52 | Deploy the Helm Chart to your cluster 53 | 54 | ```console 55 | helm install --name admission-webhook charts/internallb-webhook-admission-controller 56 | ``` 57 | 58 | This will create a pod called `admission-webhook-internallb-webhook-admission-controller-*` 59 | Verify the Pod is running 60 | 61 | ```console 62 | kubectl get pods 63 | ``` 64 | 65 | Next, create the following deployment and service manifest. 66 | 67 | ```yaml 68 | apiVersion: v1 69 | kind: Service 70 | metadata: 71 | name: foo-svc 72 | labels: 73 | app: foo-svc 74 | spec: 75 | type: LoadBalancer 76 | ports: 77 | - port: 80 78 | targetPort: 8080 79 | protocol: TCP 80 | name: foo-http 81 | selector: 82 | app: foo-svc 83 | --- 84 | apiVersion: extensions/v1beta1 85 | kind: Deployment 86 | metadata: 87 | name: foo-svc 88 | spec: 89 | replicas: 2 90 | template: 91 | metadata: 92 | labels: 93 | app: foo-svc 94 | spec: 95 | containers: 96 | - name: foo-svc 97 | image: gcr.io/google_containers/echoserver:1.5 98 | ports: 99 | - containerPort: 8080 100 | env: 101 | - name: NODE_NAME 102 | valueFrom: 103 | fieldRef: 104 | fieldPath: spec.nodeName 105 | - name: POD_NAME 106 | valueFrom: 107 | fieldRef: 108 | fieldPath: metadata.name 109 | - name: POD_NAMESPACE 110 | valueFrom: 111 | fieldRef: 112 | fieldPath: metadata.namespace 113 | - name: POD_IP 114 | valueFrom: 115 | fieldRef: 116 | fieldPath: status.podIP 117 | ``` 118 | 119 | Deploy it. 120 | 121 | ```console 122 | kubectl apply -f admission.yaml 123 | ``` 124 | 125 | Notice that the Service type is LoadBalancer which will normally create a Public Facing Load Balancer. Because the Admission Controller was created in *Mutating* mode when the Service is deployed, it will be mutated to automatically add the correct annotation to make the LoadBalancer an Azure Internal LoadBalancer cloud resource. 126 | 127 | ```console 128 | kubectl get svc -w 129 | ``` 130 | ```output 131 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 132 | admission-webhook-internallb-webhook-admission-controller NodePort 172.23.217.199 443:32513/TCP 1h 133 | foo-svc LoadBalancer 172.23.161.50 172.22.0.97 80:32545/TCP 1h 134 | kubernetes ClusterIP 172.23.0.1 443/TCP 23d 135 | ``` 136 | 137 | You will notice that at some point the External IP of the foo-svc service will have an IP address on the Subnet Address space of your AKS cluster and not a Public IP. 138 | 139 | You can also get the description of the service and see the proper annotation was created. 140 | 141 | ```console 142 | kubectl describe svc foo-svc 143 | ``` 144 | ```output 145 | Name: foo-svc 146 | Namespace: default 147 | Labels: app=foo-svc 148 | Annotations: service.beta.kubernetes.io/azure-load-balancer-internal: true 149 | Selector: app=foo-svc 150 | Type: LoadBalancer 151 | IP: 172.23.161.50 152 | LoadBalancer Ingress: 172.22.0.97 153 | Port: foo-http 80/TCP 154 | TargetPort: 8080/TCP 155 | NodePort: foo-http 32545/TCP 156 | Endpoints: 172.22.0.16:8080,172.22.0.61:8080 157 | Session Affinity: None 158 | External Traffic Policy: Cluster 159 | Events: 160 | Type Reason Age From Message 161 | ---- ------ ---- ---- ------- 162 | Normal EnsuringLoadBalancer 64m service-controller Ensuring load balancer 163 | Normal EnsuredLoadBalancer 63m service-controller Ensured load balancer 164 | ``` 165 | 166 | Now your policy is applied automatically. 167 | -------------------------------------------------------------------------------- /5_K8S_Extensability/5_1_CRD.md: -------------------------------------------------------------------------------- 1 | # Extending Kubernetes with Custom Resources 2 | 3 | Let’s assume you want to build a clustered application or a software as a service offering. Before you start to write one single line of application code, you must address an overflowing array of architectural issues, including security, multitenancy, API gateways, CLI, configuration management, and logging. 4 | 5 | What if you could just leverage all that infrastructure from Kubernetes, save yourself few man years in development, and focus on implementing your unique service? 6 | 7 | The latest Kubernetes adds an important feature called CustomResourceDefinitions (CRD), which enables plugging in your own managed object and application as if it were a native Kubernetes component. This way, you can leverage Kubernetes CLI, API services, security and cluster management frameworks without modifying Kubernetes or knowing its internals. 8 | 9 | Since this is our last lab let's have some fun! Let's make a Game Server using Kubernetes with Project Agones! 10 | 11 | ## Lab 5.1 - "Do You Want To Play A Game?" 12 | 13 | ### Allowing UDP traffic 14 | For Agones to work correctly, we need to allow UDP traffic to pass through to our AKS cluster. To achieve this, we must update the NSG (Network Security Group) with the proper rule. A simple way to do that is: 15 | 16 | #### Update Network Security 17 | Get the underlying resource group for the AKS cluster then get the Network Security Group that needs updating: 18 | 19 | ```console 20 | RESOURCE_GROUP=$(az group list --output json --query "[?starts_with(name, 'MC_k8s_appdev_adv')].name" | jq .[0] -r) 21 | ``` 22 | ```console 23 | NSG=$(az network nsg list -g $RESOURCE_GROUP --output json --query "[].name" | jq .[0] -r) 24 | ``` 25 | 26 | Create a new Rule with UDP as the protocol and 7000-8000 as the Destination Port Ranges. 27 | 28 | ```console 29 | az network nsg rule create --name allow-game-inbound \ 30 | --nsg-name $NSG \ 31 | --resource-group $RESOURCE_GROUP \ 32 | --priority 100 \ 33 | --direction Inbound \ 34 | --access Allow \ 35 | --protocol UDP \ 36 | --destination-port-ranges 7000-8000 37 | ``` 38 | 39 | ### Creating and assigning Public IPs to Nodes 40 | Nodes in AKS don’t get a Public IP by default. One way is to manually assign a Public IP to each node by finding the Resource Group where the AKS resources are installed on the portal (it should have a name like MC_resourceGroupName_AKSName_regionName), then adding a Public IP to each VM (this can also be done through the CLI). 41 | 42 | Alternatively, a DaemonSet can be deployed to the cluster that will automatically assign a PublicIP to each node of your cluster. You will use this approach. 43 | 44 | ```console 45 | kubectl create -n kube-system -f https://raw.githubusercontent.com/dgkanatsios/AksNodePublicIPController/master/deploy.yaml 46 | ``` 47 | 48 | ### Enabling creation of RBAC resources 49 | To install Agones, a service account needs permission to create some special RBAC resource types. 50 | 51 | ```console 52 | kubectl create clusterrolebinding cluster-admin-binding \ 53 | --clusterrole=cluster-admin \ 54 | --serviceaccount=kube-system:default 55 | ``` 56 | 57 | ### Installing Agones 58 | We can install Agones to the cluster using the install.yaml file on GitHub. We will pass the URI to the file. (You can also find the install.yaml in the latest agones-install zip from the releases archive if you prefer that approach.) 59 | 60 | ```console 61 | kubectl create namespace agones-system 62 | ``` 63 | ```console 64 | kubectl apply -f https://github.com/GoogleCloudPlatform/agones/raw/release-0.7.0/install/yaml/install.yaml 65 | ``` 66 | 67 | #### Confirm that Agones started successfully 68 | To confirm Agones is up and running, run the following command: 69 | 70 | ```console 71 | kubectl describe --namespace agones-system pods 72 | ``` 73 | 74 | It should describe the single pod created in the agones-system namespace, with no error messages or status. The Conditions section should look like this: 75 | 76 | ```output 77 | ... 78 | Conditions: 79 | Type Status 80 | Initialized True 81 | Ready True 82 | PodScheduled True 83 | ``` 84 | 85 | That’s it! This creates the Custom Resource Definitions that power Agones and allows us to define resources of type GameServer. 86 | 87 | For the purpose of this guide we’re going to use the simple-udp example as the GameServer container. It's a very simple UDP server written in Go. Don’t hesitate to look at the code of this example for more information. 88 | 89 | ### Create a GameServer 90 | Let’s create a GameServer using the following command : 91 | 92 | ```console 93 | kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/agones/release-0.7.0/examples/simple-udp/gameserver.yaml 94 | ``` 95 | 96 | You should see a successful output similar to this: 97 | 98 | ```console 99 | gameserver.stable.agones.dev/simple-udp created 100 | ``` 101 | 102 | This has created a GameServer record inside Kubernetes, which has also created a backing Pod to run our simple udp game server code in. If you want to see all your running GameServers you can run: 103 | 104 | ```console 105 | kubectl get gameservers 106 | ``` 107 | 108 | It should look something like this: 109 | 110 | ```console 111 | NAME AGE 112 | simple-udp 5m 113 | ``` 114 | 115 | You can also see the Pod that got created by running `kubectl get pods`, the Pod will be prefixed by simple-udp. 116 | 117 | ```console 118 | NAME READY STATUS RESTARTS AGE 119 | simple-udp-vwxpt 2/2 Running 0 5m 120 | ``` 121 | 122 | As you can see above it says `READY: 2/2` this means there are two containers running in this Pod, this is because Agones injected the SDK sidecar for readiness and health checking of your Game Server. 123 | 124 | 125 | ### Fetch the GameServer Status 126 | Let’s wait for the GameServer state to become Ready: 127 | 128 | ```console 129 | watch kubectl describe gameserver 130 | ``` 131 | ```output 132 | Name: simple-udp-jq8kd-q8dzg 133 | Namespace: default 134 | Labels: stable.agones.dev/gameserverset=simple-udp-jq8kd 135 | Annotations: 136 | API Version: stable.agones.dev/v1alpha1 137 | Kind: GameServer 138 | ... 139 | Status: 140 | Address: 192.168.99.100 141 | Node Name: agones 142 | Ports: 143 | Name: default 144 | Port: 7614 145 | State: Ready 146 | Events: 147 | Type Reason Age From Message 148 | ---- ------ ---- ---- ------- 149 | Normal PortAllocation 23s gameserver-controller Port allocated 150 | Normal Creating 23s gameserver-controller Pod simple-udp-jq8kd-q8dzg-9kww8 created 151 | Normal Starting 23s gameserver-controller Synced 152 | Normal Ready 20s gameserver-controller Address and Port populated 153 | ``` 154 | 155 | If you look towards the bottom, you can see there is a `Status > State` value. We are waiting for it to move to Ready, which means that the game server is ready to accept connections. 156 | 157 | You might also be interested to see the Events section, which outlines when various lifecycle events of the GameSever occur. We can also see when the GameServer is ready on the event stream as well - at which time the Status > Address and Status > Port have also been populated, letting us know what IP and port our client can now connect to! 158 | 159 | ### Let’s retrieve the IP address and the allocated port of your Game Server 160 | 161 | ```console 162 | kubectl get gs -o=custom-columns=NAME:.metadata.name,STATUS:.status.state,IP:.status.address,PORT:.status.ports 163 | ``` 164 | 165 | This will output your Game Server IP address and ports, eg: 166 | 167 | ```console 168 | NAME STATUS IP PORT 169 | simple-udp Ready 192.168.99.100 [map[name:default port:7614]] 170 | ``` 171 | 172 | ### Connect to the GameServer 173 | 174 | You can now communicate with the Game Server: 175 | 176 | > NOTE: if you do not have netcat installed (i.e. you get a response of nc: command not found), you can install netcat by running sudo apt install netcat. 177 | NetCat is not installed in the Azure Cloudshell so we will need to temporarily run a utility pod that will allow us to do the call 178 | 179 | ```console 180 | kubectl run busybox -i --tty --image=busybox --restart=Never --rm -- sh 181 | ``` 182 | ```output 183 | If you don't see a command prompt, try pressing enter. 184 | / # 185 | ``` 186 | 187 | Now use the `nc` command 188 | ```console 189 | nc -u {IP} {PORT} 190 | ``` 191 | Now type a message the game will `ACK` back to you: 192 | ```console 193 | Hello World ! 194 | ``` 195 | ```output 196 | ACK: Hello World ! 197 | ``` 198 | If the message returned successfully you can finally type EXIT which tells the SDK to run the Shutdown command, and therefore shuts down the GameServer. 199 | ```console 200 | EXIT 201 | ``` 202 | 203 | If you run `kubectl describe gameserver` again - either the GameServer will be gone completely, or it will be in Shutdown state, on the way to being deleted. 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Eddie Villalba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # k8s_appdev_adv 2 | Repo for Labs covering advanced Application Integration and Kubernetes deployments 3 | -------------------------------------------------------------------------------- /Resources/Dockerfiles/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.10 as builder 2 | RUN mkdir /build 3 | ADD . /build/ 4 | WORKDIR /build 5 | RUN go get -u github.com/Azure/azure-k8s-metrics-adapter 6 | WORKDIR $GOPATH/src/github.com/Azure/azure-k8s-metrics-adapter/samples/servicebus-queue/ 7 | RUN go get -u github.com/Azure/azure-service-bus-go && make 8 | FROM frolvlad/alpine-bash 9 | COPY --from=builder /go/src/github.com/Azure/azure-k8s-metrics-adapter/samples/servicebus-queue/bin/producer /app/ 10 | WORKDIR /app 11 | CMD ["./producer"] 12 | 13 | -------------------------------------------------------------------------------- /Resources/Manifests/adapter-ex.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: adapter 5 | labels: 6 | app: adapter-ex 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: adapter-ex 12 | template: 13 | metadata: 14 | labels: 15 | app: adapter-ex 16 | spec: 17 | # Create a volume called 'shared-logs' that the 18 | # app and adapter share. 19 | volumes: 20 | - name: shared-logs 21 | emptyDir: {} 22 | containers: 23 | # Main application container 24 | - name: app-container 25 | image: alpine 26 | command: ["/bin/sh"] 27 | args: ["-c", "while true; do date > /var/log/top.txt && top -n 1 -b >> /var/log/top.txt; sleep 5;done"] 28 | # Mount the pod's shared log file into the app 29 | # container. The app writes logs here. 30 | volumeMounts: 31 | - name: shared-logs 32 | mountPath: /var/log 33 | 34 | # Adapter container 35 | - name: adapter-container 36 | image: alpine 37 | command: ["/bin/sh"] 38 | 39 | # A long command doing a simple thing: read the `top.txt` file that the 40 | # application wrote to and adapt it to fit the status file format. 41 | # Get the date from the first line, write to `status.txt` output file. 42 | # Get the first memory usage number, write to `status.txt`. 43 | # Get the first CPU usage percentage, write to `status.txt`. 44 | 45 | args: ["-c", "while true; do (cat /var/log/top.txt | head -1 > /var/log/status.txt) && (cat /var/log/top.txt | head -2 | tail -1 | grep 46 | -o -E '\\d+\\w' | head -1 >> /var/log/status.txt) && (cat /var/log/top.txt | head -3 | tail -1 | grep 47 | -o -E '\\d+%' | head -1 >> /var/log/status.txt); sleep 5; done"] 48 | 49 | 50 | # Mount the pod's shared log file into the adapter container. 51 | volumeMounts: 52 | - name: shared-logs 53 | mountPath: /var/log -------------------------------------------------------------------------------- /Resources/Manifests/ambassador-ex.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: ambassador 5 | labels: 6 | app: ambassador-ex 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: ambassador-ex 12 | template: 13 | metadata: 14 | labels: 15 | app: ambassador-ex 16 | spec: 17 | containers: 18 | - name: app-container 19 | image: evillgenius/ambassador-example-app:1.0.0 20 | ports: 21 | - containerPort: 3000 22 | - name: ambassador-container 23 | image: evillgenius/ambassador-example-ambassador:1.0.0 24 | ports: 25 | - containerPort: 8080 26 | --- 27 | kind: Service 28 | apiVersion: v1 29 | metadata: 30 | name: ambassador-svc 31 | spec: 32 | # Select which pods can be used to handle traffic to this service 33 | # based on their labels 34 | selector: 35 | app: ambassador-ex 36 | 37 | # Expose the service on a static port on each node 38 | # so that we can access the service from outside the cluster 39 | type: LoadBalancer 40 | 41 | # Ports exposed by the service. 42 | ports: 43 | # `port` is the port for the service. 44 | # `targetPort` is the port for the pods, 45 | # it has to match what's defined in the pod YAML. 46 | - port: 80 47 | targetPort: 3000 -------------------------------------------------------------------------------- /Resources/Manifests/base-deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: nginx-deployment 5 | labels: 6 | app: nginx 7 | spec: 8 | replicas: 3 9 | strategy: 10 | type: RollingUpdate 11 | rollingUpdate: 12 | maxUnavailable: 30% 13 | maxSurge: 30% 14 | selector: 15 | matchLabels: 16 | app: nginx 17 | template: 18 | metadata: 19 | labels: 20 | app: nginx 21 | spec: 22 | containers: 23 | - name: nginx 24 | image: nginx:1.7.9 25 | ports: 26 | - containerPort: 80 -------------------------------------------------------------------------------- /Resources/Manifests/hpa-simple-hpa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v1 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: php-apache 5 | spec: 6 | maxReplicas: 10 7 | minReplicas: 1 8 | scaleTargetRef: 9 | apiVersion: extensions/v1beta1 10 | kind: Deployment 11 | name: php-apache 12 | targetCPUUtilizationPercentage: 50 -------------------------------------------------------------------------------- /Resources/Manifests/hpa-simple.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: php-apache 5 | spec: 6 | ports: 7 | - port: 80 8 | protocol: TCP 9 | targetPort: 80 10 | selector: 11 | run: php-apache 12 | type: LoadBalancer 13 | --- 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | metadata: 17 | name: php-apache 18 | labels: 19 | name: php-apache 20 | spec: 21 | replicas: 1 22 | selector: 23 | matchLabels: 24 | name: php-apache 25 | template: 26 | metadata: 27 | labels: 28 | name: php-apache 29 | spec: 30 | containers: 31 | - image: k8s.gcr.io/hpa-example 32 | name: php-apache 33 | ports: 34 | - containerPort: 80 35 | resources: 36 | requests: 37 | cpu: 200m -------------------------------------------------------------------------------- /Resources/Manifests/liveready-pod.yaml: -------------------------------------------------------------------------------- 1 | kind: Pod 2 | apiVersion: v1 3 | metadata: 4 | name: liveness-readiness-pod 5 | spec: 6 | containers: 7 | - name: server 8 | image: python:2.7-alpine 9 | # Check that a container is ready to handle requests. 10 | readinessProbe: 11 | # After five seconds, check for a 12 | # 200 response on localhost:8000/ 13 | # and check only once before thinking we've failed. 14 | initialDelaySeconds: 5 15 | failureThreshold: 1 16 | httpGet: 17 | path: / 18 | port: 8000 19 | # This container starts a simple web 20 | # server after 45 seconds. 21 | env: 22 | - name: DELAY_START 23 | value: "45" 24 | command: ["/bin/sh"] 25 | args: ["-c", "echo 'Sleeping...'; sleep $(DELAY_START); echo 'Starting server...'; python -m SimpleHTTPServer"] -------------------------------------------------------------------------------- /Resources/Manifests/redis-config-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: redis 5 | spec: 6 | containers: 7 | - name: redis 8 | image: kubernetes/redis:v1 9 | env: 10 | - name: MASTER 11 | value: "true" 12 | ports: 13 | - containerPort: 6379 14 | resources: 15 | limits: 16 | cpu: "0.1" 17 | volumeMounts: 18 | - mountPath: /redis-master-data 19 | name: data 20 | - mountPath: /redis-master 21 | name: config 22 | volumes: 23 | - name: data 24 | emptyDir: {} 25 | - name: config 26 | configMap: 27 | name: redis-config 28 | items: 29 | - key: redis-config 30 | path: redis.conf -------------------------------------------------------------------------------- /Resources/Manifests/secret-mysql.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: mysql 5 | spec: 6 | ports: 7 | - port: 3306 8 | selector: 9 | app: mysql 10 | clusterIP: None 11 | --- 12 | apiVersion: apps/v1 13 | kind: Deployment 14 | metadata: 15 | name: mysql 16 | spec: 17 | selector: 18 | matchLabels: 19 | app: mysql 20 | template: 21 | metadata: 22 | labels: 23 | app: mysql 24 | spec: 25 | containers: 26 | - image: mysql:5.6 27 | name: mysql 28 | env: 29 | - name: MYSQL_ROOT_PASSWORD 30 | valueFrom: 31 | secretKeyRef: 32 | name: mysql-password 33 | key: password 34 | ports: 35 | - containerPort: 3306 36 | name: mysql 37 | volumeMounts: 38 | - name: mysql-persistent-storage 39 | mountPath: /var/lib/mysql 40 | volumes: 41 | - name: mysql-persistent-storage 42 | emptyDir: {} -------------------------------------------------------------------------------- /Resources/Manifests/security-pod.yaml: -------------------------------------------------------------------------------- 1 | kind: Pod 2 | apiVersion: v1 3 | metadata: 4 | name: security-context-pod 5 | spec: 6 | securityContext: 7 | # User ID that containers in this pod should use 8 | runAsUser: 45 9 | # Gropu ID for filesystem access 10 | fsGroup: 231 11 | volumes: 12 | - name: simple-directory 13 | emptyDir: {} 14 | containers: 15 | - name: example-container 16 | image: alpine 17 | command: ["/bin/sh"] 18 | args: ["-c", "while true; do date >> /etc/directory/file.txt; sleep 5; done"] 19 | volumeMounts: 20 | - name: simple-directory 21 | mountPath: /etc/directory -------------------------------------------------------------------------------- /Resources/Manifests/sidecar-ex.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: nodejs-hello 5 | labels: 6 | app: nodejs 7 | proxy: nginx 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: nodejs-hello 13 | template: 14 | metadata: 15 | labels: 16 | app: nodejs-hello 17 | spec: 18 | containers: 19 | - name: nodejs-hello 20 | image: evillgenius/nodehello:v1 21 | ports: 22 | - containerPort: 3000 23 | - name: nginx 24 | image: ployst/nginx-ssl-proxy 25 | env: 26 | - name: SERVER_NAME 27 | value: "appname.example.com" 28 | - name: ENABLE_SSL 29 | value: "true" 30 | - name: TARGET_SERVICE 31 | value: "localhost:3000" 32 | volumeMounts: 33 | - name: ssl-keys 34 | readOnly: true 35 | mountPath: "/etc/secrets" 36 | ports: 37 | - containerPort: 80 38 | containerPort: 443 39 | volumes: 40 | - name: ssl-keys 41 | secret: 42 | secretName: ssl-key-secret --------------------------------------------------------------------------------