├── .build
└── build.sh
├── .gitattributes
├── .gitignore
├── Bonus
├── gcloud
│ ├── Dockerfile
│ ├── README.md
│ ├── attach.sh
│ ├── create.sh
│ ├── delete.sh
│ └── spawn.sh
├── phpMyAdmin
│ ├── Dockerfile
│ ├── README.md
│ ├── config
│ │ └── config.user.inc.php
│ └── docker-compose.yml
└── vpa
│ ├── deploy.yaml
│ ├── load-job.yaml
│ ├── svc.yaml
│ ├── vpa-advisor.yaml
│ └── vpa.yaml
├── CONTRIBUTING.md
├── Chapter02-ruby
├── 2.1.3_Dockerfile
│ ├── Dockerfile
│ └── hello.rb
├── 2.1.4_BaseImage
│ ├── Dockerfile
│ └── hello.rb
├── 2.1.5_DefaultCommand
│ ├── Dockerfile
│ └── hello.rb
├── 2.1.6_Dependencies-2
│ ├── Dockerfile
│ └── helloworld.rb
└── 2.1.6_Dependencies
│ ├── Dockerfile
│ └── helloworld.rb
├── Chapter02-swift
├── 2.1.7_CompiledCode
│ ├── Dockerfile
│ ├── Package.swift
│ └── Source
│ │ └── main.swift
└── 2.1.8_MultiStage
│ ├── Dockerfile
│ ├── Package.swift
│ └── Source
│ └── main.swift
├── Chapter02
├── 2.1.3_Dockerfile
│ ├── Dockerfile
│ └── hello.py
├── 2.1.4_BaseImage
│ ├── Dockerfile
│ └── hello.py
├── 2.1.5_DefaultCommand
│ ├── Dockerfile
│ └── hello.py
├── 2.1.6_Dependencies-2
│ ├── Dockerfile
│ └── hello.py
├── 2.1.6_Dependencies
│ ├── Dockerfile
│ └── hello.py
├── 2.1.7_CompiledCode
│ ├── Dockerfile
│ └── Hello.java
├── 2.1.8_MultiStage
│ ├── Dockerfile
│ └── Hello.java
├── 2.3.1_VolumeMount
│ ├── Dockerfile
│ ├── docker-compose.yaml
│ └── server.py
├── 2.3.2_MultipleServices
│ └── docker-compose.yaml
├── 2.3.3_Fakes
│ └── docker-compose.yaml
├── 2.3_Compose
│ └── docker-compose.yaml
└── timeserver
│ ├── Dockerfile
│ └── server.py
├── Chapter03
├── 3.2.4_ThePodSpec
│ └── pod.yaml
├── 3.2.7_Updating
│ └── deploy.yaml
├── 3.2_DeployingToKubernetes
│ ├── deploy.yaml
│ └── service.yaml
└── 3.4.3_LocalDevelopment
│ ├── deploy.yaml
│ └── svc.yaml
├── Chapter04
├── 4.1.2_Readiness
│ └── deploy.yaml
├── 4.1.3_Liveness
│ └── deploy.yaml
├── 4.1.4_GoodHealthChecks
│ └── deploy.yaml
├── 4.2.1_RollingUpdate
│ └── deploy.yaml
├── 4.2.2_Recreate
│ └── deploy.yaml
├── 4.2.3_BlueGreen
│ ├── deploy-blue.yaml
│ ├── deploy-green.yaml
│ └── service.yaml
├── timeserver2
│ ├── Dockerfile
│ └── server.py
└── timeserver3
│ ├── Dockerfile
│ └── server.py
├── Chapter05
├── 5.1.1_PodResources
│ ├── deploy_requests.yaml
│ └── deploy_requests_limits.yaml
├── 5.1.3_Priority
│ ├── deploy.yaml
│ ├── priorityclass-preemption.yaml
│ ├── priorityclass.yaml
│ └── test
│ │ ├── README.md
│ │ ├── deploy_high_priority.yaml
│ │ ├── deploy_no_priority.yaml
│ │ └── priorityclass-preemption.yaml
└── 5.2_ResourceUsageTest
│ └── deploy.yaml
├── Chapter06
├── 6.1_Replicas
│ └── deploy.yaml
├── 6.2.1_ExternalMetricGCP
│ ├── deploy.yaml
│ ├── hpa.yaml
│ ├── ingress.yaml
│ └── service.yaml
├── 6.2_HPA
│ ├── deploy.yaml
│ ├── hpa.yaml
│ └── service.yaml
├── 6.3.2_PlaceholderPod
│ ├── default-priority.yaml
│ ├── placeholder-deploy.yaml
│ └── placeholder-priority.yaml
└── timeserver4
│ ├── Dockerfile
│ ├── pi.py
│ └── server.py
├── Chapter07
├── 7.1_InternalServices
│ ├── robohash-deploy.yaml
│ ├── robohash-service.yaml
│ ├── timeserver-deploy-dns.yaml
│ ├── timeserver-deploy-env.yaml
│ └── timeserver-service.yaml
├── 7.2.1_TLS
│ └── ingress_tls.yaml
├── 7.2_Ingress
│ ├── ingress_host.yaml
│ ├── ingress_path.yaml
│ ├── robohash-deploy.yaml
│ ├── robohash-service.yaml
│ ├── timeserver-deploy-dns.yaml
│ └── timeserver-service-internal.yaml
└── timeserver5
│ ├── Dockerfile
│ ├── build.sh
│ ├── docker-compose.yaml
│ └── server.py
├── Chapter08
├── 8.1.1_NodeSelection
│ ├── deploy_nodeaffinity-autopilot.yaml
│ ├── deploy_nodeaffinity.yaml
│ ├── deploy_nodeselector-autopilot.yaml
│ └── deploy_nodeselector.yaml
├── 8.1.2_NodeAffinity
│ ├── deploy_nodeaffinity_multi-autopilot.yaml
│ ├── deploy_nodeaffinity_multi.yaml
│ ├── deploy_nodeaffinity_preferred-autopilot.yaml
│ └── deploy_nodeaffinity_preferred.yaml
├── 8.1.3_Taints
│ ├── daemonset_tolerate_all_taints.yaml
│ ├── daemonset_tolerate_antiaffinity.yaml
│ └── deploy_tolerate_spot.yaml
├── 8.1.4_WorkloadSeparation
│ ├── deploy_group1.yaml
│ └── deploy_group2.yaml
├── 8.2.1_TopologySpread
│ └── deploy_topology.yaml
├── 8.2.2_Colocation
│ ├── backend.yaml
│ └── frontend.yaml
└── 8.2.3_PodAntiAffinity
│ ├── backend.yaml
│ └── frontend.yaml
├── Chapter09
├── 9.1.1_Volume
│ ├── emptydir_pod.yaml
│ └── mariadb_pod.yaml
├── 9.1.2_PersistentVolume
│ └── pvc-mariadb.yaml
├── 9.1.3_StorageClass
│ ├── storageclass.yaml
│ └── storageclass2.yaml
├── 9.1.4_Deployment_MariaDB
│ ├── mariadb-deploy.yaml
│ └── service.yaml
├── 9.2.1_StatefulSet_MariaDB
│ └── mariadb-statefulset.yaml
├── 9.2.1_StatefulSet_Redis
│ ├── redis-configmap.yaml
│ └── redis-statefulset.yaml
├── 9.2.2_StatefulSet_Redis_Multi
│ ├── redis-configmap.yaml
│ └── redis-statefulset.yaml
├── 9.4_EphemeralVolume
│ ├── ephemeral_storageclass.yaml
│ └── ephemeralvolume_pod.yaml
└── storageclass.yaml
├── Chapter10
├── 10.1.1_TaskQueue
│ └── deploy_worker.yaml
├── 10.1.2_TaskQueue2
│ └── deploy_worker.yaml
├── 10.1.3_HPA-v1
│ └── worker_hpa_v1.yaml
├── 10.1.3_HPA
│ └── worker_hpa.yaml
├── 10.2.1_Job
│ └── job_addwork.yaml
├── 10.2.2_CronJob
│ └── cronjob_addwork.yaml
├── 10.2.2_CronJobBeta
│ └── cronjob_addwork_beta.yaml
├── 10.3.1_JobWorker
│ └── job_worker.yaml
├── 10.3.2_IndexedJob
│ └── indexed_job.yaml
├── 10.4_TaskLiveness
│ ├── deploy_worker.yaml
│ └── job_worker.yaml
├── pi_worker
│ ├── Dockerfile
│ ├── README.md
│ ├── add_tasks.py
│ ├── pi.py
│ ├── pi_test.py
│ └── pi_worker.py
├── pi_worker2
│ ├── Dockerfile
│ ├── README.md
│ ├── add_tasks.py
│ ├── pi.py
│ ├── pi_test.py
│ └── pi_worker.py
├── pi_worker3
│ ├── Dockerfile
│ ├── README.md
│ ├── add_tasks.py
│ ├── pi.py
│ ├── pi_test.py
│ └── pi_worker.py
└── pi_worker4
│ ├── Dockerfile
│ ├── README.md
│ ├── add_tasks.py
│ ├── check_liveness.sh
│ ├── liveness.py
│ ├── pi.py
│ ├── pi_test.py
│ └── pi_worker.py
├── Chapter11
├── 11.3.2_CloudBuild
│ └── cloudbuild-deploy.yaml
├── 11.4.1_StringSecrets
│ ├── deploy.yaml
│ └── secret.yaml
├── 11.4.2_Base64Secrets
│ ├── secret-base64.yaml
│ └── secrets-multiple.yaml
├── 11.4.3_FileSecrets
│ ├── deploy.yaml
│ ├── example.key
│ ├── secret_file.yaml
│ └── secret_file_base64.yaml
└── gitops
│ ├── _debug
│ └── delete_evicated.sh
│ ├── _ns
│ ├── ns-production.yaml
│ └── ns-staging.yaml
│ ├── gitops_check.sh
│ ├── production
│ └── rollout.sh
│ └── staging
│ └── rollout.sh
├── Chapter12
├── 12.1_PDB
│ └── pdb.yaml
├── 12.2_DaemonSet
│ └── logreader.yaml
├── 12.3_PodSecurityContext
│ ├── admin-ds.yaml
│ └── pod.yaml
├── 12.4_NonRootContainers
│ ├── 1_permission_error
│ │ ├── deploy-runas.yaml
│ │ ├── deploy.yaml
│ │ └── service.yaml
│ └── 2_fixed
│ │ ├── deploy.yaml
│ │ └── service.yaml
├── 12.5_PodSecurityAdmission
│ ├── namespace.yaml
│ └── nonroot_pod.yaml
├── 12.6_RBAC
│ ├── clusterrole.yaml
│ ├── clusterrolebinding.yaml
│ ├── role.yaml
│ └── rolebinding.yaml
├── timeserver6
│ ├── Dockerfile
│ └── server.py
└── timeserver7
│ ├── Dockerfile
│ └── server.py
├── Denniss-Kubernetes-720.png
├── Denniss-Kubernetes-HI.png
├── LICENSE
└── README.md
/.build/build.sh:
--------------------------------------------------------------------------------
1 | # build.sh
2 | # Build and push docker images to DockerHub
3 |
4 | cd ..
5 |
6 | # Timeserver
7 | docker build ./Chapter02/timeserver -t docker.io/wdenniss/timeserver:1 --push
8 | docker build ./Chapter04/timeserver2 -t docker.io/wdenniss/timeserver:2 --push
9 | docker build ./Chapter04/timeserver3 -t docker.io/wdenniss/timeserver:3 --push
10 | docker build ./Chapter06/timeserver4 -t docker.io/wdenniss/timeserver:4 --push
11 | docker build ./Chapter07/timeserver5 -t docker.io/wdenniss/timeserver:5 --push
12 | docker build ./Chapter12/timeserver6 -t docker.io/wdenniss/timeserver:6 --push
13 | docker build ./Chapter12/timeserver7 -t docker.io/wdenniss/timeserver:7 --push
14 | docker tag docker.io/wdenniss/timeserver:7 docker.io/wdenniss/timeserver:latest
15 | docker push docker.io/wdenniss/timeserver:latest
16 |
17 | # Pi worker
18 | docker build ./Chapter10/pi_worker -t docker.io/wdenniss/pi_worker:1 --push
19 | docker build ./Chapter10/pi_worker2 -t docker.io/wdenniss/pi_worker:2 --push
20 | docker build ./Chapter10/pi_worker3 -t docker.io/wdenniss/pi_worker:3 --push
21 | docker build ./Chapter10/pi_worker4 -t docker.io/wdenniss/pi_worker:4 --push
22 | docker tag docker.io/wdenniss/pi_worker:4 docker.io/wdenniss/pi_worker:latest
23 | docker push docker.io/wdenniss/pi_worker:latest
24 |
25 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=lf
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | scratch
2 |
--------------------------------------------------------------------------------
/Bonus/gcloud/Dockerfile:
--------------------------------------------------------------------------------
1 | # Build our own customized container that includes the gcloud CLI
2 | FROM google/cloud-sdk
3 | RUN apt-get update
4 | # Customize this package list with your favorite utilities
5 | RUN apt-get install -y watch vim kubectx
6 | COPY ../../ /kubernetes-quickly
7 | WORKDIR /kubernetes-quickly
8 |
--------------------------------------------------------------------------------
/Bonus/gcloud/README.md:
--------------------------------------------------------------------------------
1 | Ever wanted to be able to run a separate gcloud environment? That's what `google/cloud-sdk` can be used for.
2 |
3 | These scripts help you create a gcloud environment you can keep around with your favorite customizations, without
4 | impacting the config on your local machine. Great if you want to have multiple contexts for both gcloud and
5 | kubectl, or if you simply don't want to install anything on your local machine besides docker.
6 |
7 | Usage:
8 |
9 | ```
10 | # create a gcloud container, with this repository mounted at /kubernetes-quickly (run once)
11 | ./create.sh
12 |
13 | # start and attach to an already created container (run each time you want to use the environment)
14 | ./attach.sh
15 |
16 | # Once in the container:
17 | gcloud auth login
18 | # ...
19 | # when you're done:
20 | exit
21 |
22 | # From another terminal window, run to get an additional shell in the gcloud container
23 | ./spawn.sh
24 |
25 | # When you no longer need the container (NB. the container can be re-used, keeping your authentication state -- no need to delete after every use)
26 | ./delete.sh
27 | ```
28 |
--------------------------------------------------------------------------------
/Bonus/gcloud/attach.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Start and attach to the gcloud container (it must have been created earlier with create.sh)
4 | docker start gcloud
5 | docker attach gcloud
6 |
7 |
--------------------------------------------------------------------------------
/Bonus/gcloud/create.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Build the container image and tag it gcloud_container
4 | docker build -t gcloud_container .
5 |
6 | # Create a container named gcloud using the image named gcloud_container, and mount the example files
7 | docker create -it --mount type=bind,source="$(pwd)/../..",target=/kubernetes-quickly --name gcloud gcloud_container bash
8 |
--------------------------------------------------------------------------------
/Bonus/gcloud/delete.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Delete the container named gcloud
4 | docker rm gcloud
5 |
--------------------------------------------------------------------------------
/Bonus/gcloud/spawn.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Spawn a new shell in the gcloud container (it must be running with attach.sh)
4 | docker start gcloud
5 | docker exec -it gcloud bash
6 |
--------------------------------------------------------------------------------
/Bonus/phpMyAdmin/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM phpmyadmin/phpmyadmin
2 | ADD config/ /etc/phpmyadmin/
3 |
--------------------------------------------------------------------------------
/Bonus/phpMyAdmin/README.md:
--------------------------------------------------------------------------------
1 | How to use this phpMyAdmin Docker Compose setup:
2 |
3 | 1. Edit the `config/config.user.inc.php` file and add your own server details.
4 | 2. Add your SSL certs to the `config/` directory, and check the names in the config to ensure they're pointing at the right files.
5 | 3. Run `docker-compose build; docker-compose up`
6 | 4. Connect to http://localhost:8079
7 |
8 | About:
9 |
10 | The phpMyAdmin config is about the thing you'd change with a custom installation. This docker image shows how you inherit from the base phpMyAdmin image and change just the configuration that you need. I find it convenient to bundle the SSL certs in this image, and using docker-compose to run everything to make this portable, and repeatable.
--------------------------------------------------------------------------------
/Bonus/phpMyAdmin/config/config.user.inc.php:
--------------------------------------------------------------------------------
1 | .
11 | *
12 | * @package PhpMyAdmin
13 | */
14 |
15 | /**
16 | * This is needed for cookie based authentication to encrypt password in
17 | * cookie. Needs to be 32 chars long.
18 | */
19 | $cfg['blowfish_secret'] = 'YcGH28i8oDhpbOCJAOq029YCans29XwQ'; /* YOU MUST FILL IN THIS FOR COOKIE AUTH! */
20 |
21 | /**
22 | * Servers configuration
23 | */
24 | $i = 0;
25 |
26 | $cfg['AllowArbitraryServer'] = true;
27 |
28 | /**
29 | * First server
30 | */
31 |
32 | /* GCP Cloud SQL */
33 | $i++;
34 | # https://docs.phpmyadmin.net/en/latest/setup.html#ssl
35 | /* Authentication type */
36 | $cfg['Servers'][$i]['auth_type'] = 'cookie';
37 | /* Server parameters */
38 | $cfg['Servers'][$i]['host'] = ''; # CONFIG NEEDED: Your Cloud SQL Host IP
39 | $cfg['Servers'][$i]['compress'] = false;
40 | $cfg['Servers'][$i]['AllowNoPassword'] = false;
41 | $cfg['Servers'][$i]['ssl'] = true;
42 | # CONFIG NEEDED: Download these files from your Cloud SQL instance and add them to the config/ directory (they will be placed in /etc/phpmyadmin when the image is built)
43 | $cfg['Servers'][$i]['ssl_ca'] = '/etc/phpmyadmin/gcp-server-ca.pem';
44 | $cfg['Servers'][$i]['ssl_key'] = '/etc/phpmyadmin/gcp-client-key.pem';
45 | $cfg['Servers'][$i]['ssl_cert'] = '/etc/phpmyadmin/gcp-client-cert.pem';
46 | $cfg['Servers'][$i]['ssl_verify'] = false;
47 |
48 | $i++;
49 | /* Authentication type */
50 | $cfg['Servers'][$i]['auth_type'] = 'cookie';
51 | /* Server parameters */
52 | $cfg['Servers'][$i]['host'] = 'host.docker.internal';
53 | $cfg['Servers'][$i]['compress'] = false;
54 | $cfg['Servers'][$i]['AllowNoPassword'] = false;
55 | $cfg['Servers'][$i]['ssl'] = false;
56 |
57 | /* AWS RDS */
58 |
59 | $i++;
60 | /* Authentication type */
61 | $cfg['Servers'][$i]['auth_type'] = 'cookie';
62 | /* Server parameters */
63 | $cfg['Servers'][$i]['host'] = 'YOUR_HOST.rds.amazonaws.com'; # CONFIG NEEDED: Your RDS Hostname
64 | $cfg['Servers'][$i]['compress'] = false;
65 | $cfg['Servers'][$i]['AllowNoPassword'] = false;
66 | $cfg['Servers'][$i]['ssl'] = true;
67 | # CONFIG NEEDED: Download the latest RDS certificate file here: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html
68 | # add them to the config/ directory (they will be placed in /etc/phpmyadmin when the image is built)
69 | $cfg['Servers'][$i]['ssl_ca'] = '/etc/phpmyadmin/amazon-rds-ca-cert.pem';
70 |
71 | /**
72 | * phpMyAdmin configuration storage settings.
73 | */
74 |
75 | /* User used to manipulate with storage */
76 | // $cfg['Servers'][$i]['controlhost'] = '';
77 | // $cfg['Servers'][$i]['controlport'] = '';
78 | // $cfg['Servers'][$i]['controluser'] = 'pma';
79 | // $cfg['Servers'][$i]['controlpass'] = 'pmapass';
80 |
81 | /* Storage database and tables */
82 | // $cfg['Servers'][$i]['pmadb'] = 'phpmyadmin';
83 | // $cfg['Servers'][$i]['bookmarktable'] = 'pma__bookmark';
84 | // $cfg['Servers'][$i]['relation'] = 'pma__relation';
85 | // $cfg['Servers'][$i]['table_info'] = 'pma__table_info';
86 | // $cfg['Servers'][$i]['table_coords'] = 'pma__table_coords';
87 | // $cfg['Servers'][$i]['pdf_pages'] = 'pma__pdf_pages';
88 | // $cfg['Servers'][$i]['column_info'] = 'pma__column_info';
89 | // $cfg['Servers'][$i]['history'] = 'pma__history';
90 | // $cfg['Servers'][$i]['table_uiprefs'] = 'pma__table_uiprefs';
91 | // $cfg['Servers'][$i]['tracking'] = 'pma__tracking';
92 | // $cfg['Servers'][$i]['userconfig'] = 'pma__userconfig';
93 | // $cfg['Servers'][$i]['recent'] = 'pma__recent';
94 | // $cfg['Servers'][$i]['favorite'] = 'pma__favorite';
95 | // $cfg['Servers'][$i]['users'] = 'pma__users';
96 | // $cfg['Servers'][$i]['usergroups'] = 'pma__usergroups';
97 | // $cfg['Servers'][$i]['navigationhiding'] = 'pma__navigationhiding';
98 | // $cfg['Servers'][$i]['savedsearches'] = 'pma__savedsearches';
99 | // $cfg['Servers'][$i]['central_columns'] = 'pma__central_columns';
100 | // $cfg['Servers'][$i]['designer_settings'] = 'pma__designer_settings';
101 | // $cfg['Servers'][$i]['export_templates'] = 'pma__export_templates';
102 |
103 | /**
104 | * End of servers configuration
105 | */
106 |
107 | $cfg['LoginCookieValidity'] = 36000; // 10h login validity
108 |
109 | /**
110 | * Directories for saving/loading files from server
111 | */
112 | $cfg['UploadDir'] = '';
113 | $cfg['SaveDir'] = '';
114 |
115 | /**
116 | * Whether to display icons or text or both icons and text in table row
117 | * action segment. Value can be either of 'icons', 'text' or 'both'.
118 | * default = 'both'
119 | */
120 | //$cfg['RowActionType'] = 'icons';
121 |
122 | /**
123 | * Defines whether a user should be displayed a "show all (records)"
124 | * button in browse mode or not.
125 | * default = false
126 | */
127 | //$cfg['ShowAll'] = true;
128 |
129 | /**
130 | * Number of rows displayed when browsing a result set. If the result
131 | * set contains more rows, "Previous" and "Next".
132 | * Possible values: 25, 50, 100, 250, 500
133 | * default = 25
134 | */
135 | //$cfg['MaxRows'] = 50;
136 |
137 | /**
138 | * Disallow editing of binary fields
139 | * valid values are:
140 | * false allow editing
141 | * 'blob' allow editing except for BLOB fields
142 | * 'noblob' disallow editing except for BLOB fields
143 | * 'all' disallow editing
144 | * default = 'blob'
145 | */
146 | //$cfg['ProtectBinary'] = false;
147 |
148 | /**
149 | * Default language to use, if not browser-defined or user-defined
150 | * (you find all languages in the locale folder)
151 | * uncomment the desired line:
152 | * default = 'en'
153 | */
154 | //$cfg['DefaultLang'] = 'en';
155 | //$cfg['DefaultLang'] = 'de';
156 |
157 | /**
158 | * How many columns should be used for table display of a database?
159 | * (a value larger than 1 results in some information being hidden)
160 | * default = 1
161 | */
162 | //$cfg['PropertiesNumColumns'] = 2;
163 |
164 | /**
165 | * Set to true if you want DB-based query history.If false, this utilizes
166 | * JS-routines to display query history (lost by window close)
167 | *
168 | * This requires configuration storage enabled, see above.
169 | * default = false
170 | */
171 | //$cfg['QueryHistoryDB'] = true;
172 |
173 | /**
174 | * When using DB-based query history, how many entries should be kept?
175 | * default = 25
176 | */
177 | //$cfg['QueryHistoryMax'] = 100;
178 |
179 | /**
180 | * Whether or not to query the user before sending the error report to
181 | * the phpMyAdmin team when a JavaScript error occurs
182 | *
183 | * Available options
184 | * ('ask' | 'always' | 'never')
185 | * default = 'ask'
186 | */
187 | //$cfg['SendErrorReports'] = 'always';
188 |
189 | /**
190 | * You can find more configuration options in the documentation
191 | * in the doc/ folder or at .
192 | */
193 |
--------------------------------------------------------------------------------
/Bonus/phpMyAdmin/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | sync:
4 | build: .
5 | ports:
6 | - "8079:80"
7 |
--------------------------------------------------------------------------------
/Bonus/vpa/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 5
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:4
18 | resources:
19 | requests:
20 | cpu: 50m
21 | memory: 50Mi
22 | ephemeral-storage: 5Gi
23 | limits:
24 | cpu: 70m
25 | memory: 70Mi
--------------------------------------------------------------------------------
/Bonus/vpa/load-job.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: Job
3 | metadata:
4 | name: load-generate
5 | spec:
6 | backoffLimit: 1000
7 | completions: 1000
8 | template:
9 | spec:
10 | containers:
11 | - name: ab-container
12 | image: docker.io/jordi/ab
13 | command: ["ab", "-n", "1000000000", "-c", "20", "-s", "120", "http://timeserver/"]
14 | restartPolicy: OnFailure
15 |
--------------------------------------------------------------------------------
/Bonus/vpa/svc.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: timeserver
5 | spec:
6 | selector:
7 | pod: timeserver-pod
8 | ports:
9 | - port: 80
10 | targetPort: 80
11 | protocol: TCP
12 |
--------------------------------------------------------------------------------
/Bonus/vpa/vpa-advisor.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: autoscaling.k8s.io/v1
2 | kind: VerticalPodAutoscaler
3 | metadata:
4 | name: timeserver
5 | spec:
6 | targetRef:
7 | apiVersion: "apps/v1"
8 | kind: Deployment
9 | name: timeserver
10 | updatePolicy:
11 | updateMode: "Off"
12 |
13 |
--------------------------------------------------------------------------------
/Bonus/vpa/vpa.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: autoscaling.k8s.io/v1
2 | kind: VerticalPodAutoscaler
3 | metadata:
4 | name: timeserver-vpa
5 | spec:
6 | targetRef:
7 | apiVersion: "apps/v1"
8 | kind: Deployment
9 | name: timeserver
10 | updatePolicy:
11 | updateMode: "Auto"
12 | minReplicas: 1
13 | resourcePolicy:
14 | containerPolicies:
15 | - containerName: '*'
16 | minAllowed:
17 | cpu: "50m"
18 | memory: "50Mi"
19 | maxAllowed:
20 | cpu: "2"
21 | memory: "2Gi"
22 | controlledValues: RequestsAndLimits
23 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows [Google's Open Source Community
28 | Guidelines](https://opensource.google.com/conduct/).
29 |
--------------------------------------------------------------------------------
/Chapter02-ruby/2.1.3_Dockerfile/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu
2 | RUN apt-get update
3 | RUN apt-get install -y ruby
4 | COPY . /app
5 | WORKDIR /app
6 |
--------------------------------------------------------------------------------
/Chapter02-ruby/2.1.3_Dockerfile/hello.rb:
--------------------------------------------------------------------------------
1 | puts "Hello Docker"
2 |
--------------------------------------------------------------------------------
/Chapter02-ruby/2.1.4_BaseImage/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby
2 | COPY . /app
3 | WORKDIR /app
4 |
--------------------------------------------------------------------------------
/Chapter02-ruby/2.1.4_BaseImage/hello.rb:
--------------------------------------------------------------------------------
1 | puts "Hello Docker"
2 |
--------------------------------------------------------------------------------
/Chapter02-ruby/2.1.5_DefaultCommand/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby
2 | COPY . /app
3 | WORKDIR /app
4 | CMD ruby hello.rb
5 |
--------------------------------------------------------------------------------
/Chapter02-ruby/2.1.5_DefaultCommand/hello.rb:
--------------------------------------------------------------------------------
1 | puts "Hello Docker"
2 |
--------------------------------------------------------------------------------
/Chapter02-ruby/2.1.6_Dependencies-2/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby
2 | RUN apt-get update -qq
3 | RUN apt-get install -y bsdtar
4 | RUN mkdir -p ~/.fonts; cd ~/.fonts
5 | RUN curl "https://noto-website-2.storage.googleapis.com/pkgs/Noto-hinted.zip" | bsdtar -xvf-
6 | RUN fc-cache -f -v #D
7 | COPY . /app
8 | WORKDIR /app
9 | CMD ruby helloworld.rb
10 |
--------------------------------------------------------------------------------
/Chapter02-ruby/2.1.6_Dependencies-2/helloworld.rb:
--------------------------------------------------------------------------------
1 | puts "Hello World"
2 |
--------------------------------------------------------------------------------
/Chapter02-ruby/2.1.6_Dependencies/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby
2 | RUN apt-get update -qq
3 | RUN apt-get install -y mariadb-client
4 | COPY . /app
5 | WORKDIR /app
6 | CMD ruby helloworld.rb
7 |
--------------------------------------------------------------------------------
/Chapter02-ruby/2.1.6_Dependencies/helloworld.rb:
--------------------------------------------------------------------------------
1 | puts "Hello World"
2 |
--------------------------------------------------------------------------------
/Chapter02-swift/2.1.7_CompiledCode/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM swift:5.0
2 | COPY . /app
3 | WORKDIR /app
4 | RUN swift build -c release
5 | RUN cp .build/x86_64-unknown-linux/release/ContainerizedSwift .
6 | CMD ./ContainerizedSwift
7 |
--------------------------------------------------------------------------------
/Chapter02-swift/2.1.7_CompiledCode/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:4.2
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "ContainerizedSwift",
7 | dependencies: [
8 | ],
9 | targets: [
10 | .target(
11 | name: "ContainerizedSwift",
12 | dependencies: [],
13 | path: ".",
14 | sources: ["Source"]
15 | )
16 | ]
17 | )
18 |
--------------------------------------------------------------------------------
/Chapter02-swift/2.1.7_CompiledCode/Source/main.swift:
--------------------------------------------------------------------------------
1 | print("Swift in a container")
2 |
--------------------------------------------------------------------------------
/Chapter02-swift/2.1.8_MultiStage/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM swift:5.0 AS buildstage
2 | COPY . /app
3 | WORKDIR /app
4 | RUN swift build -c release
5 | RUN cp .build/x86_64-unknown-linux/release/ContainerizedSwift .
6 |
7 | FROM swift:5.0-slim
8 | COPY --from=buildstage /app/ContainerizedSwift /app/
9 | WORKDIR /app
10 | CMD ./ContainerizedSwift
11 |
--------------------------------------------------------------------------------
/Chapter02-swift/2.1.8_MultiStage/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:4.2
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "ContainerizedSwift",
7 | dependencies: [
8 | ],
9 | targets: [
10 | .target(
11 | name: "ContainerizedSwift",
12 | dependencies: [],
13 | path: ".",
14 | sources: ["Source"]
15 | )
16 | ]
17 | )
18 |
--------------------------------------------------------------------------------
/Chapter02-swift/2.1.8_MultiStage/Source/main.swift:
--------------------------------------------------------------------------------
1 | print("Swift in a container")
2 |
--------------------------------------------------------------------------------
/Chapter02/2.1.3_Dockerfile/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu
2 | RUN apt-get update
3 | RUN apt-get install -y python3
4 | COPY . /app
5 | WORKDIR /app
6 |
--------------------------------------------------------------------------------
/Chapter02/2.1.3_Dockerfile/hello.py:
--------------------------------------------------------------------------------
1 | print ('Hello Docker')
2 |
--------------------------------------------------------------------------------
/Chapter02/2.1.4_BaseImage/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3
2 | COPY . /app
3 | WORKDIR /app
4 |
--------------------------------------------------------------------------------
/Chapter02/2.1.4_BaseImage/hello.py:
--------------------------------------------------------------------------------
1 | print ('Hello Docker')
2 |
--------------------------------------------------------------------------------
/Chapter02/2.1.5_DefaultCommand/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3
2 | COPY . /app
3 | WORKDIR /app
4 | CMD python3 hello.py
5 |
--------------------------------------------------------------------------------
/Chapter02/2.1.5_DefaultCommand/hello.py:
--------------------------------------------------------------------------------
1 | print ('Hello Docker')
2 |
--------------------------------------------------------------------------------
/Chapter02/2.1.6_Dependencies-2/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3
2 | RUN apt-get update
3 | RUN apt-get install -y libarchive-tools
4 | RUN mkdir -p ~/.fonts; cd ~/.fonts
5 | RUN curl "https://noto-website-2.storage.googleapis.com/pkgs/Noto-hinted.zip" \
6 | | bsdtar -xvf-
7 | RUN fc-cache -f -v
8 | COPY . /app
9 | WORKDIR /app
10 | CMD python3 hello.py
11 |
--------------------------------------------------------------------------------
/Chapter02/2.1.6_Dependencies-2/hello.py:
--------------------------------------------------------------------------------
1 | print ('Hello Docker')
2 |
--------------------------------------------------------------------------------
/Chapter02/2.1.6_Dependencies/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3
2 | RUN apt-get update
3 | RUN apt-get install -y mariadb-client
4 | COPY . /app
5 | WORKDIR /app
6 | CMD python3 hello.py
7 |
--------------------------------------------------------------------------------
/Chapter02/2.1.6_Dependencies/hello.py:
--------------------------------------------------------------------------------
1 | print ('Hello Docker')
2 |
--------------------------------------------------------------------------------
/Chapter02/2.1.7_CompiledCode/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk
2 | COPY . /app
3 | WORKDIR /app
4 | RUN javac Hello.java
5 | CMD java Hello
6 |
--------------------------------------------------------------------------------
/Chapter02/2.1.7_CompiledCode/Hello.java:
--------------------------------------------------------------------------------
1 | class Hello {
2 | public static void main(String[] args) {
3 | System.out.println("Hello Docker");
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/Chapter02/2.1.8_MultiStage/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:11 AS buildstage
2 | COPY . /app
3 | WORKDIR /app
4 | RUN javac Hello.java
5 |
6 | FROM openjdk:11-jre-slim
7 | COPY --from=buildstage /app/Hello.class /app/
8 | WORKDIR /app
9 | CMD java Hello
10 |
--------------------------------------------------------------------------------
/Chapter02/2.1.8_MultiStage/Hello.java:
--------------------------------------------------------------------------------
1 | class Hello {
2 | public static void main(String[] args) {
3 | System.out.println("Hello Docker");
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/Chapter02/2.3.1_VolumeMount/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3
2 | RUN pip install reloading
3 | ENV PYTHONUNBUFFERED 1
4 | COPY . /app
5 | WORKDIR /app
6 | CMD python3 server.py
7 |
--------------------------------------------------------------------------------
/Chapter02/2.3.1_VolumeMount/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | frontend:
3 | build: .
4 | command: python3 server.py
5 | volumes:
6 | - type: bind
7 | source: .
8 | target: /app
9 | environment:
10 | PYTHONDONTWRITEBYTECODE: 1
11 | ports:
12 | - "8080:80"
13 |
--------------------------------------------------------------------------------
/Chapter02/2.3.1_VolumeMount/server.py:
--------------------------------------------------------------------------------
1 | from reloading import reloading
2 | from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
3 | from datetime import datetime
4 |
5 | class RequestHandler(BaseHTTPRequestHandler):
6 | @reloading
7 | def do_GET(self):
8 | self.send_response(200)
9 | self.send_header('Content-type', 'text/plain')
10 | self.end_headers()
11 | now = datetime.now()
12 | response_string = now.strftime("The time is %-I:%M %p, UTC.")
13 | self.wfile.write(bytes(response_string,"utf-8"))
14 |
15 | def startServer():
16 | try:
17 | server = ThreadingHTTPServer(('',80), RequestHandler)
18 | print("Listening on " + ":".join(map(str, server.server_address)))
19 | server.serve_forever()
20 | except KeyboardInterrupt:
21 | server.shutdown()
22 |
23 | if __name__== "__main__":
24 | startServer()
25 |
--------------------------------------------------------------------------------
/Chapter02/2.3.2_MultipleServices/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | frontend:
3 | build: ../timeserver
4 | command: python3 server.py
5 | environment:
6 | PYTHONDONTWRITEBYTECODE: 1
7 | ports:
8 | - "8080:80"
9 |
10 | db:
11 | image: mysql:5.7
12 | volumes:
13 | - db_data:/var/lib/mysql
14 | restart: always
15 | environment:
16 | MYSQL_ROOT_PASSWORD: super secret password
17 | MYSQL_DATABASE: my_database
18 | MYSQL_USER: dev_user
19 | MYSQL_PASSWORD: another secret password
20 |
21 | volumes:
22 | db_data:
23 |
--------------------------------------------------------------------------------
/Chapter02/2.3.3_Fakes/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | storage:
4 | image: minio/minio
5 | command: minio server /data
6 | volumes:
7 | - storage_data:/data
8 | restart: always
9 | environment:
10 | MINIO_ACCESS_KEY: fakeaccesskey
11 | MINIO_SECRET_KEY: fakesecretkey
12 | ports:
13 | - "9000:9000"
14 |
15 | frontend:
16 | build: ../timeserver
17 | command: python3 server.py
18 | environment:
19 | PYTHONDONTWRITEBYTECODE: 1
20 | S3_ENDPOINT: http://storage:9000
21 | S3_USE_PATH_STYLE: 1
22 | S3_ACCESS_KEY_ID: fakeaccesskey
23 | S3_SECRET_ACCESS_KEY: fakesecretkey
24 | ports:
25 | - "8080:80"
26 |
27 | volumes:
28 | db_data:
29 | storage_data:
30 |
--------------------------------------------------------------------------------
/Chapter02/2.3_Compose/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | web:
3 | build: ../timeserver
4 | command: python3 server.py
5 | ports:
6 | - "8080:80"
7 |
--------------------------------------------------------------------------------
/Chapter02/timeserver/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.12
2 | ENV PYTHONUNBUFFERED 1
3 | COPY . /app
4 | WORKDIR /app
5 | CMD python3 server.py
6 |
--------------------------------------------------------------------------------
/Chapter02/timeserver/server.py:
--------------------------------------------------------------------------------
1 | from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
2 | from datetime import datetime
3 |
4 | class RequestHandler(BaseHTTPRequestHandler):
5 | def do_GET(self):
6 | self.send_response(200)
7 | self.send_header('Content-type', 'text/plain')
8 | self.end_headers()
9 | now = datetime.now()
10 | response_string = now.strftime("The time is %-I:%M %p, UTC.\n")
11 | self.wfile.write(bytes(response_string, "utf-8"))
12 |
13 | def startServer():
14 | try:
15 | server = ThreadingHTTPServer(('', 80), RequestHandler)
16 | print("Listening on " + ":".join(map(str, server.server_address)))
17 | server.serve_forever()
18 | except KeyboardInterrupt:
19 | server.shutdown()
20 |
21 | if __name__== "__main__":
22 | startServer()
23 |
--------------------------------------------------------------------------------
/Chapter03/3.2.4_ThePodSpec/pod.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: timeserver
5 | labels:
6 | pod: timeserver-pod
7 | spec:
8 | containers:
9 | - name: timeserver-container
10 | image: docker.io/wdenniss/timeserver:1
11 |
--------------------------------------------------------------------------------
/Chapter03/3.2.7_Updating/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:2
18 |
--------------------------------------------------------------------------------
/Chapter03/3.2_DeployingToKubernetes/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:1
18 |
--------------------------------------------------------------------------------
/Chapter03/3.2_DeployingToKubernetes/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: timeserver
5 | spec:
6 | selector:
7 | pod: timeserver-pod
8 | ports:
9 | - port: 80
10 | targetPort: 80
11 | protocol: TCP
12 | type: LoadBalancer
13 |
--------------------------------------------------------------------------------
/Chapter03/3.4.3_LocalDevelopment/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: timeserver:latest
18 | imagePullPolicy: Never
19 |
--------------------------------------------------------------------------------
/Chapter03/3.4.3_LocalDevelopment/svc.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: timeserver
5 | spec:
6 | selector:
7 | pod: timeserver-pod
8 | ports:
9 | - port: 80
10 | targetPort: 80
11 | protocol: TCP
12 | type: LoadBalancer
13 |
--------------------------------------------------------------------------------
/Chapter04/4.1.2_Readiness/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:1
18 | readinessProbe:
19 | initialDelaySeconds: 15
20 | periodSeconds: 30
21 | httpGet:
22 | path: /
23 | port: 80
24 | scheme: HTTP
25 | timeoutSeconds: 2
26 | failureThreshold: 1
27 | successThreshold: 1
28 |
--------------------------------------------------------------------------------
/Chapter04/4.1.3_Liveness/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:1
18 | readinessProbe:
19 | initialDelaySeconds: 15
20 | periodSeconds: 30
21 | httpGet:
22 | path: /
23 | port: 80
24 | scheme: HTTP
25 | timeoutSeconds: 2
26 | failureThreshold: 1
27 | successThreshold: 1
28 | livenessProbe:
29 | initialDelaySeconds: 30
30 | periodSeconds: 30
31 | httpGet:
32 | path: /
33 | port: 80
34 | scheme: HTTP
35 | timeoutSeconds: 5
36 | failureThreshold: 10
37 | successThreshold: 1
38 |
--------------------------------------------------------------------------------
/Chapter04/4.1.4_GoodHealthChecks/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:2
18 | readinessProbe:
19 | initialDelaySeconds: 15
20 | periodSeconds: 30
21 | httpGet:
22 | path: /readyz
23 | port: 80
24 | scheme: HTTP
25 | timeoutSeconds: 2
26 | failureThreshold: 1
27 | successThreshold: 1
28 | livenessProbe:
29 | initialDelaySeconds: 30
30 | periodSeconds: 30
31 | httpGet:
32 | path: /healthz
33 | port: 80
34 | scheme: HTTP
35 | timeoutSeconds: 5
36 | failureThreshold: 3
37 | successThreshold: 1
38 |
--------------------------------------------------------------------------------
/Chapter04/4.2.1_RollingUpdate/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | strategy:
11 | type: RollingUpdate
12 | rollingUpdate:
13 | maxSurge: 2
14 | maxUnavailable: 1
15 | template:
16 | metadata:
17 | labels:
18 | pod: timeserver-pod
19 | spec:
20 | containers:
21 | - name: timeserver-container
22 | image: docker.io/wdenniss/timeserver:3
23 |
--------------------------------------------------------------------------------
/Chapter04/4.2.2_Recreate/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | strategy:
11 | type: Recreate
12 | template:
13 | metadata:
14 | labels:
15 | pod: timeserver-pod
16 | spec:
17 | containers:
18 | - name: timeserver-container
19 | image: docker.io/wdenniss/timeserver:3
20 |
--------------------------------------------------------------------------------
/Chapter04/4.2.3_BlueGreen/deploy-blue.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver-blue
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-blue
10 | strategy:
11 | type: Recreate
12 | template:
13 | metadata:
14 | labels:
15 | pod: timeserver-blue
16 | spec:
17 | containers:
18 | - name: timeserver-container
19 | image: docker.io/wdenniss/timeserver:1
20 |
--------------------------------------------------------------------------------
/Chapter04/4.2.3_BlueGreen/deploy-green.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver-green
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-green
10 | strategy:
11 | type: Recreate
12 | template:
13 | metadata:
14 | labels:
15 | pod: timeserver-green
16 | spec:
17 | containers:
18 | - name: timeserver-container
19 | image: docker.io/wdenniss/timeserver:2
20 |
--------------------------------------------------------------------------------
/Chapter04/4.2.3_BlueGreen/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: timeserver
5 | spec:
6 | selector:
7 | pod: timeserver-blue
8 | ports:
9 | - port: 80
10 | targetPort: 80
11 | protocol: TCP
12 | type: LoadBalancer
13 |
--------------------------------------------------------------------------------
/Chapter04/timeserver2/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.12
2 | ENV PYTHONUNBUFFERED 1
3 | COPY . /app
4 | WORKDIR /app
5 | CMD python3 server.py
6 |
--------------------------------------------------------------------------------
/Chapter04/timeserver2/server.py:
--------------------------------------------------------------------------------
1 | from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
2 | from datetime import datetime
3 |
4 | class RequestHandler(BaseHTTPRequestHandler):
5 | def do_GET(self):
6 | match self.path:
7 | case '/':
8 | now = datetime.now()
9 | response_string = now.strftime("The time is %-I:%M %p, UTC.")
10 | self.respond_with(200, response_string)
11 | case '/healthz':
12 | self.respond_with(200, "Healthy")
13 | case '/readyz':
14 | dependencies_connected = True
15 | # TODO: actually verify any dependencies
16 | if (dependencies_connected):
17 | self.respond_with(200, "Ready")
18 | else:
19 | self.respond_with(503, "Not Ready")
20 | case _:
21 | self.respond_with(404, "Not Found")
22 |
23 | def respond_with(self, status_code: int, content: str) -> None:
24 | self.send_response(status_code)
25 | self.send_header('Content-type', 'text/plain')
26 | self.end_headers()
27 | self.wfile.write(bytes(content, "utf-8"))
28 |
29 | def startServer():
30 | try:
31 | server = ThreadingHTTPServer(('', 80), RequestHandler)
32 | print("Listening on " + ":".join(map(str, server.server_address)))
33 | server.serve_forever()
34 | except KeyboardInterrupt:
35 | server.shutdown()
36 |
37 | if __name__== "__main__":
38 | startServer()
39 |
--------------------------------------------------------------------------------
/Chapter04/timeserver3/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.12
2 | ENV PYTHONUNBUFFERED 1
3 | COPY . /app
4 | WORKDIR /app
5 | CMD python3 server.py
6 |
--------------------------------------------------------------------------------
/Chapter04/timeserver3/server.py:
--------------------------------------------------------------------------------
1 | from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
2 | from datetime import datetime, timedelta
3 |
4 | last_ready_time = datetime.now()
5 |
6 | class RequestHandler(BaseHTTPRequestHandler):
7 |
8 | def do_GET(self):
9 | global last_ready_time
10 |
11 | match self.path:
12 | case '/':
13 | now = datetime.now()
14 | response_string = now.strftime("The time is %-I:%M %p, UTC.")
15 | self.respond_with(200, response_string)
16 | case '/healthz':
17 | if (datetime.now() > last_ready_time + timedelta(minutes=5)):
18 | self.respond_with(503, "Not Healthy")
19 | else:
20 | self.respond_with(200, "Healthy")
21 | case '/readyz':
22 | dependencies_connected = True
23 | # TODO: actually verify any dependencies
24 | if (dependencies_connected):
25 | last_ready_time = datetime.now()
26 | self.respond_with(200, "Ready")
27 | else:
28 | self.respond_with(503, "Not Ready")
29 | case _:
30 | self.respond_with(404, "Not Found")
31 |
32 | def respond_with(self, status_code: int, content: str) -> None:
33 | self.send_response(status_code)
34 | self.send_header('Content-type', 'text/plain')
35 | self.end_headers()
36 | self.wfile.write(bytes(content, "utf-8"))
37 |
38 | def startServer():
39 | try:
40 | server = ThreadingHTTPServer(('', 80), RequestHandler)
41 | print("Listening on " + ":".join(map(str, server.server_address)))
42 | server.serve_forever()
43 | except KeyboardInterrupt:
44 | server.shutdown()
45 |
46 | if __name__== "__main__":
47 | startServer()
48 |
--------------------------------------------------------------------------------
/Chapter05/5.1.1_PodResources/deploy_requests.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:3
18 | resources:
19 | requests:
20 | cpu: 200m
21 | memory: 250Mi
22 |
--------------------------------------------------------------------------------
/Chapter05/5.1.1_PodResources/deploy_requests_limits.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:3
18 | resources:
19 | requests:
20 | cpu: 200m
21 | memory: 250Mi
22 | limits:
23 | cpu: 300m
24 | memory: 400Mi
25 |
--------------------------------------------------------------------------------
/Chapter05/5.1.3_Priority/deploy.yaml:
--------------------------------------------------------------------------------
1 | # A Deployment that uses a priority class
2 | apiVersion: apps/v1
3 | kind: Deployment
4 | metadata:
5 | name: timeserver
6 | spec:
7 | replicas: 3
8 | selector:
9 | matchLabels:
10 | pod: timeserver-pod
11 | template:
12 | metadata:
13 | labels:
14 | pod: timeserver-pod
15 | spec:
16 | priorityClassName: high-priority
17 | containers:
18 | - name: timeserver-container
19 | image: docker.io/wdenniss/timeserver:1
20 | resources:
21 | requests:
22 | cpu: 200m
23 | memory: 250Mi
24 |
--------------------------------------------------------------------------------
/Chapter05/5.1.3_Priority/priorityclass-preemption.yaml:
--------------------------------------------------------------------------------
1 | # A high priority class definition that will preempt lower-priority running Pods
2 | apiVersion: scheduling.k8s.io/v1
3 | kind: PriorityClass
4 | metadata:
5 | name: high-priority-preemption
6 | value: 1000000
7 | preemptionPolicy: PreemptLowerPriority
8 | globalDefault: false
9 | description: "Critical services."
10 |
--------------------------------------------------------------------------------
/Chapter05/5.1.3_Priority/priorityclass.yaml:
--------------------------------------------------------------------------------
1 | # A high priority class definition that does not preempt running Pods
2 | apiVersion: scheduling.k8s.io/v1
3 | kind: PriorityClass
4 | metadata:
5 | name: high-priority
6 | value: 1000000
7 | preemptionPolicy: Never
8 | globalDefault: false
9 | description: "Critical services."
10 |
--------------------------------------------------------------------------------
/Chapter05/5.1.3_Priority/test/README.md:
--------------------------------------------------------------------------------
1 | Priority classes can be used without preemption (`preemptionPolicy: Never`) which governs the
2 | scheduling and eviction order
3 |
4 | To run this test, try the following:
5 |
6 | On a cluster with 3 or less nodes:
7 |
8 | ```
9 | # Fill the cluster with Pods that have no priority set
10 | kubectl create -f deploy_no_priority.yaml
11 |
12 | # Check the status
13 | kubectl get pods
14 |
15 | # You should see a bunch of pods with "Pending" since we filled up the nodes.
16 |
17 | # Create a high priority class
18 | kubectl create -f priorityclass_with_preemption.yaml
19 |
20 | # Create a high priority deployment
21 | kubectl create -f deploy_high_priority.yaml
22 |
23 | # Check the status
24 | kubectl get pods
25 | ```
26 |
27 | Normally, since we filled the nodes up with other Pods, Pods in the second deployment would
28 | sit there pending. But with `preemptionPolicy: PreemptLowerPriority`, Pods from the previous
29 | deployment are evicted to make room.
30 |
31 | ## Cleanup
32 |
33 | ```
34 | kubectl delete -f .
35 | ```
36 |
--------------------------------------------------------------------------------
/Chapter05/5.1.3_Priority/test/deploy_high_priority.yaml:
--------------------------------------------------------------------------------
1 | # A Deployment that uses the high priority class
2 | apiVersion: apps/v1
3 | kind: Deployment
4 | metadata:
5 | name: timeserver-hpp
6 | spec:
7 | replicas: 3
8 | selector:
9 | matchLabels:
10 | pod: timeserver-pod-hpp
11 | template:
12 | metadata:
13 | labels:
14 | pod: timeserver-pod-hpp
15 | spec:
16 | priorityClassName: high-priority-preemption
17 | containers:
18 | - name: timeserver-container
19 | image: docker.io/wdenniss/timeserver:1
20 | resources:
21 | requests:
22 | cpu: 200m
23 | memory: 250Mi
24 |
--------------------------------------------------------------------------------
/Chapter05/5.1.3_Priority/test/deploy_no_priority.yaml:
--------------------------------------------------------------------------------
1 | # A Deployment with no priority set, and lots of replicas
2 | apiVersion: apps/v1
3 | kind: Deployment
4 | metadata:
5 | name: timeserver-np
6 | spec:
7 | replicas: 15
8 | selector:
9 | matchLabels:
10 | pod: timeserver-pod-np
11 | template:
12 | metadata:
13 | labels:
14 | pod: timeserver-pod-np
15 | spec:
16 | containers:
17 | - name: timeserver-container
18 | image: docker.io/wdenniss/timeserver:1
19 | resources:
20 | requests:
21 | cpu: 200m
22 | memory: 250Mi
23 |
--------------------------------------------------------------------------------
/Chapter05/5.1.3_Priority/test/priorityclass-preemption.yaml:
--------------------------------------------------------------------------------
1 | # A high priority class definition that will preempt lower-priority running Pods
2 | apiVersion: scheduling.k8s.io/v1
3 | kind: PriorityClass
4 | metadata:
5 | name: high-priority-preemption
6 | value: 1000000
7 | preemptionPolicy: PreemptLowerPriority
8 | globalDefault: false
9 | description: "Critical services."
10 |
--------------------------------------------------------------------------------
/Chapter05/5.2_ResourceUsageTest/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:3
18 | resources:
19 | requests:
20 | cpu: 200m
21 | memory: 250Mi
22 |
--------------------------------------------------------------------------------
/Chapter06/6.1_Replicas/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 6
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:3
18 | resources:
19 | requests:
20 | cpu: 200m
21 | memory: 250Mi
22 |
--------------------------------------------------------------------------------
/Chapter06/6.2.1_ExternalMetricGCP/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:4
18 | resources:
19 | requests:
20 | cpu: 250m
21 | memory: 250Mi
22 |
--------------------------------------------------------------------------------
/Chapter06/6.2.1_ExternalMetricGCP/hpa.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: autoscaling/v2
2 | kind: HorizontalPodAutoscaler
3 | metadata:
4 | name: timeserver-autoscaler
5 | spec:
6 | minReplicas: 1
7 | maxReplicas: 6
8 | metrics:
9 | - type: External
10 | external:
11 | metric:
12 | name: loadbalancing.googleapis.com|https|request_count
13 | selector:
14 | matchLabels:
15 | resource.labels.forwarding_rule_name: "k8s2-fr-21mgs2fl"
16 | target:
17 | type: AverageValue
18 | averageValue: 5
19 | scaleTargetRef:
20 | apiVersion: apps/v1
21 | kind: Deployment
22 | name: timeserver
23 |
--------------------------------------------------------------------------------
/Chapter06/6.2.1_ExternalMetricGCP/ingress.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: networking.k8s.io/v1
2 | kind: Ingress
3 | metadata:
4 | name: timeserver-ingress
5 | spec:
6 | rules:
7 | - http:
8 | paths:
9 | - path: /
10 | pathType: Prefix
11 | backend:
12 | service:
13 | name: timeserver-internal
14 | port:
15 | number: 80
16 |
--------------------------------------------------------------------------------
/Chapter06/6.2.1_ExternalMetricGCP/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: timeserver-internal
5 | spec:
6 | selector:
7 | pod: timeserver-pod
8 | ports:
9 | - port: 80
10 | targetPort: 80
11 | protocol: TCP
12 | type: NodePort
13 |
--------------------------------------------------------------------------------
/Chapter06/6.2_HPA/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:4
18 | resources:
19 | requests:
20 | cpu: 250m
21 | memory: 250Mi
22 |
--------------------------------------------------------------------------------
/Chapter06/6.2_HPA/hpa.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: autoscaling/v2
2 | kind: HorizontalPodAutoscaler
3 | metadata:
4 | name: timeserver
5 | spec:
6 | minReplicas: 1
7 | maxReplicas: 10
8 | metrics:
9 | - resource:
10 | name: cpu
11 | target:
12 | averageUtilization: 20
13 | type: Utilization
14 | type: Resource
15 | scaleTargetRef:
16 | apiVersion: apps/v1
17 | kind: Deployment
18 | name: timeserver
19 |
--------------------------------------------------------------------------------
/Chapter06/6.2_HPA/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: timeserver
5 | spec:
6 | selector:
7 | pod: timeserver-pod
8 | ports:
9 | - port: 80
10 | targetPort: 80
11 | protocol: TCP
12 | type: LoadBalancer
13 |
--------------------------------------------------------------------------------
/Chapter06/6.3.2_PlaceholderPod/default-priority.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: scheduling.k8s.io/v1
2 | kind: PriorityClass
3 | metadata:
4 | name: default-priority
5 | value: 0
6 | preemptionPolicy: PreemptLowerPriority
7 | globalDefault: true
8 | description: "The global default priority. Will preempt the placeholder Pods."
9 |
--------------------------------------------------------------------------------
/Chapter06/6.3.2_PlaceholderPod/placeholder-deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: placeholder
5 | spec:
6 | replicas: 10
7 | selector:
8 | matchLabels:
9 | pod: placeholder-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: placeholder-pod
14 | spec:
15 | priorityClassName: placeholder-priority
16 | terminationGracePeriodSeconds: 0
17 | containers:
18 | - name: ubuntu
19 | image: ubuntu
20 | command: ["sleep"]
21 | args: ["infinity"]
22 | resources:
23 | requests:
24 | cpu: 200m
25 | memory: 250Mi
26 |
--------------------------------------------------------------------------------
/Chapter06/6.3.2_PlaceholderPod/placeholder-priority.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: scheduling.k8s.io/v1
2 | kind: PriorityClass
3 | metadata:
4 | name: placeholder-priority
5 | value: -10
6 | preemptionPolicy: Never
7 | globalDefault: false
8 | description: "Placeholder Pod priority."
9 |
--------------------------------------------------------------------------------
/Chapter06/timeserver4/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.12
2 | ENV PYTHONUNBUFFERED 1
3 | COPY . /app
4 | WORKDIR /app
5 | CMD python3 server.py
6 |
--------------------------------------------------------------------------------
/Chapter06/timeserver4/pi.py:
--------------------------------------------------------------------------------
1 | from decimal import *
2 |
3 | # Calculate pi using the Gregory-Leibniz infinity series
4 | def leibniz_pi(iterations):
5 |
6 | precision = 20
7 | getcontext().prec = 20
8 | piDiv4 = Decimal(1)
9 | odd = Decimal(3)
10 |
11 | for i in range(0, iterations):
12 | piDiv4 = piDiv4 - 1/odd
13 | odd = odd + 2
14 | piDiv4 = piDiv4 + 1/odd
15 | odd = odd + 2
16 |
17 | return piDiv4 * 4
18 |
--------------------------------------------------------------------------------
/Chapter06/timeserver4/server.py:
--------------------------------------------------------------------------------
1 | from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
2 | from datetime import datetime, timedelta
3 | from pi import *
4 |
5 | last_ready_time = datetime.now()
6 |
7 | class RequestHandler(BaseHTTPRequestHandler):
8 |
9 | def do_GET(self):
10 | global last_ready_time
11 |
12 | match self.path:
13 | case '/':
14 | now = datetime.now()
15 | response_string = now.strftime("The time is %-I:%M %p, UTC.")
16 | self.respond_with(200, response_string)
17 | case '/pi':
18 | pi = leibniz_pi(1000000)
19 | self.respond_with(200, str(pi))
20 | case '/healthz':
21 | if (datetime.now() > last_ready_time + timedelta(minutes=5)):
22 | self.respond_with(503, "Not Healthy")
23 | else:
24 | self.respond_with(200, "Healthy")
25 | case '/readyz':
26 | dependencies_connected = True
27 | # TODO: actually verify any dependencies
28 | if (dependencies_connected):
29 | last_ready_time = datetime.now()
30 | self.respond_with(200, "Ready")
31 | else:
32 | self.respond_with(503, "Not Ready")
33 | case _:
34 | self.respond_with(404, "Not Found")
35 |
36 | def respond_with(self, status_code: int, content: str) -> None:
37 | self.send_response(status_code)
38 | self.send_header('Content-type', 'text/plain')
39 | self.end_headers()
40 | self.wfile.write(bytes(content, "utf-8"))
41 |
42 | def startServer():
43 | try:
44 | server = ThreadingHTTPServer(('', 80), RequestHandler)
45 | print("Listening on " + ":".join(map(str, server.server_address)))
46 | server.serve_forever()
47 | except KeyboardInterrupt:
48 | server.shutdown()
49 |
50 | if __name__== "__main__":
51 | startServer()
52 |
--------------------------------------------------------------------------------
/Chapter07/7.1_InternalServices/robohash-deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: robohash
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: robohash
10 | template:
11 | metadata:
12 | labels:
13 | app: robohash
14 | spec:
15 | containers:
16 | - name: robohash-container
17 | image: docker.io/wdenniss/robohash:1
18 |
--------------------------------------------------------------------------------
/Chapter07/7.1_InternalServices/robohash-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: robohash-internal
5 | spec:
6 | selector:
7 | app: robohash
8 | ports:
9 | - port: 80
10 | targetPort: 80
11 | protocol: TCP
12 | type: ClusterIP
13 |
--------------------------------------------------------------------------------
/Chapter07/7.1_InternalServices/timeserver-deploy-dns.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:5
18 | env:
19 | - name: AVATAR_ENDPOINT
20 | value: http://robohash-internal
21 |
--------------------------------------------------------------------------------
/Chapter07/7.1_InternalServices/timeserver-deploy-env.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:5
18 | env:
19 | - name: AVATAR_ENDPOINT
20 | value: http://$(ROBOHASH_INTERNAL_SERVICE_HOST)
21 |
--------------------------------------------------------------------------------
/Chapter07/7.1_InternalServices/timeserver-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: timeserver
5 | spec:
6 | selector:
7 | pod: timeserver-pod
8 | ports:
9 | - port: 80
10 | targetPort: 80
11 | protocol: TCP
12 | type: LoadBalancer
13 |
--------------------------------------------------------------------------------
/Chapter07/7.2.1_TLS/ingress_tls.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: networking.k8s.io/v1
2 | kind: Ingress
3 | metadata:
4 | name: timeserver-tls
5 | spec:
6 | tls:
7 | - secretName: my-tls-cert
8 | rules:
9 | - host: example.com
10 | http:
11 | paths:
12 | - path: /
13 | pathType: Prefix
14 | backend:
15 | service:
16 | name: timeserver-internal
17 | port:
18 | number: 80
19 | - http:
20 | paths:
21 | - path: /
22 | pathType: Prefix
23 | backend:
24 | service:
25 | name: robohash-internal
26 | port:
27 | number: 80
28 |
--------------------------------------------------------------------------------
/Chapter07/7.2_Ingress/ingress_host.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: networking.k8s.io/v1
2 | kind: Ingress
3 | metadata:
4 | name: timeserver-ingress
5 | spec:
6 | rules:
7 | - host: timeserver.example.com
8 | http:
9 | paths:
10 | - path: /
11 | pathType: Prefix
12 | backend:
13 | service:
14 | name: timeserver-internal
15 | port:
16 | number: 80
17 | - host: robohash.example.com
18 | http:
19 | paths:
20 | - path: /
21 | pathType: Prefix
22 | backend:
23 | service:
24 | name: robohash-internal
25 | port:
26 | number: 80
27 |
--------------------------------------------------------------------------------
/Chapter07/7.2_Ingress/ingress_path.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: networking.k8s.io/v1
2 | kind: Ingress
3 | metadata:
4 | name: timeserver-ingress
5 | spec:
6 | rules:
7 | - http:
8 | paths:
9 | - path: /
10 | pathType: Prefix
11 | backend:
12 | service:
13 | name: timeserver-internal
14 | port:
15 | number: 80
16 | - path: /robohash
17 | pathType: Prefix
18 | backend:
19 | service:
20 | name: robohash-internal
21 | port:
22 | number: 80
23 |
--------------------------------------------------------------------------------
/Chapter07/7.2_Ingress/robohash-deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: robohash
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: robohash
10 | template:
11 | metadata:
12 | labels:
13 | app: robohash
14 | spec:
15 | containers:
16 | - name: robohash-container
17 | image: docker.io/wdenniss/robohash:1
18 |
--------------------------------------------------------------------------------
/Chapter07/7.2_Ingress/robohash-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: robohash-internal
5 | spec:
6 | selector:
7 | app: robohash
8 | ports:
9 | - port: 80
10 | targetPort: 80
11 | protocol: TCP
12 | type: NodePort
13 |
--------------------------------------------------------------------------------
/Chapter07/7.2_Ingress/timeserver-deploy-dns.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:5
18 | env:
19 | - name: AVATAR_ENDPOINT
20 | value: http://robohash-internal
21 |
--------------------------------------------------------------------------------
/Chapter07/7.2_Ingress/timeserver-service-internal.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: timeserver-internal
5 | spec:
6 | selector:
7 | pod: timeserver-pod
8 | ports:
9 | - port: 80
10 | targetPort: 80
11 | protocol: TCP
12 | type: NodePort
13 |
--------------------------------------------------------------------------------
/Chapter07/timeserver5/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.12
2 | ENV PYTHONUNBUFFERED 1
3 | COPY . /app
4 | WORKDIR /app
5 | CMD python3 server.py
6 |
--------------------------------------------------------------------------------
/Chapter07/timeserver5/build.sh:
--------------------------------------------------------------------------------
1 | docker buildx build --push --platform linux/arm/v7,linux/arm64/v8,linux/amd64 --tag docker.io/wdenniss/timeserver:5 .
2 |
--------------------------------------------------------------------------------
/Chapter07/timeserver5/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | web:
3 | build: .
4 | ports:
5 | - "8080:80"
6 | environment:
7 | AVATAR_ENDPOINT: http://robohash
8 |
9 |
10 | robohash:
11 | image: wdenniss/robohash:v1
12 | ports:
13 | - "8089:80"
14 |
--------------------------------------------------------------------------------
/Chapter07/timeserver5/server.py:
--------------------------------------------------------------------------------
1 | from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
2 | from datetime import datetime, timedelta
3 | import urllib.request
4 | import os
5 | import random
6 |
7 | last_ready_time = datetime.now()
8 |
9 | class RequestHandler(BaseHTTPRequestHandler):
10 |
11 | def do_GET(self):
12 | global last_ready_time
13 |
14 | match self.path:
15 | case '/':
16 | now = datetime.now()
17 | response_string = now.strftime("The time is %-I:%M %p, UTC.")
18 | self.respond_with(200, response_string)
19 | case '/avatar':
20 | url = os.environ['AVATAR_ENDPOINT'] + "/" + str(random.randint(0, 100))
21 | try:
22 | with urllib.request.urlopen(url) as f:
23 | data = f.read()
24 | self.send_response(200)
25 | self.send_header('Content-type', 'image/png')
26 | self.end_headers()
27 | self.wfile.write(data)
28 | except urllib.error.URLError as e:
29 | self.respond_with(500, e.reason)
30 | case '/healthz':
31 | if (datetime.now() > last_ready_time + timedelta(minutes=5)):
32 | self.respond_with(503, "Not Healthy")
33 | else:
34 | self.respond_with(200, "Healthy")
35 | case '/readyz':
36 | dependencies_connected = True
37 | # TODO: actually verify any dependencies
38 | if (dependencies_connected):
39 | last_ready_time = datetime.now()
40 | self.respond_with(200, "Ready")
41 | else:
42 | self.respond_with(503, "Not Ready")
43 | case _:
44 | self.respond_with(404, "Not Found")
45 |
46 | def respond_with(self, status_code: int, content: str) -> None:
47 | self.send_response(status_code)
48 | self.send_header('Content-type', 'text/plain')
49 | self.end_headers()
50 | self.wfile.write(bytes(content, "utf-8"))
51 |
52 | def startServer():
53 | try:
54 | server = ThreadingHTTPServer(('', 80), RequestHandler)
55 | print("Listening on " + ":".join(map(str, server.server_address)))
56 | server.serve_forever()
57 | except KeyboardInterrupt:
58 | server.shutdown()
59 |
60 | if __name__== "__main__":
61 | startServer()
62 |
--------------------------------------------------------------------------------
/Chapter08/8.1.1_NodeSelection/deploy_nodeaffinity-autopilot.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | nodeSelector:
16 | cloud.google.com/compute-class: Scale-Out
17 | affinity:
18 | nodeAffinity:
19 | requiredDuringSchedulingIgnoredDuringExecution:
20 | nodeSelectorTerms:
21 | - matchExpressions:
22 | - key: kubernetes.io/arch
23 | operator: In
24 | values:
25 | - arm64
26 | containers:
27 | - name: timeserver-container
28 | image: docker.io/wdenniss/timeserver:5
29 |
--------------------------------------------------------------------------------
/Chapter08/8.1.1_NodeSelection/deploy_nodeaffinity.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | affinity:
16 | nodeAffinity:
17 | requiredDuringSchedulingIgnoredDuringExecution:
18 | nodeSelectorTerms:
19 | - matchExpressions:
20 | - key: kubernetes.io/arch
21 | operator: In
22 | values:
23 | - arm64
24 | containers:
25 | - name: timeserver-container
26 | image: docker.io/wdenniss/timeserver:5
27 |
--------------------------------------------------------------------------------
/Chapter08/8.1.1_NodeSelection/deploy_nodeselector-autopilot.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | nodeSelector:
16 | cloud.google.com/compute-class: Scale-Out
17 | kubernetes.io/arch: arm64
18 | containers:
19 | - name: timeserver-container
20 | image: docker.io/wdenniss/timeserver:5
21 |
--------------------------------------------------------------------------------
/Chapter08/8.1.1_NodeSelection/deploy_nodeselector.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | nodeSelector:
16 | kubernetes.io/arch: arm64
17 | containers:
18 | - name: timeserver-container
19 | image: docker.io/wdenniss/timeserver:5
20 |
--------------------------------------------------------------------------------
/Chapter08/8.1.2_NodeAffinity/deploy_nodeaffinity_multi-autopilot.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 6
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | nodeSelector:
16 | cloud.google.com/compute-class: Scale-Out
17 | affinity:
18 | nodeAffinity:
19 | requiredDuringSchedulingIgnoredDuringExecution:
20 | nodeSelectorTerms:
21 | - matchExpressions:
22 | - key: kubernetes.io/arch
23 | operator: In
24 | values:
25 | - arm64
26 | - amd64
27 | containers:
28 | - name: timeserver-container
29 | image: docker.io/wdenniss/timeserver:5
30 | resources:
31 | requests:
32 | cpu: 500m
33 |
--------------------------------------------------------------------------------
/Chapter08/8.1.2_NodeAffinity/deploy_nodeaffinity_multi.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 6
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | affinity:
16 | nodeAffinity:
17 | requiredDuringSchedulingIgnoredDuringExecution:
18 | nodeSelectorTerms:
19 | - matchExpressions:
20 | - key: kubernetes.io/arch
21 | operator: In
22 | values:
23 | - arm64
24 | - amd64
25 | containers:
26 | - name: timeserver-container
27 | image: docker.io/wdenniss/timeserver:5
28 | resources:
29 | requests:
30 | cpu: 500m
31 |
--------------------------------------------------------------------------------
/Chapter08/8.1.2_NodeAffinity/deploy_nodeaffinity_preferred-autopilot.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 6
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | tolerations:
16 | - effect: NoSchedule
17 | key: kubernetes.io/arch
18 | operator: Equal
19 | value: arm64
20 | nodeSelector:
21 | cloud.google.com/compute-class: Scale-Out
22 | affinity:
23 | nodeAffinity:
24 | preferredDuringSchedulingIgnoredDuringExecution:
25 | - weight: 100
26 | preference:
27 | matchExpressions:
28 | - key: kubernetes.io/arch
29 | operator: In
30 | values:
31 | - arm64
32 | containers:
33 | - name: timeserver-container
34 | image: docker.io/wdenniss/timeserver:5
35 | resources:
36 | requests:
37 | cpu: 500m
38 |
--------------------------------------------------------------------------------
/Chapter08/8.1.2_NodeAffinity/deploy_nodeaffinity_preferred.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 6
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | affinity:
16 | nodeAffinity:
17 | preferredDuringSchedulingIgnoredDuringExecution:
18 | - weight: 100
19 | preference:
20 | matchExpressions:
21 | - key: kubernetes.io/arch
22 | operator: In
23 | values:
24 | - arm64
25 | containers:
26 | - name: timeserver-container
27 | image: docker.io/wdenniss/timeserver:5
28 | resources:
29 | requests:
30 | cpu: 500m
31 |
--------------------------------------------------------------------------------
/Chapter08/8.1.3_Taints/daemonset_tolerate_all_taints.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: DaemonSet
3 | metadata:
4 | name: example-ds
5 | spec:
6 | selector:
7 | matchLabels:
8 | pod: example-pod
9 | template:
10 | metadata:
11 | labels:
12 | pod: example-pod
13 | spec:
14 | tolerations:
15 | - effect: NoExecute
16 | operator: Exists
17 | - effect: NoSchedule
18 | operator: Exists
19 | containers:
20 | - image: ubuntu
21 | command: ["sleep", "infinity"]
22 | name: ubuntu-container
23 |
--------------------------------------------------------------------------------
/Chapter08/8.1.3_Taints/daemonset_tolerate_antiaffinity.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: DaemonSet
3 | metadata:
4 | name: example-ds
5 | spec:
6 | selector:
7 | matchLabels:
8 | pod: example-pod
9 | template:
10 | metadata:
11 | labels:
12 | pod: example-pod
13 | spec:
14 | tolerations:
15 | - effect: NoExecute
16 | operator: Exists
17 | - effect: NoSchedule
18 | operator: Exists
19 | affinity:
20 | nodeAffinity:
21 | requiredDuringSchedulingIgnoredDuringExecution:
22 | nodeSelectorTerms:
23 | - matchExpressions:
24 | - key: kubernetes.io/arch
25 | operator: NotIn
26 | values:
27 | - arm64
28 | containers:
29 | - image: ubuntu
30 | command: ["sleep", "infinity"]
31 | name: ubuntu-container
32 |
--------------------------------------------------------------------------------
/Chapter08/8.1.3_Taints/deploy_tolerate_spot.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | tolerations:
16 | - key: spot
17 | value: "true"
18 | containers:
19 | - name: timeserver-container
20 | image: docker.io/wdenniss/timeserver:5
21 |
--------------------------------------------------------------------------------
/Chapter08/8.1.4_WorkloadSeparation/deploy_group1.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver1
5 | spec:
6 | replicas: 5
7 | selector:
8 | matchLabels:
9 | pod: timeserver1-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver1-pod
14 | spec:
15 | tolerations:
16 | - key: group
17 | operator: Equal
18 | value: "1"
19 | effect: NoSchedule
20 | nodeSelector:
21 | group: "1"
22 | containers:
23 | - name: timeserver-container
24 | image: docker.io/wdenniss/timeserver:5
25 |
--------------------------------------------------------------------------------
/Chapter08/8.1.4_WorkloadSeparation/deploy_group2.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver2
5 | spec:
6 | replicas: 5
7 | selector:
8 | matchLabels:
9 | pod: timeserver2-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver2-pod
14 | spec:
15 | tolerations:
16 | - key: group
17 | operator: Equal
18 | value: "2"
19 | effect: NoSchedule
20 | nodeSelector:
21 | group: "2"
22 | containers:
23 | - name: timeserver-container
24 | image: docker.io/wdenniss/timeserver:5
25 |
--------------------------------------------------------------------------------
/Chapter08/8.2.1_TopologySpread/deploy_topology.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | topologySpreadConstraints:
16 | - maxSkew: 1
17 | topologyKey: kubernetes.io/hostname
18 | whenUnsatisfiable: ScheduleAnyway
19 | labelSelector:
20 | matchLabels:
21 | pod: timeserver-pod
22 | containers:
23 | - name: timeserver-container
24 | image: docker.io/wdenniss/timeserver:5
25 | resources:
26 | requests:
27 | cpu: 200m
28 | memory: 250Mi
29 |
--------------------------------------------------------------------------------
/Chapter08/8.2.2_Colocation/backend.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: mariadb
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: mariadb-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: mariadb-pod
14 | spec:
15 | containers:
16 | - name: mariadb-container
17 | image: mariadb
18 | env:
19 | - name: MARIADB_RANDOM_ROOT_PASSWORD
20 | value: "1"
21 |
--------------------------------------------------------------------------------
/Chapter08/8.2.2_Colocation/frontend.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:5
18 | affinity:
19 | podAffinity:
20 | requiredDuringSchedulingIgnoredDuringExecution:
21 | - labelSelector:
22 | matchExpressions:
23 | - key: pod
24 | operator: In
25 | values:
26 | - mariadb-pod
27 | topologyKey: "kubernetes.io/hostname"
28 |
--------------------------------------------------------------------------------
/Chapter08/8.2.3_PodAntiAffinity/backend.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: mariadb
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: mariadb-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: mariadb-pod
14 | spec:
15 | containers:
16 | - name: mariadb-container
17 | image: mariadb
18 | env:
19 | - name: MARIADB_RANDOM_ROOT_PASSWORD
20 | value: "1"
21 | resources:
22 | requests:
23 | cpu: 20m
24 | memory: 20Mi
25 |
--------------------------------------------------------------------------------
/Chapter08/8.2.3_PodAntiAffinity/frontend.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 3
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:5
18 | affinity:
19 | podAntiAffinity:
20 | requiredDuringSchedulingIgnoredDuringExecution:
21 | - labelSelector:
22 | matchExpressions:
23 | - key: pod
24 | operator: In
25 | values:
26 | - mariadb-pod
27 | topologyKey: "kubernetes.io/hostname"
28 |
--------------------------------------------------------------------------------
/Chapter09/9.1.1_Volume/emptydir_pod.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: emptydir-pod
5 | labels:
6 | pod: timeserver-pod
7 | spec:
8 | containers:
9 | - name: timeserver-container
10 | image: docker.io/wdenniss/timeserver:5
11 | volumeMounts:
12 | - name: cache-volume
13 | mountPath: /app/cache/
14 | volumes:
15 | - name: cache-volume
16 | emptyDir: {}
17 |
--------------------------------------------------------------------------------
/Chapter09/9.1.1_Volume/mariadb_pod.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: mariadb-demo
5 | labels:
6 | app: mariadb
7 | spec:
8 | nodeSelector:
9 | topology.kubernetes.io/zone: us-west1-a # The zone of the disk
10 | containers:
11 | - name: mariadb-container
12 | image: mariadb:latest
13 | volumeMounts:
14 | - mountPath: /var/lib/mysql
15 | name: mariadb-volume
16 | env:
17 | - name: MARIADB_ROOT_PASSWORD
18 | value: "your database password"
19 | volumes:
20 | - name: mariadb-volume
21 | gcePersistentDisk:
22 | pdName: mariadb-disk # A disk with this name must exist in the zone used above. See instructions below.
23 | fsType: ext4
24 |
25 | # Note: This method of attaching disks to Pods is completely manual.
26 | # You need to create the disk resource outside of Kubernetes first.
27 | # Be sure that the zone of the disk and the Pod match (update the nodeAffinity if needed).
28 | # For this example in GCP using the zone us-west-1a, and a disk with the name `mariadb-disk` you
29 | # can run:
30 | # `gcloud compute disks create --size=10GB --zone=us-west1-a mariadb-disk`
31 |
--------------------------------------------------------------------------------
/Chapter09/9.1.2_PersistentVolume/pvc-mariadb.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: mariadb-demo
5 | labels:
6 | app: mariadb
7 | spec:
8 | containers:
9 | - name: mariadb-container
10 | image: mariadb:latest
11 | volumeMounts:
12 | - mountPath: /var/lib/mysql
13 | name: mariadb-volume
14 | resources:
15 | requests:
16 | cpu: 1
17 | memory: 4Gi
18 | env:
19 | - name: MARIADB_ROOT_PASSWORD
20 | value: "your database password"
21 | volumes:
22 | - name: mariadb-volume
23 | persistentVolumeClaim:
24 | claimName: mariadb-pv-claim
25 | ---
26 | apiVersion: v1
27 | kind: PersistentVolumeClaim
28 | metadata:
29 | name: mariadb-pv-claim
30 | spec:
31 | accessModes:
32 | - ReadWriteOnce
33 | resources:
34 | requests:
35 | storage: 2Gi
36 |
--------------------------------------------------------------------------------
/Chapter09/9.1.3_StorageClass/storageclass.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: storage.k8s.io/v1
2 | kind: StorageClass
3 | metadata:
4 | annotations:
5 | storageclass.kubernetes.io/is-default-class: "true"
6 | name: example-default-rwo
7 | parameters:
8 | type: pd-balanced
9 | provisioner: pd.csi.storage.gke.io
10 | reclaimPolicy: Retain
11 | volumeBindingMode: WaitForFirstConsumer
12 | allowVolumeExpansion: true
13 |
--------------------------------------------------------------------------------
/Chapter09/9.1.3_StorageClass/storageclass2.yaml:
--------------------------------------------------------------------------------
1 | allowVolumeExpansion: true
2 | apiVersion: storage.k8s.io/v1
3 | kind: StorageClass
4 | metadata:
5 | name: standard-rwo
6 | parameters:
7 | type: pd-balanced
8 | provisioner: pd.csi.storage.gke.io
9 | reclaimPolicy: Retain
10 | volumeBindingMode: WaitForFirstConsumer
11 |
--------------------------------------------------------------------------------
/Chapter09/9.1.4_Deployment_MariaDB/mariadb-deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: mariadb-demo
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: mariadb
10 | strategy:
11 | type: Recreate
12 | template:
13 | metadata:
14 | labels:
15 | app: mariadb
16 | spec:
17 | containers:
18 | - name: mariadb-container
19 | image: mariadb:latest
20 | volumeMounts:
21 | - mountPath: /var/lib/mysql
22 | name: mariadb-volume
23 | resources:
24 | requests:
25 | cpu: 1
26 | memory: 4Gi
27 | env:
28 | - name: MARIADB_ROOT_PASSWORD
29 | value: "your database password"
30 | volumes:
31 | - name: mariadb-volume
32 | persistentVolumeClaim:
33 | claimName: mariadb-pvc
34 | ---
35 | apiVersion: v1
36 | kind: PersistentVolumeClaim
37 | metadata:
38 | name: mariadb-pvc
39 | spec:
40 | accessModes:
41 | - ReadWriteOnce
42 | resources:
43 | requests:
44 | storage: 2Gi
45 |
--------------------------------------------------------------------------------
/Chapter09/9.1.4_Deployment_MariaDB/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: mariadb-service
5 | spec:
6 | selector:
7 | app: mariadb
8 | ports:
9 | - port: 3306
10 | targetPort: 3306
11 | protocol: TCP
12 | type: LoadBalancer
13 |
--------------------------------------------------------------------------------
/Chapter09/9.2.1_StatefulSet_MariaDB/mariadb-statefulset.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: StatefulSet
3 | metadata:
4 | name: mariadb
5 | spec:
6 | selector:
7 | matchLabels:
8 | app: mariadb-sts
9 | serviceName: mariadb-service
10 | replicas: 1
11 | template:
12 | metadata:
13 | labels:
14 | app: mariadb-sts
15 | spec:
16 | terminationGracePeriodSeconds: 10
17 | containers:
18 | - name: mariadb-container
19 | image: mariadb:latest
20 | volumeMounts:
21 | - name: mariadb-pvc
22 | mountPath: /var/lib/mysql
23 | resources:
24 | requests:
25 | cpu: 1
26 | memory: 4Gi
27 | env:
28 | - name: MARIADB_ROOT_PASSWORD
29 | value: "your database password"
30 | volumeClaimTemplates:
31 | - metadata:
32 | name: mariadb-pvc
33 | spec:
34 | accessModes:
35 | - ReadWriteOnce
36 | resources:
37 | requests:
38 | storage: 2Gi
39 | ---
40 | apiVersion: v1
41 | kind: Service
42 | metadata:
43 | name: mariadb-service
44 | spec:
45 | ports:
46 | - port: 3306
47 | clusterIP: None
48 | selector:
49 | app: mariadb-sts
50 |
--------------------------------------------------------------------------------
/Chapter09/9.2.1_StatefulSet_Redis/redis-configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: redis-config
5 | data:
6 | redis.conf: |
7 | bind 0.0.0.0
8 | port 6379
9 | protected-mode no
10 | appendonly yes
11 | dir /redis/data
12 |
--------------------------------------------------------------------------------
/Chapter09/9.2.1_StatefulSet_Redis/redis-statefulset.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: StatefulSet
3 | metadata:
4 | name: redis
5 | spec:
6 | selector:
7 | matchLabels:
8 | app: redis-sts
9 | serviceName: redis-service
10 | replicas: 1
11 | template:
12 | metadata:
13 | labels:
14 | app: redis-sts
15 | spec:
16 | terminationGracePeriodSeconds: 10
17 | containers:
18 | - name: redis-container
19 | image: redis:latest
20 | command: ["redis-server"]
21 | args: ["/redis/conf/redis.conf"]
22 | volumeMounts:
23 | - name: redis-configmap-volume
24 | mountPath: /redis/conf/
25 | - name: redis-pvc
26 | mountPath: /redis/data
27 | resources:
28 | requests:
29 | cpu: 1
30 | memory: 4Gi
31 | volumes:
32 | - name: redis-configmap-volume
33 | configMap:
34 | name: redis-config
35 | volumeClaimTemplates:
36 | - metadata:
37 | name: redis-pvc
38 | spec:
39 | accessModes: [ "ReadWriteOnce" ]
40 | resources:
41 | requests:
42 | storage: 1Gi
43 | ---
44 | apiVersion: v1
45 | kind: Service
46 | metadata:
47 | name: redis-service
48 | spec:
49 | ports:
50 | - port: 6379
51 | clusterIP: None
52 | selector:
53 | app: redis-sts
54 |
--------------------------------------------------------------------------------
/Chapter09/9.2.2_StatefulSet_Redis_Multi/redis-configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: redis-role-config
5 | data:
6 | primary.conf: |
7 | bind 0.0.0.0
8 | port 6379
9 | protected-mode no
10 | appendonly yes
11 | dir /redis/data
12 | replica.conf: |
13 | replicaof redis-0.redis-service 6379
14 | bind 0.0.0.0
15 | port 6379
16 | protected-mode no
17 | appendonly yes
18 | dir /redis/data
19 |
--------------------------------------------------------------------------------
/Chapter09/9.2.2_StatefulSet_Redis_Multi/redis-statefulset.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: StatefulSet
3 | metadata:
4 | name: redis
5 | spec:
6 | selector:
7 | matchLabels:
8 | app: redis-sts
9 | serviceName: redis-service
10 | replicas: 3
11 | template:
12 | metadata:
13 | labels:
14 | app: redis-sts
15 | spec:
16 | terminationGracePeriodSeconds: 10
17 | initContainers:
18 | - name: init-redis
19 | image: redis:latest
20 | command:
21 | - bash
22 | - "-c"
23 | - |
24 | set -ex
25 | # Generate server-id from Pod ordinal index.
26 | [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
27 | ordinal=${BASH_REMATCH[1]}
28 | echo "ordinal ${ordinal}"
29 | # Copy appropriate config files from config-map to emptyDir.
30 | mkdir -p /redis/conf/
31 | if [[ $ordinal -eq 0 ]]; then
32 | cp /mnt/redis-configmap/primary.conf /redis/conf/redis.conf
33 | else
34 | cp /mnt/redis-configmap/replica.conf /redis/conf/redis.conf
35 | fi
36 | cat /redis/conf/redis.conf
37 | volumeMounts:
38 | - name: redis-config-volume
39 | mountPath: /redis/conf/
40 | - name: redis-configmap-volume
41 | mountPath: /mnt/redis-configmap
42 | containers:
43 | - name: redis-container
44 | image: redis:latest
45 | command: ["redis-server"]
46 | args: ["/redis/conf/redis.conf"]
47 | volumeMounts:
48 | - name: redis-config-volume
49 | mountPath: /redis/conf/
50 | - name: redis-pvc
51 | mountPath: /redis/data
52 | resources:
53 | requests:
54 | cpu: 1
55 | memory: 4Gi
56 | volumes:
57 | - name: redis-configmap-volume
58 | configMap:
59 | name: redis-role-config
60 | - name: redis-config-volume
61 | emptyDir: {}
62 | volumeClaimTemplates:
63 | - metadata:
64 | name: redis-pvc
65 | spec:
66 | accessModes: [ "ReadWriteOnce" ]
67 | resources:
68 | requests:
69 | storage: 1Gi
70 | ---
71 | apiVersion: v1
72 | kind: Service
73 | metadata:
74 | name: redis-service
75 | spec:
76 | ports:
77 | - port: 6379
78 | clusterIP: None
79 | selector:
80 | app: redis-sts
81 |
--------------------------------------------------------------------------------
/Chapter09/9.4_EphemeralVolume/ephemeral_storageclass.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: storage.k8s.io/v1
2 | kind: StorageClass
3 | metadata:
4 | name: ephemeral
5 | parameters:
6 | type: pd-ssd
7 | provisioner: pd.csi.storage.gke.io
8 | reclaimPolicy: Delete
9 | volumeBindingMode: WaitForFirstConsumer
10 | allowVolumeExpansion: true
11 |
--------------------------------------------------------------------------------
/Chapter09/9.4_EphemeralVolume/ephemeralvolume_pod.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: ephemeralvolume-pod
5 | labels:
6 | pod: timeserver-pod
7 | spec:
8 | containers:
9 | - name: timeserver-container
10 | image: docker.io/wdenniss/timeserver:1
11 | volumeMounts:
12 | - mountPath: "/scratch"
13 | name: scratch-volume
14 | volumes:
15 | - name: scratch-volume
16 | ephemeral:
17 | volumeClaimTemplate:
18 | metadata:
19 | labels:
20 | type: scratch-volume
21 | spec:
22 | accessModes: [ "ReadWriteOnce" ]
23 | storageClassName: "ephemeral"
24 | resources:
25 | requests:
26 | storage: 1Ti
27 |
--------------------------------------------------------------------------------
/Chapter09/storageclass.yaml:
--------------------------------------------------------------------------------
1 | allowVolumeExpansion: true
2 | apiVersion: storage.k8s.io/v1
3 | kind: StorageClass
4 | metadata:
5 | annotations:
6 | components.gke.io/component-name: pdcsi
7 | components.gke.io/component-version: 0.16.11
8 | components.gke.io/layer: addon
9 | storageclass.kubernetes.io/is-default-class: "true"
10 | creationTimestamp: "2023-08-16T17:51:10Z"
11 | labels:
12 | addonmanager.kubernetes.io/mode: EnsureExists
13 | k8s-app: gcp-compute-persistent-disk-csi-driver
14 | name: standard-rwo
15 | resourceVersion: "1263"
16 | uid: adbc1224-988c-482b-889b-f1b9ba7ecef3
17 | parameters:
18 | type: pd-balanced
19 | provisioner: pd.csi.storage.gke.io
20 | reclaimPolicy: Delete
21 | volumeBindingMode: WaitForFirstConsumer
22 |
--------------------------------------------------------------------------------
/Chapter10/10.1.1_TaskQueue/deploy_worker.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: pi-worker
5 | spec:
6 | replicas: 2
7 | selector:
8 | matchLabels:
9 | pod: pi
10 | template:
11 | metadata:
12 | labels:
13 | pod: pi
14 | spec:
15 | containers:
16 | - name: pi-container
17 | image: docker.io/wdenniss/pi_worker:1
18 | env:
19 | - name: REDIS_HOST
20 | value: redis-0.redis-service
21 | - name: PYTHONUNBUFFERED
22 | value: "1"
23 |
--------------------------------------------------------------------------------
/Chapter10/10.1.2_TaskQueue2/deploy_worker.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: pi-worker
5 | spec:
6 | replicas: 2
7 | selector:
8 | matchLabels:
9 | pod: pi
10 | template:
11 | metadata:
12 | labels:
13 | pod: pi
14 | spec:
15 | containers:
16 | - name: pi-container
17 | image: docker.io/wdenniss/pi_worker:2
18 | env:
19 | - name: REDIS_HOST
20 | value: redis-0.redis-service
21 | - name: PYTHONUNBUFFERED
22 | value: "1"
23 | resources:
24 | requests:
25 | cpu: 250m
26 | memory: 250Mi
27 | terminationGracePeriodSeconds: 120
28 |
--------------------------------------------------------------------------------
/Chapter10/10.1.3_HPA-v1/worker_hpa_v1.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: autoscaling/v1
2 | kind: HorizontalPodAutoscaler
3 | metadata:
4 | name: pi-worker-autoscaler
5 | spec:
6 | minReplicas: 2
7 | maxReplicas: 10
8 | targetCPUUtilizationPercentage: 20
9 | scaleTargetRef:
10 | apiVersion: apps/v1
11 | kind: Deployment
12 | name: pi-worker
13 |
--------------------------------------------------------------------------------
/Chapter10/10.1.3_HPA/worker_hpa.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: autoscaling/v2
2 | kind: HorizontalPodAutoscaler
3 | metadata:
4 | name: pi-worker-autoscaler
5 | spec:
6 | scaleTargetRef:
7 | apiVersion: apps/v1
8 | kind: Deployment
9 | name: pi-worker
10 | minReplicas: 2
11 | maxReplicas: 10
12 | metrics:
13 | - type: Resource
14 | resource:
15 | name: cpu
16 | target:
17 | type: Utilization
18 | averageUtilization: 20
19 |
--------------------------------------------------------------------------------
/Chapter10/10.2.1_Job/job_addwork.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: Job
3 | metadata:
4 | name: addwork
5 | spec:
6 | backoffLimit: 2
7 | template:
8 | spec:
9 | containers:
10 | - name: pi-container
11 | image: docker.io/wdenniss/pi_worker:2
12 | command: ["python3", "add_tasks.py"]
13 | env:
14 | - name: REDIS_HOST
15 | value: redis-0.redis-service
16 | - name: PYTHONUNBUFFERED
17 | value: "1"
18 | restartPolicy: Never
19 |
--------------------------------------------------------------------------------
/Chapter10/10.2.2_CronJob/cronjob_addwork.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: CronJob
3 | metadata:
4 | name: addwork
5 | spec:
6 | schedule: "*/5 * * * *"
7 | jobTemplate:
8 | spec:
9 | backoffLimit: 2
10 | template:
11 | spec:
12 | containers:
13 | - name: pi-container
14 | image: docker.io/wdenniss/pi_worker:2
15 | command: ["python3", "add_tasks.py"]
16 | env:
17 | - name: REDIS_HOST
18 | value: redis-0.redis-service
19 | - name: PYTHONUNBUFFERED
20 | value: "1"
21 | restartPolicy: Never
22 |
--------------------------------------------------------------------------------
/Chapter10/10.2.2_CronJobBeta/cronjob_addwork_beta.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1beta1
2 | kind: CronJob
3 | metadata:
4 | name: addwork
5 | spec:
6 | schedule: "*/5 * * * *"
7 | jobTemplate:
8 | spec:
9 | backoffLimit: 2
10 | template:
11 | spec:
12 | containers:
13 | - name: pi-container
14 | image: docker.io/wdenniss/pi_worker:1
15 | command: ["python3", "add_tasks.py"]
16 | env:
17 | - name: REDIS_HOST
18 | value: redis-0.redis-service
19 | - name: PYTHONUNBUFFERED
20 | value: "1"
21 | restartPolicy: Never
22 |
--------------------------------------------------------------------------------
/Chapter10/10.3.1_JobWorker/job_worker.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: Job
3 | metadata:
4 | name: jobworker
5 | spec:
6 | backoffLimit: 2
7 | parallelism: 2
8 | template:
9 | metadata:
10 | labels:
11 | pod: pi
12 | spec:
13 | containers:
14 | - name: pi-container
15 | image: docker.io/wdenniss/pi_worker:3
16 | env:
17 | - name: REDIS_HOST
18 | value: redis-0.redis-service
19 | - name: PYTHONUNBUFFERED
20 | value: "1"
21 | - name: COMPLETE_WHEN_EMPTY
22 | value: "1"
23 | restartPolicy: OnFailure
24 |
--------------------------------------------------------------------------------
/Chapter10/10.3.2_IndexedJob/indexed_job.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: Job
3 | metadata:
4 | name: echo
5 | spec:
6 | completions: 5
7 | parallelism: 3
8 | completionMode: Indexed
9 | template:
10 | metadata:
11 | labels:
12 | pod: framerender
13 | spec:
14 | restartPolicy: Never
15 | containers:
16 | - name: 'worker'
17 | image: 'docker.io/library/busybox'
18 | command: ["echo", "render frame: $(JOB_COMPLETION_INDEX)"]
19 |
--------------------------------------------------------------------------------
/Chapter10/10.4_TaskLiveness/deploy_worker.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: pi-worker
5 | spec:
6 | replicas: 2
7 | selector:
8 | matchLabels:
9 | pod: pi
10 | template:
11 | metadata:
12 | labels:
13 | pod: pi
14 | spec:
15 | containers:
16 | - name: pi-container
17 | image: docker.io/wdenniss/pi_worker:4
18 | env:
19 | - name: REDIS_HOST
20 | value: redis-0.redis-service
21 | - name: PYTHONUNBUFFERED
22 | value: "1"
23 | livenessProbe:
24 | initialDelaySeconds: 60
25 | periodSeconds: 30
26 | exec:
27 | command: ["./check_liveness.sh", "logs/lastrun.date", "300"]
28 | successThreshold: 1
29 | timeoutSeconds: 1
30 | terminationGracePeriodSeconds: 120
31 |
--------------------------------------------------------------------------------
/Chapter10/10.4_TaskLiveness/job_worker.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: Job
3 | metadata:
4 | name: jobworker
5 | spec:
6 | backoffLimit: 2
7 | parallelism: 2
8 | template:
9 | metadata:
10 | labels:
11 | pod: pi
12 | spec:
13 | containers:
14 | - name: pi-container
15 | image: docker.io/wdenniss/pi_worker:4
16 | env:
17 | - name: REDIS_HOST
18 | value: redis-0.redis-service
19 | - name: PYTHONUNBUFFERED
20 | value: "1"
21 | - name: COMPLETE_WHEN_EMPTY
22 | value: "0"
23 | livenessProbe:
24 | initialDelaySeconds: 60
25 | periodSeconds: 30
26 | exec:
27 | command: ["./check_liveness.sh", "logs/lastrun.date", "300"]
28 | successThreshold: 1
29 | timeoutSeconds: 1
30 | restartPolicy: OnFailure
31 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3
2 | RUN pip install redis
3 | COPY . /app
4 | WORKDIR /app
5 | CMD python3 pi_worker.py
6 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WilliamDenniss/kubernetes-for-developers/0e79fe47d599c057422df7a77f8abbf9f407b62f/Chapter10/pi_worker/README.md
--------------------------------------------------------------------------------
/Chapter10/pi_worker/add_tasks.py:
--------------------------------------------------------------------------------
1 | import os
2 | import redis
3 | import random
4 |
5 | redis_host = os.environ.get('REDIS_HOST')
6 | assert redis_host != None
7 | r = redis.Redis(host=redis_host,
8 | port='6379',
9 | decode_responses=True)
10 |
11 | random.seed()
12 | for i in range(0, 10):
13 | rand = random.randint(10,100)
14 | iterations = rand * 100000
15 | r.rpush('queue:task', iterations)
16 | print("added task: " + str(iterations))
17 |
18 | print("queue depth", str(r.llen('queue:task')))
19 | print ("done")
20 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker/pi.py:
--------------------------------------------------------------------------------
1 | from decimal import *
2 |
3 | # Calculate pi using the Gregory-Leibniz infinity series
4 | def leibniz_pi(iterations):
5 |
6 | precision = 20
7 | getcontext().prec = 20
8 | piDiv4 = Decimal(1)
9 | odd = Decimal(3)
10 |
11 | for i in range(0, iterations):
12 | piDiv4 = piDiv4 - 1/odd
13 | odd = odd + 2
14 | piDiv4 = piDiv4 + 1/odd
15 | odd = odd + 2
16 |
17 | return piDiv4 * 4
18 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker/pi_test.py:
--------------------------------------------------------------------------------
1 | from pi import *
2 |
3 | print (leibniz_pi(100000000, 20))
4 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker/pi_worker.py:
--------------------------------------------------------------------------------
1 | import os
2 | import redis
3 | from pi import *
4 |
5 | redis_host = os.environ.get('REDIS_HOST')
6 | assert redis_host != None
7 | r = redis.Redis(host=redis_host,
8 | port='6379',
9 | decode_responses=True)
10 |
11 | print("starting")
12 | while True:
13 | task = r.blpop('queue:task')
14 | iterations = int(task[1])
15 | print("got task: " + str(iterations))
16 | pi = leibniz_pi(iterations)
17 | print (pi)
18 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker2/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3
2 | RUN pip install redis
3 | COPY . /app
4 | WORKDIR /app
5 | CMD python3 pi_worker.py
6 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker2/README.md:
--------------------------------------------------------------------------------
1 | Improves handling
--------------------------------------------------------------------------------
/Chapter10/pi_worker2/add_tasks.py:
--------------------------------------------------------------------------------
1 | import os
2 | import redis
3 | import random
4 |
5 | redis_host = os.environ.get('REDIS_HOST')
6 | assert redis_host != None
7 | r = redis.Redis(host=redis_host,
8 | port='6379',
9 | decode_responses=True)
10 |
11 | random.seed()
12 | for i in range(0, 10):
13 | rand = random.randint(10,100)
14 | iterations = rand * 100000
15 | r.rpush('queue:task', iterations)
16 | print("added task: " + str(iterations))
17 |
18 | print("queue depth", str(r.llen('queue:task')))
19 | print ("done")
20 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker2/pi.py:
--------------------------------------------------------------------------------
1 | from decimal import *
2 |
3 | # Calculate pi using the Gregory-Leibniz infinity series
4 | def leibniz_pi(iterations):
5 |
6 | precision = 20
7 | getcontext().prec = 20
8 | piDiv4 = Decimal(1)
9 | odd = Decimal(3)
10 |
11 | for i in range(0, iterations):
12 | piDiv4 = piDiv4 - 1/odd
13 | odd = odd + 2
14 | piDiv4 = piDiv4 + 1/odd
15 | odd = odd + 2
16 |
17 | return piDiv4 * 4
18 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker2/pi_test.py:
--------------------------------------------------------------------------------
1 | from pi import *
2 |
3 | print (leibniz_pi(100000000, 20))
4 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker2/pi_worker.py:
--------------------------------------------------------------------------------
1 | import os
2 | import signal
3 | import redis
4 | from pi import *
5 |
6 | redis_host = os.environ.get('REDIS_HOST')
7 | assert redis_host != None
8 | r = redis.Redis(host=redis_host,
9 | port='6379',
10 | decode_responses=True)
11 | running = True
12 |
13 | def signal_handler(signum, frame):
14 | print("got signal")
15 | running = False
16 |
17 | signal.signal(signal.SIGTERM, signal_handler)
18 |
19 | print("starting")
20 | while running:
21 | task = r.blpop('queue:task', 5)
22 | if task != None:
23 | iterations = int(task[1])
24 | print("got task: " + str(iterations))
25 | pi = leibniz_pi(iterations)
26 | print (pi)
27 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker3/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3
2 | RUN pip install redis
3 | COPY . /app
4 | WORKDIR /app
5 | CMD python3 pi_worker.py
6 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker3/README.md:
--------------------------------------------------------------------------------
1 | Adds termination for Job queue
--------------------------------------------------------------------------------
/Chapter10/pi_worker3/add_tasks.py:
--------------------------------------------------------------------------------
1 | import os
2 | import redis
3 | import random
4 |
5 | redis_host = os.environ.get('REDIS_HOST')
6 | assert redis_host != None
7 | r = redis.Redis(host=redis_host,
8 | port='6379',
9 | decode_responses=True)
10 |
11 | random.seed()
12 | for i in range(0, 10):
13 | rand = random.randint(10,100)
14 | iterations = rand * 100000
15 | r.rpush('queue:task', iterations)
16 | print("added task: " + str(iterations))
17 |
18 | print("queue depth", str(r.llen('queue:task')))
19 | print ("done")
20 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker3/pi.py:
--------------------------------------------------------------------------------
1 | from decimal import *
2 |
3 | # Calculate pi using the Gregory-Leibniz infinity series
4 | def leibniz_pi(iterations):
5 |
6 | precision = 20
7 | getcontext().prec = 20
8 | piDiv4 = Decimal(1)
9 | odd = Decimal(3)
10 |
11 | for i in range(0, iterations):
12 | piDiv4 = piDiv4 - 1/odd
13 | odd = odd + 2
14 | piDiv4 = piDiv4 + 1/odd
15 | odd = odd + 2
16 |
17 | return piDiv4 * 4
18 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker3/pi_test.py:
--------------------------------------------------------------------------------
1 | from pi import *
2 |
3 | print (leibniz_pi(100000000, 20))
4 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker3/pi_worker.py:
--------------------------------------------------------------------------------
1 | import os
2 | import signal
3 | import redis
4 | from pi import *
5 |
6 | redis_host = os.environ.get('REDIS_HOST')
7 | assert redis_host != None
8 | r = redis.Redis(host=redis_host,
9 | port='6379',
10 | decode_responses=True)
11 | running = True
12 |
13 | def signal_handler(signum, frame):
14 | print("got signal")
15 | running = False
16 |
17 | signal.signal(signal.SIGTERM, signal_handler)
18 |
19 | print("starting")
20 | while running:
21 | task = r.blpop('queue:task', 5)
22 | if task != None:
23 | iterations = int(task[1])
24 | print("got task: " + str(iterations))
25 | pi = leibniz_pi(iterations)
26 | print (pi)
27 | else:
28 | if os.getenv('COMPLETE_WHEN_EMPTY', '0') != '0':
29 | print ("no more work")
30 | running = False
31 |
32 | exit(0)
33 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker4/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3
2 | RUN pip install redis
3 | COPY . /app
4 | WORKDIR /app
5 | RUN mkdir logs
6 | CMD python3 pi_worker.py
7 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker4/README.md:
--------------------------------------------------------------------------------
1 | Adds termination for Job queue
--------------------------------------------------------------------------------
/Chapter10/pi_worker4/add_tasks.py:
--------------------------------------------------------------------------------
1 | import os
2 | import redis
3 | import random
4 |
5 | redis_host = os.environ.get('REDIS_HOST')
6 | assert redis_host != None
7 | r = redis.Redis(host=redis_host,
8 | port='6379',
9 | decode_responses=True)
10 |
11 | random.seed()
12 | for i in range(0, 10):
13 | rand = random.randint(10,100)
14 | iterations = rand * 100000
15 | r.rpush('queue:task', iterations)
16 | print("added task: " + str(iterations))
17 |
18 | print("queue depth", str(r.llen('queue:task')))
19 | print ("done")
20 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker4/check_liveness.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Liveness probe for batch process
4 | # The process writes a logfile every time it runs with the current Unix
5 | # timestamp.
6 |
7 | # Usage: process_liveness.sh
8 | # The file must contain only the latest date as a Unix timestamp and no
9 | # newlines
10 |
11 | if [ -z "$1" ] || [ -z "$2" ]; then
12 | echo >&2 'Missing parameters'
13 | echo >&2 'Usage: process_liveness.sh '
14 | echo >&2 ' e.g: process_liveness.sh lastrun.date 300'
15 | exit 1
16 | fi
17 |
18 | if ! rundate=$(<$1); then
19 | echo >&2 "Failed: unable to read logfile"
20 | exit 2
21 | fi
22 |
23 | curdate=$(date +'%s')
24 |
25 | time_difference=$((curdate-rundate))
26 |
27 | if [ $time_difference -gt $2 ]
28 | then
29 | echo >&2 "Liveness failing, timestamp too old."
30 | exit 1
31 | fi
32 |
33 | exit 0
34 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker4/liveness.py:
--------------------------------------------------------------------------------
1 | import os
2 | import time
3 |
4 | def update_liveness():
5 |
6 | timestamp = int(time.time())
7 | with open("logs/lastrun.date", "w") as myfile:
8 | myfile.write(f"{timestamp}")
9 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker4/pi.py:
--------------------------------------------------------------------------------
1 | from decimal import *
2 |
3 | # Calculate pi using the Gregory-Leibniz infinity series
4 | def leibniz_pi(iterations):
5 |
6 | precision = 20
7 | getcontext().prec = 20
8 | piDiv4 = Decimal(1)
9 | odd = Decimal(3)
10 |
11 | for i in range(0, iterations):
12 | piDiv4 = piDiv4 - 1/odd
13 | odd = odd + 2
14 | piDiv4 = piDiv4 + 1/odd
15 | odd = odd + 2
16 |
17 | return piDiv4 * 4
18 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker4/pi_test.py:
--------------------------------------------------------------------------------
1 | from pi import *
2 |
3 | print (leibniz_pi(100000000, 20))
4 |
--------------------------------------------------------------------------------
/Chapter10/pi_worker4/pi_worker.py:
--------------------------------------------------------------------------------
1 | import os
2 | import signal
3 | import redis
4 | from pi import *
5 | from liveness import *
6 |
7 | redis_host = os.environ.get('REDIS_HOST')
8 | assert redis_host != None
9 | r = redis.Redis(host=redis_host,
10 | port='6379',
11 | decode_responses=True)
12 | running = True
13 |
14 | def signal_handler(signum, frame):
15 | print("got signal")
16 | running = False
17 |
18 | signal.signal(signal.SIGTERM, signal_handler)
19 |
20 | print("starting")
21 | while running:
22 | update_liveness()
23 | task = r.blpop('queue:task', 5)
24 | if task != None:
25 | iterations = int(task[1])
26 | print("got task: " + str(iterations))
27 | pi = leibniz_pi(iterations)
28 | print (pi)
29 | else:
30 | if os.getenv('COMPLETE_WHEN_EMPTY', '0') != '0':
31 | print ("no more work")
32 | running = False
33 |
34 | exit(0)
35 |
--------------------------------------------------------------------------------
/Chapter11/11.3.2_CloudBuild/cloudbuild-deploy.yaml:
--------------------------------------------------------------------------------
1 | steps:
2 | - name: 'gcr.io/cloud-builders/kubectl'
3 | id: Deploy
4 | args:
5 | - 'apply'
6 | - '-f'
7 | - '$FOLDER'
8 | env:
9 | - 'CLOUDSDK_COMPUTE_REGION=us-west1'
10 | - 'CLOUDSDK_CONTAINER_CLUSTER=my-cluster'
11 |
--------------------------------------------------------------------------------
/Chapter11/11.4.1_StringSecrets/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:5
18 | env:
19 | - name: AVATAR_ENDPOINT
20 | value: http://robohash-internal
21 | - name: SECRET_KEY
22 | valueFrom:
23 | secretKeyRef:
24 | name: secrets-production
25 | key: SECRET_KEY
26 |
--------------------------------------------------------------------------------
/Chapter11/11.4.1_StringSecrets/secret.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Secret
3 | metadata:
4 | name: secrets-production
5 | type: Opaque
6 | stringData:
7 | SECRET_KEY: secret_value
8 | ANOTHER_KEY: another_secret_value
9 |
--------------------------------------------------------------------------------
/Chapter11/11.4.2_Base64Secrets/secret-base64.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Secret
3 | metadata:
4 | name: secrets-production
5 | type: Opaque
6 | data:
7 | SECRET_KEY: c2VjcmV0X3ZhbHVlCg==
8 |
--------------------------------------------------------------------------------
/Chapter11/11.4.2_Base64Secrets/secrets-multiple.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Secret
3 | metadata:
4 | name: secrets-production
5 | type: Opaque
6 | stringData:
7 | SECRET_KEY: secret_value
8 | ANOTHER_KEY: another_value
9 | data:
10 | ENCODED_KEY:
11 | VGhpcyBzdHJpbmcKbWlnaHQgYmUgaGFyZCB0byByZXByZXNlbnQgaW4gWUFNTCDwn5iFCg==
12 |
--------------------------------------------------------------------------------
/Chapter11/11.4.3_FileSecrets/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:5
18 | volumeMounts:
19 | - name: secret-volume
20 | mountPath: "/etc/config"
21 | readOnly: true
22 | volumes:
23 | - name: secret-volume
24 | secret:
25 | secretName: secret-files
26 |
--------------------------------------------------------------------------------
/Chapter11/11.4.3_FileSecrets/example.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIGsAgEAAiEA4TneQFg/UMsVGrAvsm1wkonC/5jX+ykJAMeNffnlPQkCAwEAAQIh
3 | ANgcs+MgClkXFQAP0SSvmJRmnRze3+zgUbN+u+rrYNRlAhEA+K0ghKRgKlzVnOxw
4 | qltgTwIRAOfb8LCVNf6FAdD+bJGwHycCED6YzfO1sONZBQiAWAf6Am8CEQDIEXI8
5 | fVSNHmp108UNZcNLAhEA3hHFV5jZppEHHHLy4F9Dnw==
6 | -----END RSA PRIVATE KEY-----
7 |
--------------------------------------------------------------------------------
/Chapter11/11.4.3_FileSecrets/secret_file.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Secret
3 | metadata:
4 | name: secret-files
5 | type: Opaque
6 | stringData:
7 | example.key: |
8 | -----BEGIN RSA PRIVATE KEY-----
9 | MIGsAgEAAiEA4TneQFg/UMsVGrAvsm1wkonC/5jX+ykJAMeNffnlPQkCAwEAAQIh
10 | ANgcs+MgClkXFQAP0SSvmJRmnRze3+zgUbN+u+rrYNRlAhEA+K0ghKRgKlzVnOxw
11 | qltgTwIRAOfb8LCVNf6FAdD+bJGwHycCED6YzfO1sONZBQiAWAf6Am8CEQDIEXI8
12 | fVSNHmp108UNZcNLAhEA3hHFV5jZppEHHHLy4F9Dnw==
13 | -----END RSA PRIVATE KEY-----
14 |
--------------------------------------------------------------------------------
/Chapter11/11.4.3_FileSecrets/secret_file_base64.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Secret
3 | metadata:
4 | name: secret-files
5 | type: Opaque
6 | data:
7 | example.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUdzQWdFQUFpRUE0VG5lUUZnL1VNc1ZHckF2c20xd2tvbkMvNWpYK3lrSkFNZU5mZm5sUFFrQ0F3RUFBUUloCkFOZ2NzK01nQ2xrWEZRQVAwU1N2bUpSbW5SemUzK3pnVWJOK3UrcnJZTlJsQWhFQStLMGdoS1JnS2x6Vm5PeHcKcWx0Z1R3SVJBT2ZiOExDVk5mNkZBZEQrYkpHd0h5Y0NFRDZZemZPMXNPTlpCUWlBV0FmNkFtOENFUURJRVhJOApmVlNOSG1wMTA4VU5aY05MQWhFQTNoSEZWNWpacHBFSEhITHk0RjlEbnc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
8 |
--------------------------------------------------------------------------------
/Chapter11/gitops/_debug/delete_evicated.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | # Script to delete evicted pods.
4 |
5 | # ref: https://github.com/kubernetes/kubernetes/issues/55051#issuecomment-342503382
6 |
7 | kubectl get pods --all-namespaces -ojson | jq -r '.items[] | select(.status.reason!=null) | select(.status.reason | contains("Evicted")) | .metadata.name + " " + .metadata.namespace' | xargs -n2 bash -c 'kubectl delete pods $0 --namespace=$1'
8 |
--------------------------------------------------------------------------------
/Chapter11/gitops/_ns/ns-production.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Namespace
3 | metadata:
4 | name: production
5 |
--------------------------------------------------------------------------------
/Chapter11/gitops/_ns/ns-staging.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Namespace
3 | metadata:
4 | name: staging
5 |
--------------------------------------------------------------------------------
/Chapter11/gitops/gitops_check.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | CURRENT_DIR=`echo "${PWD##*/}"`
4 | CURRENT_NAMESPACE=`kubectl config view --minify -o=jsonpath='{.contexts[0].context.namespace}'`
5 |
6 | if [ "$CURRENT_DIR" != "$CURRENT_NAMESPACE" ]; then
7 | >&2 echo "Wrong namespace (currently $CURRENT_NAMESPACE but" \
8 | "$CURRENT_DIR expected)"
9 | exit 1
10 | fi
11 |
12 | exit 0
13 |
--------------------------------------------------------------------------------
/Chapter11/gitops/production/rollout.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 |
3 | if [ $(../gitops_check.sh; echo $?) != 0 ]; then exit 1; fi
4 |
5 | kubectl apply -f .
6 |
--------------------------------------------------------------------------------
/Chapter11/gitops/staging/rollout.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 |
3 | if [ $(../gitops_check.sh; echo $?) != 0 ]; then exit 1; fi
4 |
5 | kubectl apply -f .
6 |
--------------------------------------------------------------------------------
/Chapter12/12.1_PDB/pdb.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: policy/v1
2 | kind: PodDisruptionBudget
3 | metadata:
4 | name: timeserver-pdb
5 | spec:
6 | maxUnavailable: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 |
--------------------------------------------------------------------------------
/Chapter12/12.2_DaemonSet/logreader.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: DaemonSet
3 | metadata:
4 | name: logreader
5 | spec:
6 | selector:
7 | matchLabels:
8 | pod: logreader-pod
9 | template:
10 | metadata:
11 | labels:
12 | pod: logreader-pod
13 | spec:
14 | containers:
15 | - image: ubuntu
16 | command:
17 | - bash
18 | - "-c"
19 | - |
20 | tail -f /var/log/containers/*_kube-system_*.log
21 | name: logreader-container
22 | resources:
23 | requests:
24 | cpu: 50m
25 | memory: 100Mi
26 | ephemeral-storage: 100Mi
27 | volumeMounts:
28 | - name: logpath
29 | mountPath: /var/log
30 | readOnly: true
31 | volumes:
32 | - hostPath:
33 | path: /var/log
34 | name: logpath
35 |
--------------------------------------------------------------------------------
/Chapter12/12.3_PodSecurityContext/admin-ds.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: DaemonSet
3 | metadata:
4 | name: admin-workload
5 | spec:
6 | selector:
7 | matchLabels:
8 | name: admin-app
9 | template:
10 | metadata:
11 | labels:
12 | name: admin-app
13 | spec:
14 | containers:
15 | - name: admin-container
16 | image: ubuntu
17 | command: ["sleep", "infinity"]
18 | securityContext:
19 | privileged: true
20 |
--------------------------------------------------------------------------------
/Chapter12/12.3_PodSecurityContext/pod.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: ubuntu
5 | labels:
6 | pod: ubuntu-pod
7 | spec:
8 | containers:
9 | - name: ubuntu-container
10 | image: ubuntu
11 | command: ["sleep", "infinity"]
12 | securityContext:
13 | runAsNonRoot: true
14 | runAsUser: 1001
15 | allowPrivilegeEscalation: false
16 | capabilities:
17 | drop:
18 | - ALL
19 |
--------------------------------------------------------------------------------
/Chapter12/12.4_NonRootContainers/1_permission_error/deploy-runas.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:6
18 | securityContext:
19 | runAsNonRoot: true
20 | runAsUser: 1001
21 |
--------------------------------------------------------------------------------
/Chapter12/12.4_NonRootContainers/1_permission_error/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:6
18 | securityContext:
19 | runAsNonRoot: true
20 |
--------------------------------------------------------------------------------
/Chapter12/12.4_NonRootContainers/1_permission_error/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: timeserver
5 | spec:
6 | selector:
7 | pod: timeserver-pod
8 | ports:
9 | - port: 80
10 | targetPort: 80
11 | protocol: TCP
12 | type: LoadBalancer
13 |
--------------------------------------------------------------------------------
/Chapter12/12.4_NonRootContainers/2_fixed/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: timeserver
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | pod: timeserver-pod
10 | template:
11 | metadata:
12 | labels:
13 | pod: timeserver-pod
14 | spec:
15 | containers:
16 | - name: timeserver-container
17 | image: docker.io/wdenniss/timeserver:7
18 | securityContext:
19 | runAsNonRoot: true
20 | runAsUser: 1001
21 |
--------------------------------------------------------------------------------
/Chapter12/12.4_NonRootContainers/2_fixed/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: timeserver
5 | spec:
6 | selector:
7 | pod: timeserver-pod
8 | ports:
9 | - port: 80
10 | targetPort: 8080
11 | protocol: TCP
12 | type: LoadBalancer
13 |
--------------------------------------------------------------------------------
/Chapter12/12.5_PodSecurityAdmission/namespace.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Namespace
3 | metadata:
4 | name: team1
5 | labels:
6 | pod-security.kubernetes.io/enforce: restricted
7 | pod-security.kubernetes.io/enforce-version: v1.28
8 |
--------------------------------------------------------------------------------
/Chapter12/12.5_PodSecurityAdmission/nonroot_pod.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: timeserver-pod
5 | spec:
6 | securityContext:
7 | seccompProfile:
8 | type: RuntimeDefault
9 | containers:
10 | - name: timeserver-container
11 | image: docker.io/wdenniss/timeserver:7
12 | securityContext:
13 | runAsNonRoot: true
14 | allowPrivilegeEscalation: false
15 | runAsUser: 1001
16 | capabilities:
17 | drop:
18 | - ALL
19 |
--------------------------------------------------------------------------------
/Chapter12/12.6_RBAC/clusterrole.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: developer-cluster-access
5 | rules:
6 | - apiGroups:
7 | - scheduling.k8s.io
8 | resources:
9 | - priorityclasses
10 | verbs: ["*"]
11 | - apiGroups:
12 | - storage.k8s.io
13 | resources:
14 | - storageclasses
15 | verbs: ["*"]
16 | - apiGroups:
17 | - ""
18 | resources:
19 | - persistentvolumes
20 | - namespaces
21 | verbs: ["get", "list"]
22 |
--------------------------------------------------------------------------------
/Chapter12/12.6_RBAC/clusterrolebinding.yaml:
--------------------------------------------------------------------------------
1 | kind: ClusterRoleBinding
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | metadata:
4 | name: developerA
5 | namespace: team1
6 | roleRef:
7 | kind: ClusterRole
8 | name: developer-cluster-access
9 | apiGroup: rbac.authorization.k8s.io
10 | subjects:
11 | - kind: User
12 | name: example@gmail.com # set this to your user identity
13 |
--------------------------------------------------------------------------------
/Chapter12/12.6_RBAC/role.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: Role
3 | metadata:
4 | name: developer-access
5 | namespace: team1
6 | rules:
7 | - apiGroups:
8 | - "" # "" indicates the core API group
9 | resources:
10 | - namespaces
11 | verbs: ["get"]
12 | - apiGroups:
13 | - ""
14 | resources:
15 | - events
16 | - pods
17 | - pods/log
18 | - pods/portforward
19 | - services
20 | - secrets
21 | - configmaps
22 | - persistentvolumeclaims
23 | verbs: ["*"]
24 | - apiGroups:
25 | - apps
26 | - autoscaling
27 | - batch
28 | - networking.k8s.io
29 | - policy
30 | resources: ["*"]
31 | verbs: ["*"]
32 |
--------------------------------------------------------------------------------
/Chapter12/12.6_RBAC/rolebinding.yaml:
--------------------------------------------------------------------------------
1 | kind: RoleBinding
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | metadata:
4 | name: developerA
5 | namespace: team1
6 | roleRef:
7 | kind: Role
8 | name: developer-access
9 | apiGroup: rbac.authorization.k8s.io
10 | subjects:
11 | # Google Cloud user account
12 | - kind: User
13 | name: example@gmail.com
14 |
--------------------------------------------------------------------------------
/Chapter12/timeserver6/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.12
2 | ENV PYTHONUNBUFFERED 1
3 | COPY . /app
4 | WORKDIR /app
5 | RUN mkdir logs
6 | CMD python3 server.py
7 |
--------------------------------------------------------------------------------
/Chapter12/timeserver6/server.py:
--------------------------------------------------------------------------------
1 | from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
2 | from datetime import datetime, timedelta
3 | import urllib.request
4 | import os
5 | import random
6 |
7 | last_ready_time = datetime.now()
8 |
9 | class RequestHandler(BaseHTTPRequestHandler):
10 |
11 | def do_GET(self):
12 | global last_ready_time
13 |
14 | match self.path:
15 | case '/':
16 | now = datetime.now()
17 | response_string = now.strftime("The time is %-I:%M %p, UTC.")
18 | self.respond_with(200, response_string)
19 | case '/healthz':
20 | if (datetime.now() > last_ready_time + timedelta(minutes=5)):
21 | self.respond_with(503, "Not Healthy")
22 | else:
23 | self.respond_with(200, "Healthy")
24 | case '/readyz':
25 | dependencies_connected = True
26 | # TODO: actually verify any dependencies
27 | if (dependencies_connected):
28 | last_ready_time = datetime.now()
29 | self.respond_with(200, "Ready")
30 | else:
31 | self.respond_with(503, "Not Ready")
32 | case _:
33 | self.respond_with(404, "Not Found")
34 |
35 | def respond_with(self, status_code: int, content: str) -> None:
36 | self.send_response(status_code)
37 | self.send_header('Content-type', 'text/plain')
38 | self.end_headers()
39 | with open("logs/log.txt", "a") as myfile:
40 | myfile.write(content + "\n")
41 | self.wfile.write(bytes(content, "utf-8"))
42 |
43 | def startServer():
44 | try:
45 | server = ThreadingHTTPServer(('', 80), RequestHandler)
46 | print("Listening on " + ":".join(map(str, server.server_address)))
47 | server.serve_forever()
48 | except KeyboardInterrupt:
49 | server.shutdown()
50 |
51 | if __name__== "__main__":
52 | startServer()
53 |
--------------------------------------------------------------------------------
/Chapter12/timeserver7/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.12
2 | ENV PYTHONUNBUFFERED 1
3 | COPY . /app
4 | WORKDIR /app
5 | RUN mkdir logs
6 | RUN chgrp -R 0 logs \
7 | && chmod -R g+rwX logs
8 | CMD python3 server.py
9 |
--------------------------------------------------------------------------------
/Chapter12/timeserver7/server.py:
--------------------------------------------------------------------------------
1 | from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
2 | from datetime import datetime, timedelta
3 | import urllib.request
4 | import os
5 | import random
6 |
7 | last_ready_time = datetime.now()
8 |
9 | class RequestHandler(BaseHTTPRequestHandler):
10 |
11 | def do_GET(self):
12 | global last_ready_time
13 |
14 | match self.path:
15 | case '/':
16 | now = datetime.now()
17 | response_string = now.strftime("The time is %-I:%M %p, UTC.")
18 | self.respond_with(200, response_string)
19 | case '/healthz':
20 | if (datetime.now() > last_ready_time + timedelta(minutes=5)):
21 | self.respond_with(503, "Not Healthy")
22 | else:
23 | self.respond_with(200, "Healthy")
24 | case '/readyz':
25 | dependencies_connected = True
26 | # TODO: actually verify any dependencies
27 | if (dependencies_connected):
28 | last_ready_time = datetime.now()
29 | self.respond_with(200, "Ready")
30 | else:
31 | self.respond_with(503, "Not Ready")
32 | case _:
33 | self.respond_with(404, "Not Found")
34 |
35 | def respond_with(self, status_code: int, content: str) -> None:
36 | self.send_response(status_code)
37 | self.send_header('Content-type', 'text/plain')
38 | self.end_headers()
39 | with open("logs/log.txt", "a") as myfile:
40 | myfile.write(content + "\n")
41 | self.wfile.write(bytes(content, "utf-8"))
42 |
43 | def startServer():
44 | try:
45 | server = ThreadingHTTPServer(('', 8080), RequestHandler)
46 | print("Listening on " + ":".join(map(str, server.server_address)))
47 | server.serve_forever()
48 | except KeyboardInterrupt:
49 | server.shutdown()
50 |
51 | if __name__== "__main__":
52 | startServer()
53 |
--------------------------------------------------------------------------------
/Denniss-Kubernetes-720.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WilliamDenniss/kubernetes-for-developers/0e79fe47d599c057422df7a77f8abbf9f407b62f/Denniss-Kubernetes-720.png
--------------------------------------------------------------------------------
/Denniss-Kubernetes-HI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WilliamDenniss/kubernetes-for-developers/0e79fe47d599c057422df7a77f8abbf9f407b62f/Denniss-Kubernetes-HI.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kubernetes for Developers
2 |
3 | Code samples and experiments to accompany the book
4 | [Kubernetes for Developers](https://www.manning.com/books/kubernetes-for-developers),
5 | by William Denniss.
6 |
7 | *This is not an officially supported Google product*
8 |
9 |
10 |
11 | ## How to Use This Repository
12 |
13 | The folder names (Chapter01, Chapter02, etc) correspond to the chapters in the book. There's a subfolder for each
14 | subsection, so samples for Section 5.1.3 in Chapter 5 are found in folder `Chapter05/5.1.3_DescriptiveTitle`. If
15 | a section has multiple discrete samples, they will be in separate folders like `Chapter05/5.1.3_Sample1`,
16 | `Chapter05/5.1.3_Sample2`.
17 |
18 | All the code listings in the book are included as files in this repo. The intent is to make it
19 | easy to grab the files and try them out yourself, following along with the text of the book.
20 |
21 | In some cases I've included a more comprehensive sample or experiment in this repo than what's in
22 | the book, in order for you to easily try the concept out yourself. In those cases, the folder
23 | has a `README.md` file with some instructions not found in the book (but still related to
24 | the concept being discussed in that section).
25 |
26 | ## Running the Samples
27 |
28 | All samples assume you have a Kubernetes cluster setup, and `kubectl` authenticated.
29 |
30 | Generally, to deploy a sample, from the folder containing the YAML files,
31 | run `kubectl create -f .` (which will create all the Kubernetes resources defined in the current
32 | folder), and to cleanup, run `kubectl delete -f .`.
33 |
34 | The samples are only designed to be deployed one at a time. If you try to deploy 2 without
35 | cleaning up, there will likely be issues.
36 |
37 | To reset your cluster to a blank state (CAUTION: only do this
38 | on a test cluster!), you can run the following one-liner:
39 |
40 | ```
41 | # Delete all objects from the current namespace
42 | kubectl delete service,deployment,ingress,PriorityClass --all
43 | ```
44 |
45 |
--------------------------------------------------------------------------------