├── .gitignore ├── README.md └── example-app ├── Dockerfile ├── binding.yml ├── frontend-with-secret.yaml ├── frontend.yaml ├── ingress.yaml ├── package-lock.json ├── package.json ├── server.js └── values.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules/* 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Best Practices (2nd Edition) 2 | This is the repository for "Kubernetes Best Practices, 2nd Edition". 3 | 4 | *It is currently under construction, content will be added over coming months* 5 | 6 | ## Contents 7 | * [example-app](./example-app/) - An example application for deploying to Kubernetes. 8 | -------------------------------------------------------------------------------- /example-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY server.js /app 6 | 7 | RUN npm install redis 8 | 9 | CMD node /app/server.js -------------------------------------------------------------------------------- /example-app/binding.yml: -------------------------------------------------------------------------------- 1 | kind: ClusterRoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: view 5 | subjects: 6 | - kind: User 7 | name: system:serviceaccount:default:default 8 | apiGroup: rbac.authorization.k8s.io 9 | roleRef: 10 | kind: ClusterRole 11 | name: admin 12 | apiGroup: rbac.authorization.k8s.io 13 | -------------------------------------------------------------------------------- /example-app/frontend-with-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | run: frontend 6 | name: frontend 7 | namespace: default 8 | spec: 9 | replicas: 2 10 | selector: 11 | matchLabels: 12 | run: frontend 13 | template: 14 | metadata: 15 | labels: 16 | run: frontend 17 | spec: 18 | containers: 19 | - image: brendanburns/journal-server:latest 20 | imagePullPolicy: Always 21 | name: frontend 22 | volumeMounts: 23 | - name: passwd-volume 24 | readOnly: true 25 | mountPath: "/etc/redis-passwd" 26 | resources: 27 | requests: 28 | cpu: "1.0" 29 | memory: "1G" 30 | limits: 31 | cpu: "1.0" 32 | memory: "1G" 33 | dnsPolicy: ClusterFirst 34 | restartPolicy: Always 35 | volumes: 36 | - name: passwd-volume 37 | secret: 38 | secretName: redis-passwd 39 | -------------------------------------------------------------------------------- /example-app/frontend.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | run: frontend 6 | name: frontend 7 | namespace: default 8 | spec: 9 | replicas: 2 10 | selector: 11 | matchLabels: 12 | run: frontend 13 | template: 14 | metadata: 15 | labels: 16 | run: frontend 17 | spec: 18 | containers: 19 | - image: brendanburns/journal-server:latest 20 | imagePullPolicy: Always 21 | name: frontend 22 | terminationMessagePath: /dev/termination-log 23 | terminationMessagePolicy: File 24 | resources: 25 | requests: 26 | cpu: "1.0" 27 | memory: "1G" 28 | limits: 29 | cpu: "1.0" 30 | memory: "1G" 31 | dnsPolicy: ClusterFirst 32 | restartPolicy: Always 33 | -------------------------------------------------------------------------------- /example-app/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: frontend-ingress 5 | spec: 6 | rules: 7 | - http: 8 | paths: 9 | - path: /api 10 | pathType: Prefix 11 | backend: 12 | service: 13 | name: frontend 14 | port: 15 | number: 8080 16 | # NOTE: this should come after /api or else it will hijack requests 17 | - path: / 18 | pathType: Prefix 19 | backend: 20 | service: 21 | name: nginx 22 | port: 23 | number: 80 24 | -------------------------------------------------------------------------------- /example-app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kbp-sample", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "redis": "^4.6.14" 9 | } 10 | }, 11 | "node_modules/@redis/bloom": { 12 | "version": "1.2.0", 13 | "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", 14 | "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", 15 | "peerDependencies": { 16 | "@redis/client": "^1.0.0" 17 | } 18 | }, 19 | "node_modules/@redis/client": { 20 | "version": "1.5.16", 21 | "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.16.tgz", 22 | "integrity": "sha512-X1a3xQ5kEMvTib5fBrHKh6Y+pXbeKXqziYuxOUo1ojQNECg4M5Etd1qqyhMap+lFUOAh8S7UYevgJHOm4A+NOg==", 23 | "dependencies": { 24 | "cluster-key-slot": "1.1.2", 25 | "generic-pool": "3.9.0", 26 | "yallist": "4.0.0" 27 | }, 28 | "engines": { 29 | "node": ">=14" 30 | } 31 | }, 32 | "node_modules/@redis/graph": { 33 | "version": "1.1.1", 34 | "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", 35 | "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", 36 | "peerDependencies": { 37 | "@redis/client": "^1.0.0" 38 | } 39 | }, 40 | "node_modules/@redis/json": { 41 | "version": "1.0.6", 42 | "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.6.tgz", 43 | "integrity": "sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==", 44 | "peerDependencies": { 45 | "@redis/client": "^1.0.0" 46 | } 47 | }, 48 | "node_modules/@redis/search": { 49 | "version": "1.1.6", 50 | "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.6.tgz", 51 | "integrity": "sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==", 52 | "peerDependencies": { 53 | "@redis/client": "^1.0.0" 54 | } 55 | }, 56 | "node_modules/@redis/time-series": { 57 | "version": "1.0.5", 58 | "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.5.tgz", 59 | "integrity": "sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==", 60 | "peerDependencies": { 61 | "@redis/client": "^1.0.0" 62 | } 63 | }, 64 | "node_modules/cluster-key-slot": { 65 | "version": "1.1.2", 66 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", 67 | "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", 68 | "engines": { 69 | "node": ">=0.10.0" 70 | } 71 | }, 72 | "node_modules/generic-pool": { 73 | "version": "3.9.0", 74 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", 75 | "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", 76 | "engines": { 77 | "node": ">= 4" 78 | } 79 | }, 80 | "node_modules/redis": { 81 | "version": "4.6.14", 82 | "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.14.tgz", 83 | "integrity": "sha512-GrNg/e33HtsQwNXL7kJT+iNFPSwE1IPmd7wzV3j4f2z0EYxZfZE7FVTmUysgAtqQQtg5NXF5SNLR9OdO/UHOfw==", 84 | "workspaces": [ 85 | "./packages/*" 86 | ], 87 | "dependencies": { 88 | "@redis/bloom": "1.2.0", 89 | "@redis/client": "1.5.16", 90 | "@redis/graph": "1.1.1", 91 | "@redis/json": "1.0.6", 92 | "@redis/search": "1.1.6", 93 | "@redis/time-series": "1.0.5" 94 | } 95 | }, 96 | "node_modules/yallist": { 97 | "version": "4.0.0", 98 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 99 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /example-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "redis": "^4.6.14" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /example-app/server.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const redis = require('redis'); 3 | 4 | const client = redis.createClient({ 5 | 'host': '127.0.0.1' 6 | }); 7 | 8 | const port = 8080; 9 | 10 | const requestHandler = (request, response) => { 11 | console.log(request.url); 12 | if (!request.url.startsWith('/api')) { 13 | response.writeHead(404); 14 | response.end('Not found'); 15 | return; 16 | } 17 | if (request.method != 'GET' && request.method != 'POST') { 18 | response.writeHead(400); 19 | response.end('Unsupported method.'); 20 | return; 21 | } 22 | const key = 'journal-key'; 23 | client.get(key, (err, value) => { 24 | if (err) { 25 | response.writeHead(500); 26 | response.end(err.toString()); 27 | return; 28 | } 29 | var journals = []; 30 | if (value) { 31 | journals = JSON.parse(value); 32 | } 33 | if (request.method == 'GET') { 34 | response.writeHead(200); 35 | response.end(JSON.stringify(journals)); 36 | } 37 | if (request.method == 'POST') { 38 | try { 39 | let body = []; 40 | request.on('data', (chunk) => { 41 | body.push(chunk); 42 | }).on('end', () => { 43 | body = Buffer.concat(body).toString(); 44 | const msg = JSON.parse(body); 45 | journals.push(msg); 46 | client.set(key, JSON.stringify(journals)); 47 | response.writeHead(200); 48 | response.end(JSON.stringify(journals)); 49 | }); 50 | } catch (err) { 51 | response.writeHeader(500); 52 | response.end(err.toString()); 53 | return; 54 | } 55 | } 56 | }); 57 | return; 58 | } 59 | 60 | const server = http.createServer(requestHandler); 61 | 62 | server.listen(port, (err) => { 63 | if (err) { 64 | return console.log('could not start server', err); 65 | } 66 | 67 | console.log('api server up and running.'); 68 | }) -------------------------------------------------------------------------------- /example-app/values.yaml: -------------------------------------------------------------------------------- 1 | controller: 2 | service: 3 | type: NodePort 4 | nodePorts: 5 | http: 32080 6 | https: 32443 7 | --------------------------------------------------------------------------------