├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── autoscaling ├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── app.js ├── bin │ └── www ├── config.js ├── k8s │ ├── deployment.yaml │ └── service.yaml ├── locust │ ├── docker-image │ │ ├── Dockerfile │ │ ├── licenses │ │ │ ├── Flask │ │ │ │ └── LICENSE │ │ │ ├── Jinja2 │ │ │ │ └── LICENSE │ │ │ ├── MarkupSafe │ │ │ │ └── LICENSE │ │ │ ├── Werkzeug │ │ │ │ └── LICENSE │ │ │ ├── gevent │ │ │ │ └── LICENSE │ │ │ ├── greenlet │ │ │ │ └── LICENSE │ │ │ ├── itsdangerous │ │ │ │ └── LICENSE │ │ │ ├── licenses.txt │ │ │ ├── locustio │ │ │ │ └── LICENSE │ │ │ ├── msgpack-python │ │ │ │ └── LICENSE │ │ │ ├── pyzmq │ │ │ │ └── LICENSE │ │ │ └── requests │ │ │ │ └── LICENSE │ │ └── locust-tasks │ │ │ ├── requirements.txt │ │ │ ├── run.sh │ │ │ └── tasks.py │ └── k8s │ │ ├── locust-master-controller.yaml │ │ ├── locust-master-service.yaml │ │ └── locust-worker-controller.yaml ├── package-lock.json ├── package.json ├── routes.js └── scripts │ ├── add_autoscaling.sh │ ├── scale_load_runner.sh │ ├── startup.sh │ ├── startup_load_runner.sh │ ├── startup_wo_autoscaling.sh │ ├── teardown.sh │ └── teardown_load_runner.sh ├── batch-job ├── .gitignore ├── README.md ├── endpoint │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── app.js │ ├── bin │ │ └── www │ ├── config.js │ ├── package-lock.json │ ├── package.json │ └── routes.js ├── job │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── app.js │ ├── config.js │ ├── package-lock.json │ └── package.json ├── k8s │ ├── deployment.yaml │ ├── parallel-jobs.yaml │ ├── sequential-jobs.yaml │ ├── service.yaml │ └── single-job.yaml └── scripts │ ├── check-endpoint.sh │ ├── deploy.sh │ ├── run-parallel-job.sh │ ├── run-sequential-job.sh │ ├── run-single-job.sh │ ├── startup.sh │ └── teardown.sh ├── communication ├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── app.js ├── bin │ └── www ├── config.js ├── k8s │ ├── deployment_1.yaml │ ├── deployment_2.yaml │ ├── service_1.yaml │ └── service_2.yaml ├── package-lock.json ├── package.json ├── routes.js └── scripts │ ├── check-endpoint.sh │ ├── deploy.sh │ ├── startup.sh │ └── teardown.sh ├── cron ├── .gitignore ├── README.md ├── cron-container │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── app.js │ ├── config.js │ ├── package-lock.json │ └── package.json ├── endpoint │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── app.js │ ├── bin │ │ └── www │ ├── config.js │ ├── package-lock.json │ ├── package.json │ └── routes.js ├── k8s │ ├── cronjob.yaml │ ├── deployment.yaml │ └── service.yaml └── scripts │ ├── check-endpoint.sh │ ├── create_cronjob.sh │ ├── delete_cronjob.sh │ ├── deploy.sh │ ├── startup.sh │ └── teardown.sh ├── daemon ├── .gitignore ├── README.md ├── daemon-container │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── app.js │ ├── bin │ │ └── www │ ├── config.js │ ├── package-lock.json │ ├── package.json │ └── routes.js ├── endpoint │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── app.js │ ├── bin │ │ └── www │ ├── config.js │ ├── package-lock.json │ ├── package.json │ └── routes.js ├── k8s │ ├── daemonset.yaml │ ├── deployment.yaml │ └── service.yaml └── scripts │ ├── check-endpoint.sh │ ├── delete-scale.sh │ ├── deploy.sh │ ├── scale.sh │ ├── startup.sh │ └── teardown.sh ├── deployment-manager ├── .dockerignore ├── .gitignore ├── Dockerfile ├── NOTES.md ├── README.md ├── app.js ├── bin │ └── www ├── config.js ├── dm │ ├── cluster.yaml │ └── templates │ │ ├── cluster.jinja │ │ └── cluster.jinja.schema ├── k8s │ ├── deployment.yaml │ └── service.yaml ├── package-lock.json ├── package.json ├── routes.js └── scripts │ ├── check-endpoint.sh │ ├── create-dm.sh │ ├── delete-dm.sh │ ├── deploy.sh │ ├── startup.sh │ └── teardown.sh ├── external-communication ├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── app.js ├── bin │ └── www ├── config.js ├── k8s │ ├── deployment.yaml │ ├── external-service.yaml │ └── service.yaml ├── package.json ├── routes.js └── scripts │ ├── check-endpoint.sh │ ├── deploy.sh │ ├── startup.sh │ └── teardown.sh ├── helm ├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── app.js ├── bin │ └── www ├── config.js ├── endpoints │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── deployment.yaml │ │ ├── ingress.yaml │ │ └── service.yaml │ └── values.yaml ├── index.yaml ├── k8s │ ├── deployment.yaml │ └── service.yaml ├── package-lock.json ├── package.json ├── routes.js └── scripts │ ├── add__secure_redis.sh │ ├── add_helm.sh │ ├── add_redis.sh │ ├── add_secure_helm.sh │ ├── check-endpoint.sh │ ├── deploy.sh │ ├── remove_redis.sh │ ├── remove_secure_redis.sh │ ├── startup.sh │ ├── teardown.sh │ └── values │ ├── rbac-config.yaml │ ├── role-tiller.yaml │ ├── rolebinding-tiller.yaml │ └── values-production.yaml ├── partone ├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── app.js ├── bin │ └── www ├── config.js ├── k8s │ ├── deployment.yaml │ └── service.yaml ├── package-lock.json ├── package.json ├── routes.js └── scripts │ ├── check-endpoint.sh │ ├── deploy.sh │ ├── startup.sh │ └── teardown.sh └── secrets ├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── app.js ├── bin └── www ├── config.js ├── k8s ├── basic-config.yaml ├── deployment.yaml ├── files │ └── serviceAccountKey.json └── service.yaml ├── package-lock.json ├── package.json ├── routes.js └── scripts ├── check-endpoint.sh ├── create-secret.sh ├── deploy.sh ├── startup.sh └── teardown.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .DS_Store 61 | .idea 62 | *.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Blog Post Series 2 | Here is a variety of folders for the various blog posts that they are attached to all around Kubernetes. Feel free to play and make comments. You can find the [first post here](https://medium.com/@jonbcampos/kubernetes-day-one-30a80b5dcb29). 3 | 4 | [My Medium Posts](https://medium.com/@jonbcampos) 5 | -------------------------------------------------------------------------------- /autoscaling/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .dockerignore 3 | Dockerfile 4 | npm-debug.log 5 | .git 6 | .hg 7 | .svn 8 | test 9 | k8s 10 | .nyc_output 11 | coverage -------------------------------------------------------------------------------- /autoscaling/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store -------------------------------------------------------------------------------- /autoscaling/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jonathan Campos 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM gcr.io/google_appengine/nodejs 15 | MAINTAINER Jonathan Campos 16 | RUN /usr/local/bin/install_node '>=0.12.7' 17 | COPY . /app/ 18 | RUN npm install --unsafe-perm || \ 19 | ((if [ -f npm-debug.log ]; then \ 20 | cat npm-debug.log; \ 21 | fi) && false) 22 | CMD npm start -------------------------------------------------------------------------------- /autoscaling/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes - Autoscaling 2 | This is the scaling segment in a multipart series about Kubernetes and some of the finer points 3 | I've learned along the way when using [Kubernetes](https://kubernetes.io/) with 4 | [Google Cloud Platform](https://cloud.google.com/). 5 | 6 | You can read the accompanying post for this folder here: 7 | [Kubernetes - Autoscaling](https://medium.com/google-cloud/kubernetes-cluster-autoscaler-f1948a0f686d) and [Kubernetes - Horizontal Pod Scaling](https://medium.com/google-cloud/kubernetes-horizontal-pod-scaling-190e95c258f5). 8 | 9 | To run this code here are the quick steps. 10 | 11 | ### To startup and deploy with autoscaling: 12 | ```bash 13 | git clone https://github.com/jonbcampos/kubernetes-series.git 14 | cd kubernetes-series/autoscaling/scripts 15 | sh startup.sh 16 | sh deploy.sh 17 | sh check-endpoint.sh 18 | ``` 19 | 20 | ### To startup and deploy withOUT autoscaling: 21 | ```bash 22 | git clone https://github.com/jonbcampos/kubernetes-series.git 23 | cd kubernetes-series/autoscaling/scripts 24 | sh startup_wo_autoscaling.sh 25 | sh deploy.sh 26 | sh check-endpoint.sh 27 | ``` 28 | 29 | ### To Teardown: 30 | ```bash 31 | cd kubernetes-series/autoscaling/scripts # if necessary 32 | sh teardown.sh 33 | ``` -------------------------------------------------------------------------------- /autoscaling/app.js: -------------------------------------------------------------------------------- 1 | const createError = require('http-errors'); 2 | const express = require('express'); 3 | const logger = require('morgan'); 4 | const config = require('./config'); 5 | const healthCheck = require('express-healthcheck'); 6 | const pkg = require('./package'); 7 | 8 | //------------------------------------- 9 | // 10 | // setup the app 11 | // 12 | //------------------------------------- 13 | const app = express(); 14 | app.use(logger('dev')); 15 | app.set('json spaces', 2); 16 | app.set('trust proxy', true); 17 | app.set('case sensitive routing', true); 18 | app.use(express.json()); 19 | app.use(express.urlencoded({ extended: false })); 20 | 21 | //------------------------------------- 22 | // 23 | // setup some routes for us to hit and 24 | // other helpful routes when working with kubernetes 25 | // 26 | //------------------------------------- 27 | app.use('/', require('./routes')); 28 | app.use('/healthcheck', healthCheck()); 29 | app.use('/readiness', function (req, res, next) { 30 | res.status(200).json({ ready: true }); 31 | }); 32 | app.get('/version', function (req, res, next) { 33 | const version = pkg.version; 34 | res.status(200).json({ version }); 35 | }); 36 | process.on('SIGTERM', function () { 37 | // cleanup environment, this is about to be shut down 38 | process.exit(0); 39 | }); 40 | 41 | //------------------------------------- 42 | // 43 | // catch 404 and forward to error handler 44 | // 45 | //------------------------------------- 46 | app.use(function (req, res, next) { 47 | next(createError(404)); 48 | }); 49 | 50 | //------------------------------------- 51 | // 52 | // error handler 53 | // 54 | //------------------------------------- 55 | app.use(function (err, req, res, next) { 56 | // set locals, only providing error in development 57 | err.response = { 58 | message: err.message, 59 | internalCode: err.code 60 | }; 61 | // render the error page 62 | res.status(err.status || 500).json(err.response); 63 | }); 64 | 65 | //------------------------------------- 66 | // 67 | // create server if this is main 68 | // 69 | //------------------------------------- 70 | if (module === require.main) { 71 | // Start the server 72 | const server = app.listen(config.get('PORT'), () => { 73 | const port = server.address().port; 74 | console.log(`App listening on port ${port}`); 75 | }); 76 | } 77 | 78 | module.exports = app; 79 | -------------------------------------------------------------------------------- /autoscaling/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | const app = require('../app'); 7 | const debug = require('debug')('partone:server'); 8 | const http = require('http'); 9 | 10 | /** 11 | * Get port from environment and store in Express. 12 | */ 13 | const port = normalizePort(process.env.PORT || '3000'); 14 | app.set('port', port); 15 | 16 | /** 17 | * Create HTTP server. 18 | */ 19 | const server = http.createServer(app); 20 | 21 | /** 22 | * Listen on provided port, on all network interfaces. 23 | */ 24 | server.listen(port); 25 | server.on('error', onError); 26 | server.on('listening', onListening); 27 | 28 | /** 29 | * Normalize a port into a number, string, or false. 30 | */ 31 | function normalizePort(val) { 32 | const port = parseInt(val, 10); 33 | 34 | if (isNaN(port)) { 35 | // named pipe 36 | return val; 37 | } 38 | 39 | if (port >= 0) { 40 | // port number 41 | return port; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | /** 48 | * Event listener for HTTP server "error" event. 49 | */ 50 | function onError(error) { 51 | if (error.syscall !== 'listen') { 52 | throw error; 53 | } 54 | 55 | const bind = typeof port === 'string' 56 | ? 'Pipe ' + port 57 | : 'Port ' + port; 58 | 59 | // handle specific listen errors with friendly messages 60 | switch (error.code) { 61 | case 'EACCES': 62 | console.error(bind + ' requires elevated privileges'); 63 | process.exit(1); 64 | break; 65 | case 'EADDRINUSE': 66 | console.error(bind + ' is already in use'); 67 | process.exit(1); 68 | break; 69 | default: 70 | throw error; 71 | } 72 | } 73 | 74 | /** 75 | * Event listener for HTTP server "listening" event. 76 | */ 77 | function onListening() { 78 | const addr = server.address(); 79 | const bind = typeof addr === 'string' 80 | ? 'pipe ' + addr 81 | : 'port ' + addr.port; 82 | debug('Listening on ' + bind); 83 | } 84 | -------------------------------------------------------------------------------- /autoscaling/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nconf = module.exports = require('nconf'); 4 | const path = require('path'); 5 | 6 | nconf 7 | // 1. command-line arguments 8 | .argv() 9 | // 2. env variables 10 | .env([ 11 | 'GCLOUD_PROJECT', 12 | 'POD_ENDPOINT' 13 | ]) 14 | // 3. config file 15 | .file({ file: path.join(__dirname, 'config.json') }) 16 | // 4. defaults 17 | .defaults({ 18 | GCLOUD_PROJECT: '', 19 | POD_ENDPOINT: '', 20 | }); 21 | 22 | // check required settings 23 | checkConfig('GCLOUD_PROJECT'); 24 | checkConfig('POD_ENDPOINT'); 25 | 26 | function checkConfig(setting) { 27 | if (!nconf.get(setting)) { 28 | throw new Error(`You must set ${setting} as an environment variable or in config.json!`); 29 | } 30 | } -------------------------------------------------------------------------------- /autoscaling/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta1 2 | kind: Deployment # it is a deployment 3 | metadata: 4 | name: endpoints # name of the Deployment 5 | labels: 6 | # any Pods with matching labels are included in this Deployment 7 | app: kubernetes-series 8 | tier: endpoints 9 | spec: 10 | # ReplicaSet specification 11 | replicas: 3 # we are making 3 Pods 12 | selector: 13 | matchLabels: 14 | # ReplicaSet labels will match Pods with the following labels 15 | tier: endpoints 16 | template: 17 | # Pod template 18 | metadata: 19 | labels: 20 | # Pod's labels 21 | app: kubernetes-series 22 | tier: endpoints 23 | spec: 24 | # Pod specification 25 | containers: 26 | # the container(s) in this Pod 27 | - name: container 28 | image: gcr.io/PROJECT_NAME/CONTAINER_NAME:latest 29 | # environment variables for the Pod 30 | env: 31 | - name: GCLOUD_PROJECT 32 | value: PROJECT_NAME 33 | # we are going to use this later 34 | # for now it creates a custom endpoint 35 | # for this pod 36 | - name: POD_ENDPOINT 37 | value: endpoint 38 | - name: NODE_ENV 39 | value: production 40 | ports: 41 | - containerPort: 80 -------------------------------------------------------------------------------- /autoscaling/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service # a way for the outside world to reach the Pods 3 | metadata: 4 | # any Pods with matching labels are included in this Service 5 | name: endpoints 6 | spec: 7 | # Service ports 8 | ports: 9 | - name: http 10 | port: 80 11 | targetPort: 8080 12 | protocol: TCP 13 | - name: https 14 | port: 443 15 | targetPort: 8443 16 | protocol: TCP 17 | # It includes a LoadBalancer between Pods 18 | type: LoadBalancer 19 | selector: 20 | app: kubernetes-series -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | # Start with a base Python 2.7.8 image 17 | FROM python:2.7.8 18 | 19 | MAINTAINER Sandeep Parikh 20 | 21 | # Add the licenses for third party software and libraries 22 | ADD licenses /licenses 23 | 24 | # Add the external tasks directory into /tasks 25 | ADD locust-tasks /locust-tasks 26 | 27 | # Install the required dependencies via pip 28 | RUN pip install -r /locust-tasks/requirements.txt 29 | 30 | # Expose the required Locust ports 31 | EXPOSE 5557 5558 8089 32 | 33 | # Set script to be executable 34 | RUN chmod 755 /locust-tasks/run.sh 35 | 36 | # Start Locust using LOCUS_OPTS environment variable 37 | ENTRYPOINT ["/locust-tasks/run.sh"] 38 | -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/licenses/Flask/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 by Armin Ronacher and contributors. See AUTHORS 2 | for more details. 3 | 4 | Some rights reserved. 5 | 6 | Redistribution and use in source and binary forms of the software as well 7 | as documentation, with or without modification, are permitted provided 8 | that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above 14 | copyright notice, this list of conditions and the following 15 | disclaimer in the documentation and/or other materials provided 16 | with the distribution. 17 | 18 | * The names of the contributors may not be used to endorse or 19 | promote products derived from this software without specific 20 | prior written permission. 21 | 22 | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND 23 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 24 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 26 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 27 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 28 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 33 | DAMAGE. 34 | -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/licenses/Jinja2/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 by the Jinja Team, see AUTHORS for more details. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/licenses/MarkupSafe/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 by Armin Ronacher and contributors. See AUTHORS 2 | for more details. 3 | 4 | Some rights reserved. 5 | 6 | Redistribution and use in source and binary forms of the software as well 7 | as documentation, with or without modification, are permitted provided 8 | that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above 14 | copyright notice, this list of conditions and the following 15 | disclaimer in the documentation and/or other materials provided 16 | with the distribution. 17 | 18 | * The names of the contributors may not be used to endorse or 19 | promote products derived from this software without specific 20 | prior written permission. 21 | 22 | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND 23 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 24 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 26 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 27 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 28 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 33 | DAMAGE. 34 | -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/licenses/Werkzeug/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 by the Werkzeug Team, see AUTHORS for more details. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | 15 | * The names of the contributors may not be used to endorse or 16 | promote products derived from this software without specific 17 | prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/licenses/gevent/LICENSE: -------------------------------------------------------------------------------- 1 | Except when otherwise stated (look at the beginning of each file) the software 2 | and the documentation in this project are copyrighted by: 3 | 4 | Denis Bilenko and the contributors, http://www.gevent.org 5 | 6 | and licensed under the MIT license: 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/licenses/greenlet/LICENSE: -------------------------------------------------------------------------------- 1 | The following files are derived from Stackless Python and are subject to the 2 | same license as Stackless Python: 3 | 4 | slp_platformselect.h 5 | files in platform/ directory 6 | 7 | See LICENSE.PSF and http://www.stackless.com/ for details. 8 | 9 | Unless otherwise noted, the files in greenlet have been released under the 10 | following MIT license: 11 | 12 | Copyright (c) Armin Rigo, Christian Tismer and contributors 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in 22 | all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | THE SOFTWARE. 31 | -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/licenses/itsdangerous/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 by Armin Ronacher and the Django Software Foundation. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/licenses/licenses.txt: -------------------------------------------------------------------------------- 1 | Flask (BSD, 0.10.1) https://github.com/mitsuhiko/flask/raw/master/LICENSE 2 | gevent (MIT, 1.0.1) https://github.com/gevent/gevent/raw/master/LICENSE 3 | greenlet (MIT, 0.4.5) https://github.com/python-greenlet/greenlet/raw/master/LICENSE 4 | itsdangerous (BSD, 0.24) https://github.com/mitsuhiko/itsdangerous/raw/master/LICENSE 5 | Jinja2 (BSD, 2.7.3) https://github.com/mitsuhiko/jinja2/raw/master/LICENSE 6 | locustio (MIT, 0.7.2) https://github.com/locustio/locust/raw/master/LICENSE 7 | MarkupSafe (BSD, 0.23) https://github.com/mitsuhiko/markupsafe/raw/master/LICENSE 8 | msgpack-python (Apache, 0.4.6) https://github.com/msgpack/msgpack-python/raw/master/COPYING 9 | pyzmq (BSD, 14.5.0) https://github.com/zeromq/pyzmq/raw/master/COPYING.BSD 10 | requests (Apache, 2.6.2) https://github.com/kennethreitz/requests/raw/master/LICENSE 11 | Werkzeug (BSD, 0.10.4) https://github.com/mitsuhiko/werkzeug/raw/master/LICENSE -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/licenses/locustio/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009-2010, Carl Byström, Jonatan Heyman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/licenses/msgpack-python/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2008-2011 INADA Naoki 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/licenses/pyzmq/LICENSE: -------------------------------------------------------------------------------- 1 | PyZMQ is licensed under the terms of the Modified BSD License (also known as 2 | New or Revised BSD), as follows: 3 | 4 | Copyright (c) 2009-2012, Brian Granger, Min Ragan-Kelley 5 | 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | 11 | Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | 14 | Redistributions in binary form must reproduce the above copyright notice, this 15 | list of conditions and the following disclaimer in the documentation and/or 16 | other materials provided with the distribution. 17 | 18 | Neither the name of PyZMQ nor the names of its contributors may be used to 19 | endorse or promote products derived from this software without specific prior 20 | written permission. 21 | 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 27 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/licenses/requests/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 Kenneth Reitz 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/locust-tasks/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | gevent==1.0.1 3 | greenlet==0.4.5 4 | itsdangerous==0.24 5 | Jinja2==2.7.3 6 | locustio==0.7.2 7 | MarkupSafe==0.23 8 | msgpack-python==0.4.6 9 | pyzmq==14.5.0 10 | requests==2.6.2 11 | Werkzeug==0.10.4 12 | -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/locust-tasks/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2015 Google Inc. All rights reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | LOCUST="/usr/local/bin/locust" 19 | LOCUS_OPTS="-f /locust-tasks/tasks.py --host=$TARGET_HOST" 20 | LOCUST_MODE=${LOCUST_MODE:-standalone} 21 | 22 | if [[ "$LOCUST_MODE" = "master" ]]; then 23 | LOCUS_OPTS="$LOCUS_OPTS --master" 24 | elif [[ "$LOCUST_MODE" = "worker" ]]; then 25 | LOCUS_OPTS="$LOCUS_OPTS --slave --master-host=$LOCUST_MASTER" 26 | fi 27 | 28 | echo "$LOCUST $LOCUS_OPTS" 29 | 30 | $LOCUST $LOCUS_OPTS -------------------------------------------------------------------------------- /autoscaling/locust/docker-image/locust-tasks/tasks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2015 Google Inc. All rights reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import uuid 19 | 20 | from datetime import datetime 21 | from locust import HttpLocust, TaskSet, task 22 | 23 | 24 | class CustomTaskSet(TaskSet): 25 | @task(1) 26 | def index(self): 27 | self.client.get('/') 28 | 29 | @task(1) 30 | def api(self): 31 | self.client.get('/api') 32 | 33 | @task(1) 34 | def healthcheck(self): 35 | self.client.get("/healthcheck") 36 | 37 | @task(1) 38 | def readiness(self): 39 | self.client.get("/readiness") 40 | 41 | @task(1) 42 | def version(self): 43 | self.client.get("/version") 44 | 45 | 46 | class LocustRunner(HttpLocust): 47 | task_set = CustomTaskSet 48 | min_wait = 5000 49 | max_wait = 15000 50 | -------------------------------------------------------------------------------- /autoscaling/locust/k8s/locust-master-controller.yaml: -------------------------------------------------------------------------------- 1 | kind: ReplicationController 2 | apiVersion: v1 3 | metadata: 4 | name: locust-master 5 | labels: 6 | name: locust 7 | role: master 8 | spec: 9 | replicas: 1 10 | selector: 11 | name: locust 12 | role: master 13 | template: 14 | metadata: 15 | labels: 16 | name: locust 17 | role: master 18 | spec: 19 | containers: 20 | - name: locust 21 | image: gcr.io/PROJECT_NAME/CONTAINER_NAME:latest 22 | env: 23 | - name: LOCUST_MODE 24 | value: master 25 | - name: TARGET_HOST 26 | value: http://LOAD_RUNNER_TARGET_HOST 27 | ports: 28 | - name: loc-master-web 29 | containerPort: 8089 30 | protocol: TCP 31 | - name: loc-master-p1 32 | containerPort: 5557 33 | protocol: TCP 34 | - name: loc-master-p2 35 | containerPort: 5558 36 | protocol: TCP -------------------------------------------------------------------------------- /autoscaling/locust/k8s/locust-master-service.yaml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: locust-master 5 | labels: 6 | name: locust 7 | role: master 8 | spec: 9 | ports: 10 | - port: 8089 11 | targetPort: loc-master-web 12 | protocol: TCP 13 | name: loc-master-web 14 | - port: 5557 15 | targetPort: loc-master-p1 16 | protocol: TCP 17 | name: loc-master-p1 18 | - port: 5558 19 | targetPort: loc-master-p2 20 | protocol: TCP 21 | name: loc-master-p2 22 | selector: 23 | name: locust 24 | role: master 25 | type: LoadBalancer -------------------------------------------------------------------------------- /autoscaling/locust/k8s/locust-worker-controller.yaml: -------------------------------------------------------------------------------- 1 | kind: ReplicationController 2 | apiVersion: v1 3 | metadata: 4 | name: locust-worker 5 | labels: 6 | name: locust 7 | role: worker 8 | spec: 9 | replicas: 10 10 | selector: 11 | name: locust 12 | role: worker 13 | template: 14 | metadata: 15 | labels: 16 | name: locust 17 | role: worker 18 | spec: 19 | containers: 20 | - name: locust 21 | image: gcr.io/PROJECT_NAME/CONTAINER_NAME:latest 22 | env: 23 | - name: LOCUST_MODE 24 | value: worker 25 | - name: LOCUST_MASTER 26 | value: locust-master 27 | - name: TARGET_HOST 28 | value: http://LOAD_RUNNER_TARGET_HOST -------------------------------------------------------------------------------- /autoscaling/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "autoscaling", 3 | "version": "0.0.1", 4 | "description": "kubernetes blog series - autoscaling", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jonbcampos/kubernetes-series" 9 | }, 10 | "author": { 11 | "name": "Jonathan Campos", 12 | "url": "https://github.com/jonbcampos" 13 | }, 14 | "contributors": [], 15 | "homepage": "http://jonbcampos.com/", 16 | "bugs": { 17 | "url": "https://github.com/jonbcampos/kubernetes-series/issues" 18 | }, 19 | "main": "app.js", 20 | "bin": "bin", 21 | "engines": { 22 | "node": ">=4.3.2" 23 | }, 24 | "scripts": { 25 | "start": "node ./bin/www", 26 | "debug": "node $NODE_DEBUG_OPTION ./app.js", 27 | "auth": "gcloud auth application-default login" 28 | }, 29 | "dependencies": { 30 | "body-parser": "^1.18.3", 31 | "cookie-parser": "~1.4.3", 32 | "debug": "~2.6.9", 33 | "ejs": "~2.5.7", 34 | "express": "~4.16.0", 35 | "express-healthcheck": "^0.1.0", 36 | "http-errors": "~1.6.2", 37 | "morgan": "~1.9.0", 38 | "nconf": "^0.10.0", 39 | "node-sass-middleware": "0.11.0", 40 | "request-promise": "^4.2.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /autoscaling/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const config = require('./config'); 4 | const os = require('os'); 5 | 6 | const router = express.Router(); 7 | 8 | //------------------------------------- 9 | // 10 | // Automatically parse request body as JSON 11 | // 12 | //------------------------------------- 13 | router.use(bodyParser.json()); 14 | 15 | //------------------------------------- 16 | // 17 | // Set Content-Type for all responses for these routes 18 | // 19 | //------------------------------------- 20 | router.use((req, res, next) => { 21 | res.set('Content-Type', 'application/json'); 22 | next(); 23 | }); 24 | 25 | //------------------------------------- 26 | // 27 | // Routes 28 | // 29 | //------------------------------------- 30 | router.get('/', function (req, res, next) { 31 | res.status(200).json({ hello: 'world' }); 32 | }); 33 | 34 | router.get('/api', function (req, res, next) { 35 | const remoteAddress = req.connection.remoteAddress; 36 | const hostName = os.hostname(); 37 | res.status(200).json({ remoteAddress, hostName }); 38 | }); 39 | 40 | router.get(`/${config.get('POD_ENDPOINT')}`, function (req, res, next) { 41 | const remoteAddress = req.connection.remoteAddress; 42 | const hostName = os.hostname(); 43 | const requestHost = req.headers.host; 44 | res.status(200).json({ remoteAddress, hostName, requestHost }); 45 | }); 46 | 47 | module.exports = router; -------------------------------------------------------------------------------- /autoscaling/scripts/add_autoscaling.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=autoscaling 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "enable services" 15 | gcloud services enable compute.googleapis.com 16 | gcloud services enable container.googleapis.com 17 | 18 | echo "adding autoscaling node pool" 19 | gcloud container node-pools create ${CLUSTER_NAME}-pool \ 20 | --cluster ${CLUSTER_NAME} \ 21 | --enable-autoscaling --min-nodes 3 --max-nodes 10 \ 22 | --zone ${INSTANCE_ZONE} 23 | 24 | kubectl autoscale deployment endpoints --cpu-percent=10 --min=1 --max=10 -------------------------------------------------------------------------------- /autoscaling/scripts/scale_load_runner.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-b 7 | export PROJECT_NAME=locust-tasks 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | export POD_COUNT=$1 11 | 12 | echo "setup" 13 | gcloud config set compute/zone ${INSTANCE_ZONE} 14 | 15 | echo "scale locust-worker pods" 16 | kubectl scale --replicas=${POD_COUNT} replicationcontrollers locust-worker 17 | 18 | echo "confirm scaling" 19 | kubectl get pods -l name=locust,role=worker -------------------------------------------------------------------------------- /autoscaling/scripts/startup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=autoscaling 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "enable services" 15 | gcloud services enable compute.googleapis.com 16 | gcloud services enable container.googleapis.com 17 | 18 | echo "creating container engine cluster" 19 | gcloud container clusters create ${CLUSTER_NAME} \ 20 | --preemptible \ 21 | --zone ${INSTANCE_ZONE} \ 22 | --scopes cloud-platform \ 23 | --enable-autoscaling --min-nodes 0 --max-nodes 10 \ 24 | --num-nodes 1 25 | 26 | echo "confirm cluster is running" 27 | gcloud container clusters list 28 | 29 | echo "get credentials" 30 | gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${INSTANCE_ZONE} 31 | 32 | echo "add autoscaling" 33 | kubectl autoscale deployment endpoints --cpu-percent=10 --min=1 --max=10 34 | 35 | echo "confirm connection to cluster" 36 | kubectl cluster-info 37 | 38 | echo "create cluster administrator" 39 | kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=$(gcloud config get-value account) 40 | 41 | echo "confirm the pod is running" 42 | kubectl get pods 43 | 44 | echo "list production services" 45 | kubectl get svc 46 | 47 | echo "enable services" 48 | gcloud services enable cloudbuild.googleapis.com 49 | 50 | echo "building containers" 51 | gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} ../ 52 | 53 | echo "name replace" 54 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/deployment.yaml 55 | sed -i "s/CONTAINER_NAME/${CONTAINER_NAME}/g" ../k8s/deployment.yaml 56 | 57 | echo "create pod-replicaset" 58 | kubectl apply -f ../k8s/deployment.yaml 59 | kubectl apply -f ../k8s/service.yaml 60 | 61 | echo "wait for ip address for cluster" 62 | external_ip="" 63 | while [ -z $external_ip ]; do 64 | echo "Waiting for end point..." 65 | external_ip=$(kubectl get svc endpoints --template="{{range .status.loadBalancer.ingress}}{{.ip}}{{end}}") 66 | [ -z "$external_ip" ] && sleep 10 67 | done 68 | 69 | echo "=====================================================" 70 | echo "=" 71 | echo "= your load balancer is available at ${external_ip}" 72 | echo "=" 73 | echo "=====================================================" -------------------------------------------------------------------------------- /autoscaling/scripts/startup_wo_autoscaling.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=autoscaling 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "enable services" 15 | gcloud services enable compute.googleapis.com 16 | gcloud services enable container.googleapis.com 17 | 18 | echo "creating container engine cluster" 19 | gcloud container clusters create ${CLUSTER_NAME} \ 20 | --preemptible \ 21 | --zone ${INSTANCE_ZONE} \ 22 | --scopes cloud-platform \ 23 | --num-nodes 3 24 | 25 | echo "confirm cluster is running" 26 | gcloud container clusters list 27 | 28 | echo "get credentials" 29 | gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${INSTANCE_ZONE} 30 | 31 | echo "confirm connection to cluster" 32 | kubectl cluster-info 33 | 34 | echo "create cluster administrator" 35 | kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=$(gcloud config get-value account) 36 | 37 | echo "confirm the pod is running" 38 | kubectl get pods 39 | 40 | echo "list production services" 41 | kubectl get svc 42 | 43 | echo "enable services" 44 | gcloud services enable cloudbuild.googleapis.com 45 | 46 | echo "building containers" 47 | gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} ../ -------------------------------------------------------------------------------- /autoscaling/scripts/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=autoscaling 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "clear out load runner if it still exists" 15 | sh teardown_load_runner.sh 16 | 17 | echo "remove cluster" 18 | gcloud container clusters delete ${CLUSTER_NAME} --quiet 19 | gcloud container clusters list 20 | 21 | echo "remove container" 22 | gcloud container images delete gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} --force-delete-tags --quiet -------------------------------------------------------------------------------- /autoscaling/scripts/teardown_load_runner.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-b 7 | export PROJECT_NAME=locust-tasks 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "remove cluster" 15 | gcloud container clusters delete ${CLUSTER_NAME} --quiet 16 | gcloud container clusters list 17 | 18 | echo "remove container" 19 | gcloud container images delete gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} --force-delete-tags --quiet -------------------------------------------------------------------------------- /batch-job/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store -------------------------------------------------------------------------------- /batch-job/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes - Batch Job 2 | This is the batch-job segment in a multipart series about Kubernetes and some of the finer points 3 | I've learned along the way when using [Kubernetes](https://kubernetes.io/) with 4 | [Google Cloud Platform](https://cloud.google.com/). 5 | 6 | You can read the accompanying post for this folder here: 7 | [Kubernetes - Running Background Tasks With Batch-Jobs](https://medium.com/@jonbcampos/kubernetes-running-background-tasks-with-batch-jobs-56482fbc853). 8 | 9 | To run this code here are the quick steps. 10 | 11 | ### To startup and deploy: 12 | ```bash 13 | git clone https://github.com/jonbcampos/kubernetes-series.git 14 | cd ~/kubernetes-series/batch-job/scripts 15 | sh startup.sh 16 | sh deploy.sh 17 | sh check-endpoint.sh 18 | sh run_single_job.sh 19 | sh run-sequential-job.sh 20 | sh run-sequential-job.sh 21 | ``` 22 | 23 | ### To Teardown: 24 | ```bash 25 | cd kubernetes-series/batch-job/scripts # if necessary 26 | sh teardown.sh 27 | ``` -------------------------------------------------------------------------------- /batch-job/endpoint/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .dockerignore 3 | Dockerfile 4 | npm-debug.log 5 | .git 6 | .hg 7 | .svn 8 | test 9 | k8s 10 | .nyc_output 11 | coverage -------------------------------------------------------------------------------- /batch-job/endpoint/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store -------------------------------------------------------------------------------- /batch-job/endpoint/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jonathan Campos 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM gcr.io/google_appengine/nodejs 15 | MAINTAINER Jonathan Campos 16 | RUN /usr/local/bin/install_node '>=0.12.7' 17 | COPY . /app/ 18 | RUN npm install --unsafe-perm || \ 19 | ((if [ -f npm-debug.log ]; then \ 20 | cat npm-debug.log; \ 21 | fi) && false) 22 | CMD npm start -------------------------------------------------------------------------------- /batch-job/endpoint/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | const app = require('../app'); 7 | const debug = require('debug')('endpoint:server'); 8 | const http = require('http'); 9 | 10 | /** 11 | * Get port from environment and store in Express. 12 | */ 13 | const port = normalizePort(process.env.PORT || '3000'); 14 | app.set('port', port); 15 | 16 | /** 17 | * Create HTTP server. 18 | */ 19 | const server = http.createServer(app); 20 | 21 | /** 22 | * Listen on provided port, on all network interfaces. 23 | */ 24 | server.listen(port); 25 | server.on('error', onError); 26 | server.on('listening', onListening); 27 | 28 | /** 29 | * Normalize a port into a number, string, or false. 30 | */ 31 | function normalizePort(val) { 32 | const port = parseInt(val, 10); 33 | 34 | if (isNaN(port)) { 35 | // named pipe 36 | return val; 37 | } 38 | 39 | if (port >= 0) { 40 | // port number 41 | return port; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | /** 48 | * Event listener for HTTP server "error" event. 49 | */ 50 | function onError(error) { 51 | if (error.syscall !== 'listen') { 52 | throw error; 53 | } 54 | 55 | const bind = typeof port === 'string' 56 | ? 'Pipe ' + port 57 | : 'Port ' + port; 58 | 59 | // handle specific listen errors with friendly messages 60 | switch (error.code) { 61 | case 'EACCES': 62 | console.error(bind + ' requires elevated privileges'); 63 | process.exit(1); 64 | break; 65 | case 'EADDRINUSE': 66 | console.error(bind + ' is already in use'); 67 | process.exit(1); 68 | break; 69 | default: 70 | throw error; 71 | } 72 | } 73 | 74 | /** 75 | * Event listener for HTTP server "listening" event. 76 | */ 77 | function onListening() { 78 | const addr = server.address(); 79 | const bind = typeof addr === 'string' 80 | ? 'pipe ' + addr 81 | : 'port ' + addr.port; 82 | debug('Listening on ' + bind); 83 | } 84 | -------------------------------------------------------------------------------- /batch-job/endpoint/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nconf = module.exports = require('nconf'); 4 | const path = require('path'); 5 | 6 | nconf 7 | // 1. command-line arguments 8 | .argv() 9 | // 2. env variables 10 | .env([ 11 | 'GCLOUD_PROJECT', 12 | 'POD_ENDPOINT' 13 | ]) 14 | // 3. config file 15 | .file({ file: path.join(__dirname, 'config.json') }) 16 | // 4. defaults 17 | .defaults({ 18 | GCLOUD_PROJECT: '', 19 | POD_ENDPOINT: '', 20 | }); 21 | 22 | // check required settings 23 | checkConfig('GCLOUD_PROJECT'); 24 | 25 | function checkConfig(setting) { 26 | if (!nconf.get(setting)) { 27 | throw new Error(`You must set ${setting} as an environment variable or in config.json!`); 28 | } 29 | } -------------------------------------------------------------------------------- /batch-job/endpoint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batchjob-endpoint", 3 | "version": "0.0.1", 4 | "description": "kubernetes blog series - batch job", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jonbcampos/kubernetes-series" 9 | }, 10 | "author": { 11 | "name": "Jonathan Campos", 12 | "url": "https://github.com/jonbcampos" 13 | }, 14 | "contributors": [], 15 | "homepage": "http://jonbcampos.com/", 16 | "bugs": { 17 | "url": "https://github.com/jonbcampos/kubernetes-series/issues" 18 | }, 19 | "main": "app.js", 20 | "bin": "bin", 21 | "engines": { 22 | "node": ">=4.3.2" 23 | }, 24 | "scripts": { 25 | "start": "node ./bin/www", 26 | "debug": "node $NODE_DEBUG_OPTION ./app.js", 27 | "auth": "gcloud auth application-default login" 28 | }, 29 | "dependencies": { 30 | "body-parser": "^1.18.3", 31 | "cookie-parser": "~1.4.3", 32 | "debug": "~2.6.9", 33 | "ejs": "~2.5.7", 34 | "express": "~4.16.0", 35 | "express-healthcheck": "^0.1.0", 36 | "http-errors": "~1.6.2", 37 | "morgan": "~1.9.0", 38 | "nconf": "^0.10.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /batch-job/job/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .dockerignore 3 | Dockerfile 4 | npm-debug.log 5 | .git 6 | .hg 7 | .svn 8 | test 9 | k8s 10 | .nyc_output 11 | coverage -------------------------------------------------------------------------------- /batch-job/job/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store -------------------------------------------------------------------------------- /batch-job/job/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jonathan Campos 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM gcr.io/google_appengine/nodejs 15 | MAINTAINER Jonathan Campos 16 | RUN /usr/local/bin/install_node '>=0.12.7' 17 | COPY . /app/ 18 | RUN npm install --unsafe-perm || \ 19 | ((if [ -f npm-debug.log ]; then \ 20 | cat npm-debug.log; \ 21 | fi) && false) 22 | CMD npm start -------------------------------------------------------------------------------- /batch-job/job/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nconf = module.exports = require('nconf'); 4 | const path = require('path'); 5 | 6 | nconf 7 | // 1. command-line arguments 8 | .argv() 9 | // 2. env variables 10 | .env([ 11 | 'GCLOUD_PROJECT', 12 | 'FOREIGN_SERVICE', 13 | 'MESSAGE' 14 | ]) 15 | // 3. config file 16 | .file({ file: path.join(__dirname, 'config.json') }) 17 | // 4. defaults 18 | .defaults({ 19 | GCLOUD_PROJECT: '', 20 | FOREIGN_SERVICE: '', 21 | MESSAGE: 'Configure My Message' 22 | }); 23 | 24 | // check required settings 25 | checkConfig('GCLOUD_PROJECT'); 26 | checkConfig('FOREIGN_SERVICE'); 27 | 28 | function checkConfig(setting) { 29 | if (!nconf.get(setting)) { 30 | throw new Error(`You must set ${setting} as an environment variable or in config.json!`); 31 | } 32 | } -------------------------------------------------------------------------------- /batch-job/job/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "job", 3 | "version": "0.0.1", 4 | "description": "kubernetes blog series - batch job", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jonbcampos/kubernetes-series" 9 | }, 10 | "author": { 11 | "name": "Jonathan Campos", 12 | "url": "https://github.com/jonbcampos" 13 | }, 14 | "contributors": [], 15 | "homepage": "http://jonbcampos.com/", 16 | "bugs": { 17 | "url": "https://github.com/jonbcampos/kubernetes-series/issues" 18 | }, 19 | "main": "app.js", 20 | "bin": "bin", 21 | "engines": { 22 | "node": ">=4.3.2" 23 | }, 24 | "scripts": { 25 | "start": "node ./app.js", 26 | "debug": "node $NODE_DEBUG_OPTION ./app.js", 27 | "auth": "gcloud auth application-default login" 28 | }, 29 | "dependencies": { 30 | "body-parser": "^1.18.3", 31 | "cookie-parser": "~1.4.3", 32 | "debug": "~2.6.9", 33 | "ejs": "~2.5.7", 34 | "express": "~4.16.0", 35 | "http-errors": "~1.6.2", 36 | "morgan": "~1.9.0", 37 | "nconf": "^0.10.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /batch-job/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta1 2 | kind: Deployment # it is a deployment 3 | metadata: 4 | name: endpoints # name of the Deployment 5 | labels: 6 | # any Pods with matching labels are included in this Deployment 7 | app: kubernetes-series 8 | tier: endpoints 9 | spec: 10 | # ReplicaSet specification 11 | replicas: 1 # we are making 1 Pod 12 | selector: 13 | matchLabels: 14 | # ReplicaSet labels will match Pods with the following labels 15 | tier: endpoints 16 | template: 17 | # Pod template 18 | metadata: 19 | labels: 20 | # Pod's labels 21 | app: kubernetes-series 22 | tier: endpoints 23 | spec: 24 | # Pod specification 25 | containers: 26 | # the container(s) in this Pod 27 | - name: batchjob-container 28 | image: gcr.io/PROJECT_NAME/batchjob-container:latest 29 | # the readiness probe details 30 | readinessProbe: 31 | httpGet: # make an HTTP request 32 | port: 8080 # port to use 33 | path: /readiness # endpoint to hit 34 | scheme: HTTP # or HTTPS 35 | initialDelaySeconds: 3 # how long to wait before checking 36 | periodSeconds: 3 # how long to wait between checks 37 | successThreshold: 1 # how many successes to hit before accepting 38 | failureThreshold: 1 # how many failures to accept before failing 39 | timeoutSeconds: 1 # how long to wait for a response 40 | # the livenessProbe probe details 41 | livenessProbe: 42 | httpGet: # make an HTTP request 43 | port: 8080 # port to use 44 | path: /healthcheck # endpoint to hit 45 | scheme: HTTP # or HTTPS 46 | initialDelaySeconds: 3 # how long to wait before checking 47 | periodSeconds: 3 # how long to wait between checks 48 | successThreshold: 1 # how many successes to hit before accepting 49 | failureThreshold: 1 # how many failures to accept before failing 50 | timeoutSeconds: 1 # how long to wait for a response 51 | # environment variables for the Pod 52 | env: 53 | - name: GCLOUD_PROJECT 54 | value: PROJECT_NAME 55 | - name: NODE_ENV 56 | value: production 57 | ports: 58 | - containerPort: 80 -------------------------------------------------------------------------------- /batch-job/k8s/parallel-jobs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: parallel-job 5 | spec: 6 | completions: 6 # number of times to run 7 | parallelism: 2 # number of pods that can run in parallel 8 | backoffLimit: 6 # number of retries before throwing error 9 | activeDeadlineSeconds: 10 # time to allow job to run 10 | template: 11 | metadata: 12 | labels: 13 | app: kubernetes-series 14 | tier: job 15 | spec: 16 | restartPolicy: OnFailure 17 | containers: 18 | - name: job 19 | image: gcr.io/PROJECT_NAME/batchjob-container-job:latest 20 | # environment variables for the Pod 21 | env: 22 | - name: GCLOUD_PROJECT 23 | value: PROJECT_NAME 24 | - name: MESSAGE 25 | value: I am a parallel run job 26 | - name: FOREIGN_SERVICE 27 | value: http://endpoints.default.svc.cluster.local/parallel 28 | - name: NODE_ENV 29 | value: production 30 | ports: 31 | - containerPort: 80 -------------------------------------------------------------------------------- /batch-job/k8s/sequential-jobs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: sequential-job 5 | spec: 6 | completions: 3 # number of times to run 7 | backoffLimit: 6 # number of retries before throwing error 8 | activeDeadlineSeconds: 10 # time to allow job to run 9 | template: 10 | metadata: 11 | labels: 12 | app: kubernetes-series 13 | tier: job 14 | spec: 15 | restartPolicy: OnFailure 16 | containers: 17 | - name: job 18 | image: gcr.io/PROJECT_NAME/batchjob-container-job:latest 19 | # environment variables for the Pod 20 | env: 21 | - name: GCLOUD_PROJECT 22 | value: PROJECT_NAME 23 | - name: MESSAGE 24 | value: I am a sequential run job 25 | - name: FOREIGN_SERVICE 26 | value: http://endpoints.default.svc.cluster.local/sequential 27 | - name: NODE_ENV 28 | value: production 29 | ports: 30 | - containerPort: 80 -------------------------------------------------------------------------------- /batch-job/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service # a way for the outside world to reach the Pods 3 | metadata: 4 | # any Pods with matching labels are included in this Service 5 | name: endpoints 6 | spec: 7 | # Service ports 8 | ports: 9 | - name: http 10 | port: 80 11 | targetPort: 8080 12 | protocol: TCP 13 | # It includes a LoadBalancer between Pods 14 | type: LoadBalancer 15 | selector: 16 | app: kubernetes-series -------------------------------------------------------------------------------- /batch-job/k8s/single-job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: single-job 5 | spec: 6 | backoffLimit: 6 # number of retries before throwing error 7 | activeDeadlineSeconds: 10 # time to allow job to run 8 | template: 9 | metadata: 10 | labels: 11 | app: kubernetes-series 12 | tier: job 13 | spec: 14 | restartPolicy: OnFailure 15 | containers: 16 | - name: job 17 | image: gcr.io/PROJECT_NAME/batchjob-container-job:latest 18 | # environment variables for the Pod 19 | env: 20 | - name: GCLOUD_PROJECT 21 | value: PROJECT_NAME 22 | - name: MESSAGE 23 | value: I am a single run job 24 | - name: FOREIGN_SERVICE 25 | value: http://endpoints.default.svc.cluster.local/single 26 | - name: NODE_ENV 27 | value: production 28 | ports: 29 | - containerPort: 80 -------------------------------------------------------------------------------- /batch-job/scripts/check-endpoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Pass the name of a service to check ie: sh check-endpoint.sh endpoints 4 | # Will run forever... 5 | external_ip="" 6 | while [ -z $external_ip ]; do 7 | echo "Waiting for end point..." 8 | external_ip=$(kubectl get svc $1 --template="{{range .status.loadBalancer.ingress}}{{.ip}}{{end}}") 9 | [ -z "$external_ip" ] && sleep 10 10 | done 11 | echo 'End point ready:' && echo $external_ip -------------------------------------------------------------------------------- /batch-job/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=batchjob 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "name replace" 15 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/deployment.yaml 16 | 17 | echo "create pod-replicaset" 18 | kubectl apply -f ../k8s/deployment.yaml 19 | kubectl apply -f ../k8s/service.yaml -------------------------------------------------------------------------------- /batch-job/scripts/run-parallel-job.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=batchjob 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "name replace" 15 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/parallel-jobs.yaml 16 | 17 | echo "create parallel job" 18 | kubectl apply -f ../k8s/parallel-jobs.yaml -------------------------------------------------------------------------------- /batch-job/scripts/run-sequential-job.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=batchjob 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "name replace" 15 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/sequential-jobs.yaml 16 | 17 | echo "create sequential job" 18 | kubectl apply -f ../k8s/sequential-jobs.yaml -------------------------------------------------------------------------------- /batch-job/scripts/run-single-job.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=batchjob 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "name replace" 15 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/single-job.yaml 16 | 17 | echo "create single job" 18 | kubectl apply -f ../k8s/single-job.yaml -------------------------------------------------------------------------------- /batch-job/scripts/startup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=batchjob 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "enable services" 15 | gcloud services enable compute.googleapis.com 16 | gcloud services enable container.googleapis.com 17 | 18 | echo "creating container engine cluster" 19 | gcloud container clusters create ${CLUSTER_NAME} \ 20 | --preemptible \ 21 | --zone ${INSTANCE_ZONE} \ 22 | --scopes cloud-platform \ 23 | --num-nodes 3 24 | 25 | echo "confirm cluster is running" 26 | gcloud container clusters list 27 | 28 | echo "get credentials" 29 | gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${INSTANCE_ZONE} 30 | 31 | echo "confirm connection to cluster" 32 | kubectl cluster-info 33 | 34 | echo "create cluster administrator" 35 | kubectl create clusterrolebinding cluster-admin-binding \ 36 | --clusterrole=cluster-admin \ 37 | --user=$(gcloud config get-value account) 38 | 39 | echo "confirm the pod is running" 40 | kubectl get pods 41 | 42 | echo "list production services" 43 | kubectl get svc 44 | 45 | echo "enable services" 46 | gcloud services enable cloudbuild.googleapis.com 47 | 48 | echo "building containers" 49 | gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} ../endpoint 50 | gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME}-job ../job -------------------------------------------------------------------------------- /batch-job/scripts/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=batchjob 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "remove cluster" 15 | gcloud container clusters delete ${CLUSTER_NAME} --quiet 16 | gcloud container clusters list 17 | 18 | echo "remove container" 19 | gcloud container images delete gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} --force-delete-tags --quiet 20 | gcloud container images delete gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME}-job --force-delete-tags --quiet -------------------------------------------------------------------------------- /communication/.dockerignore: -------------------------------------------------------------------------------- 1 | service-1/node_modules 2 | .dockerignore 3 | Dockerfile 4 | npm-debug.log 5 | .git 6 | .hg 7 | .svn 8 | test 9 | k8s 10 | .nyc_output 11 | coverage -------------------------------------------------------------------------------- /communication/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store -------------------------------------------------------------------------------- /communication/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jonathan Campos 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM gcr.io/google_appengine/nodejs 15 | MAINTAINER Jonathan Campos 16 | RUN /usr/local/bin/install_node '>=0.12.7' 17 | COPY . /app/ 18 | RUN npm install --unsafe-perm || \ 19 | ((if [ -f npm-debug.log ]; then \ 20 | cat npm-debug.log; \ 21 | fi) && false) 22 | CMD npm start -------------------------------------------------------------------------------- /communication/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes - Communication 2 | This is the communication segment in a multipart series about Kubernetes and some of the finer points 3 | I've learned along the way when using [Kubernetes](https://kubernetes.io/) with 4 | [Google Cloud Platform](https://cloud.google.com/). 5 | 6 | You can read the accompanying post for this folder here: 7 | [Kubernetes - Routing Internal Services Through FQDN](https://medium.com/google-cloud/kubernetes-routing-internal-services-through-fqdn-d98db92b79d3). 8 | 9 | To run this code here are the quick steps. 10 | 11 | ### To startup and deploy: 12 | ```bash 13 | git clone https://github.com/jonbcampos/kubernetes-series.git 14 | cd ~/kubernetes-series/communication/scripts 15 | sh startup.sh 16 | sh deploy.sh 17 | sh check-endpoint.sh 18 | ``` 19 | 20 | ### To Teardown: 21 | ```bash 22 | cd ~/kubernetes-series/communication/scripts # if necessary 23 | sh teardown.sh 24 | ``` -------------------------------------------------------------------------------- /communication/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | const app = require('../app'); 7 | const debug = require('debug')('partone:server'); 8 | const http = require('http'); 9 | 10 | /** 11 | * Get port from environment and store in Express. 12 | */ 13 | const port = normalizePort(process.env.PORT || '3000'); 14 | app.set('port', port); 15 | 16 | /** 17 | * Create HTTP server. 18 | */ 19 | const server = http.createServer(app); 20 | 21 | /** 22 | * Listen on provided port, on all network interfaces. 23 | */ 24 | server.listen(port); 25 | server.on('error', onError); 26 | server.on('listening', onListening); 27 | 28 | /** 29 | * Normalize a port into a number, string, or false. 30 | */ 31 | function normalizePort(val) { 32 | const port = parseInt(val, 10); 33 | 34 | if (isNaN(port)) { 35 | // named pipe 36 | return val; 37 | } 38 | 39 | if (port >= 0) { 40 | // port number 41 | return port; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | /** 48 | * Event listener for HTTP server "error" event. 49 | */ 50 | function onError(error) { 51 | if (error.syscall !== 'listen') { 52 | throw error; 53 | } 54 | 55 | const bind = typeof port === 'string' 56 | ? 'Pipe ' + port 57 | : 'Port ' + port; 58 | 59 | // handle specific listen errors with friendly messages 60 | switch (error.code) { 61 | case 'EACCES': 62 | console.error(bind + ' requires elevated privileges'); 63 | process.exit(1); 64 | break; 65 | case 'EADDRINUSE': 66 | console.error(bind + ' is already in use'); 67 | process.exit(1); 68 | break; 69 | default: 70 | throw error; 71 | } 72 | } 73 | 74 | /** 75 | * Event listener for HTTP server "listening" event. 76 | */ 77 | function onListening() { 78 | const addr = server.address(); 79 | const bind = typeof addr === 'string' 80 | ? 'pipe ' + addr 81 | : 'port ' + addr.port; 82 | debug('Listening on ' + bind); 83 | } 84 | -------------------------------------------------------------------------------- /communication/config.js: -------------------------------------------------------------------------------- 1 | const nconf = module.exports = require('nconf'); 2 | const path = require('path'); 3 | 4 | nconf 5 | // 1. command-line arguments 6 | .argv() 7 | // 2. env variables 8 | .env([ 9 | 'GCLOUD_PROJECT', 10 | 'POD_ENDPOINT', 11 | 'FOREIGN_PATH', 12 | 'FOREIGN_SERVICE' 13 | ]) 14 | // 3. config file 15 | .file({ file: path.join(__dirname, 'config.json') }) 16 | // 4. defaults 17 | .defaults({ 18 | GCLOUD_PROJECT: '', 19 | POD_ENDPOINT: '', 20 | FOREIGN_PATH: '', 21 | FOREIGN_SERVICE: '' 22 | }); 23 | 24 | // check required settings 25 | checkConfig('GCLOUD_PROJECT'); 26 | checkConfig('POD_ENDPOINT'); 27 | checkConfig('FOREIGN_SERVICE'); 28 | checkConfig('FOREIGN_PATH'); 29 | 30 | function checkConfig(setting) { 31 | if (!nconf.get(setting)) { 32 | throw new Error(`You must set ${setting} as an environment variable or in config.json!`); 33 | } 34 | } -------------------------------------------------------------------------------- /communication/k8s/service_1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service # a way for the outside world to reach the Pods 3 | metadata: 4 | # any Pods with matching labels are included in this Service 5 | name: service-1 6 | spec: 7 | # Service ports 8 | ports: 9 | - name: http 10 | port: 80 11 | targetPort: 8080 12 | protocol: TCP 13 | - name: https 14 | port: 443 15 | targetPort: 8443 16 | protocol: TCP 17 | # It includes a LoadBalancer between Pods 18 | type: LoadBalancer 19 | selector: 20 | app: kubernetes-series 21 | tier: service-1 -------------------------------------------------------------------------------- /communication/k8s/service_2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service # a way for the outside world to reach the Pods 3 | metadata: 4 | # any Pods with matching labels are included in this Service 5 | name: service-2 6 | spec: 7 | # Service ports 8 | ports: 9 | - name: http 10 | port: 80 11 | targetPort: 8080 12 | protocol: TCP 13 | - name: https 14 | port: 443 15 | targetPort: 8443 16 | protocol: TCP 17 | # It includes a LoadBalancer between Pods 18 | type: LoadBalancer 19 | selector: 20 | app: kubernetes-series 21 | tier: service-2 -------------------------------------------------------------------------------- /communication/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "communication-service-1", 3 | "version": "0.0.1", 4 | "description": "kubernetes blog series - communication", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jonbcampos/kubernetes-series" 9 | }, 10 | "author": { 11 | "name": "Jonathan Campos", 12 | "url": "https://github.com/jonbcampos" 13 | }, 14 | "contributors": [], 15 | "homepage": "http://jonbcampos.com/", 16 | "bugs": { 17 | "url": "https://github.com/jonbcampos/kubernetes-series/issues" 18 | }, 19 | "main": "app.js", 20 | "bin": "bin", 21 | "engines": { 22 | "node": ">=4.3.2" 23 | }, 24 | "scripts": { 25 | "start": "node ./bin/www", 26 | "debug": "node $NODE_DEBUG_OPTION ./app.js", 27 | "auth": "gcloud auth application-default login" 28 | }, 29 | "dependencies": { 30 | "body-parser": "^1.18.3", 31 | "cookie-parser": "~1.4.3", 32 | "debug": "~2.6.9", 33 | "ejs": "~2.5.7", 34 | "express": "~4.16.0", 35 | "express-healthcheck": "^0.1.0", 36 | "http-errors": "~1.6.2", 37 | "morgan": "~1.9.0", 38 | "nconf": "^0.10.0", 39 | "node-sass-middleware": "0.11.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /communication/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const config = require('./config'); 4 | const os = require('os'); 5 | const http = require('http'); 6 | 7 | const router = express.Router(); 8 | 9 | //------------------------------------- 10 | // 11 | // Automatically parse request body as JSON 12 | // 13 | //------------------------------------- 14 | router.use(bodyParser.json()); 15 | 16 | //------------------------------------- 17 | // 18 | // Set Content-Type for all responses for these routes 19 | // 20 | //------------------------------------- 21 | router.use((req, res, next) => { 22 | res.set('Content-Type', 'application/json'); 23 | next(); 24 | }); 25 | 26 | //------------------------------------- 27 | // 28 | // Routes 29 | // 30 | //------------------------------------- 31 | router.get('/', function (req, res, next) { 32 | res.status(200).json({ hello: 'world' }); 33 | }); 34 | 35 | router.get('/api', function (req, res, next) { 36 | const remoteAddress = req.connection.remoteAddress; 37 | const hostName = os.hostname(); 38 | res.status(200).json({ remoteAddress, hostName }); 39 | }); 40 | 41 | router.get(config.get('POD_ENDPOINT'), function (req, res, next) { 42 | const remoteAddress = req.connection.remoteAddress; 43 | const hostName = os.hostname(); 44 | const requestHost = req.headers.host; 45 | res.status(200).json({ remoteAddress, hostName, requestHost }); 46 | }); 47 | 48 | router.get('/foreign', function (req, res, next) { 49 | const url = config.get('FOREIGN_SERVICE') + config.get('FOREIGN_PATH'); 50 | http.get(url, response => { 51 | let data = ''; 52 | response.on('data', chunk => { 53 | data += chunk; 54 | }); 55 | response.on('end', () => { 56 | res.status(200).json(JSON.parse(data)); 57 | }); 58 | }).on('error', err => { 59 | throw err; 60 | }); 61 | }); 62 | 63 | module.exports = router; -------------------------------------------------------------------------------- /communication/scripts/check-endpoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Pass the name of a service to check ie: sh check-endpoint.sh endpoints 4 | # Will run forever... 5 | external_ip="" 6 | while [ -z $external_ip ]; do 7 | echo "Waiting for end point..." 8 | external_ip=$(kubectl get svc $1 --template="{{range .status.loadBalancer.ingress}}{{.ip}}{{end}}") 9 | [ -z "$external_ip" ] && sleep 10 10 | done 11 | echo 'End point ready:' && echo $external_ip -------------------------------------------------------------------------------- /communication/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=communication 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "name replace" 15 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/deployment_1.yaml 16 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/deployment_2.yaml 17 | 18 | echo "create pod-replicaset" 19 | kubectl apply -f ../k8s/deployment_1.yaml 20 | kubectl apply -f ../k8s/deployment_2.yaml 21 | kubectl apply -f ../k8s/service_1.yaml 22 | kubectl apply -f ../k8s/service_2.yaml -------------------------------------------------------------------------------- /communication/scripts/startup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=communication 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "enable services" 15 | gcloud services enable compute.googleapis.com 16 | gcloud services enable container.googleapis.com 17 | 18 | echo "creating container engine cluster" 19 | gcloud container clusters create ${CLUSTER_NAME} \ 20 | --preemptible \ 21 | --zone ${INSTANCE_ZONE} \ 22 | --scopes cloud-platform \ 23 | --num-nodes 3 24 | 25 | echo "confirm cluster is running" 26 | gcloud container clusters list 27 | 28 | echo "get credentials" 29 | gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${INSTANCE_ZONE} 30 | 31 | echo "confirm connection to cluster" 32 | kubectl cluster-info 33 | 34 | echo "create cluster administrator" 35 | kubectl create clusterrolebinding cluster-admin-binding \ 36 | --clusterrole=cluster-admin \ 37 | --user=$(gcloud config get-value account) 38 | 39 | echo "confirm the pod is running" 40 | kubectl get pods 41 | 42 | echo "list production services" 43 | kubectl get svc 44 | 45 | echo "enable services" 46 | gcloud services enable cloudbuild.googleapis.com 47 | 48 | echo "building containers" 49 | gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} ../ -------------------------------------------------------------------------------- /communication/scripts/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=communication 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "remove cluster" 15 | gcloud container clusters delete ${CLUSTER_NAME} --quiet 16 | gcloud container clusters list 17 | 18 | echo "remove container" 19 | gcloud container images delete gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} --force-delete-tags --quiet -------------------------------------------------------------------------------- /cron/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store -------------------------------------------------------------------------------- /cron/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes - Cron 2 | This is the cron segment in a multipart series about Kubernetes and some of the finer points 3 | I've learned along the way when using [Kubernetes](https://kubernetes.io/) with 4 | [Google Cloud Platform](https://cloud.google.com/). 5 | 6 | You can read the accompanying post for this folder here: 7 | [Kubernetes - Cron Jobs](https://medium.com/google-cloud/kubernetes-cron-jobs-455fdc32e81a). 8 | 9 | To run this code here are the quick steps. 10 | 11 | ### To startup and deploy: 12 | ```bash 13 | git clone https://github.com/jonbcampos/kubernetes-series.git 14 | cd ~/kubernetes-series/cron/scripts 15 | sh startup.sh 16 | sh deploy.sh 17 | sh check-endpoint.sh 18 | sh create_cronjob.sh 19 | ``` 20 | 21 | ### To Teardown: 22 | ```bash 23 | cd kubernetes-series/cron/scripts # if necessary 24 | sh teardown.sh 25 | ``` -------------------------------------------------------------------------------- /cron/cron-container/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .dockerignore 3 | Dockerfile 4 | npm-debug.log 5 | .git 6 | .hg 7 | .svn 8 | test 9 | k8s 10 | .nyc_output 11 | coverage -------------------------------------------------------------------------------- /cron/cron-container/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store -------------------------------------------------------------------------------- /cron/cron-container/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jonathan Campos 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM gcr.io/google_appengine/nodejs 15 | MAINTAINER Jonathan Campos 16 | RUN /usr/local/bin/install_node '>=0.12.7' 17 | COPY . /app/ 18 | RUN npm install --unsafe-perm || \ 19 | ((if [ -f npm-debug.log ]; then \ 20 | cat npm-debug.log; \ 21 | fi) && false) 22 | CMD npm start -------------------------------------------------------------------------------- /cron/cron-container/app.js: -------------------------------------------------------------------------------- 1 | const createError = require('http-errors'); 2 | const express = require('express'); 3 | const logger = require('morgan'); 4 | const config = require('./config'); 5 | const http = require('http'); 6 | 7 | //------------------------------------- 8 | // 9 | // setup the app 10 | // 11 | //------------------------------------- 12 | const app = express(); 13 | app.use(logger('dev')); 14 | app.set('json spaces', 2); 15 | app.set('trust proxy', true); 16 | app.set('case sensitive routing', true); 17 | app.use(express.json()); 18 | app.use(express.urlencoded({ extended: false })); 19 | 20 | //------------------------------------- 21 | // 22 | // setup some routes for us to hit and 23 | // other helpful routes when working with kubernetes 24 | // 25 | //------------------------------------- 26 | process.on('SIGTERM', function () { 27 | // cleanup environment, this is about to be shut down 28 | process.exit(0); 29 | }); 30 | 31 | //------------------------------------- 32 | // 33 | // catch 404 and forward to error handler 34 | // 35 | //------------------------------------- 36 | app.use(function (req, res, next) { 37 | next(createError(404)); 38 | }); 39 | 40 | //------------------------------------- 41 | // 42 | // error handler 43 | // 44 | //------------------------------------- 45 | app.use(function (err, req, res, next) { 46 | // set locals, only providing error in development 47 | err.response = { 48 | message: err.message, 49 | internalCode: err.code 50 | }; 51 | // render the error page 52 | res.status(err.status || 500).json(err.response); 53 | }); 54 | 55 | //------------------------------------- 56 | // 57 | // create server if this is main 58 | // 59 | //------------------------------------- 60 | if (module === require.main) { 61 | // Start the server 62 | const server = app.listen(config.get('PORT'), () => { 63 | const endpoint = config.get('FOREIGN_SERVICE'); 64 | const port = server.address().port; 65 | console.log(`Calling endpoint ${endpoint}`); 66 | http.get(endpoint, response => { 67 | let data = ''; 68 | response.on('data', chunk => { 69 | data += chunk; 70 | }); 71 | response.on('end', () => { 72 | console.log(`Called endpoint, got response ${data}`); 73 | process.exit(0); 74 | }); 75 | }).on('error', err => { 76 | throw err; 77 | }); 78 | }); 79 | } 80 | 81 | module.exports = app; 82 | -------------------------------------------------------------------------------- /cron/cron-container/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nconf = module.exports = require('nconf'); 4 | const path = require('path'); 5 | 6 | nconf 7 | // 1. command-line arguments 8 | .argv() 9 | // 2. env variables 10 | .env([ 11 | 'GCLOUD_PROJECT', 12 | 'FOREIGN_SERVICE' 13 | ]) 14 | // 3. config file 15 | .file({ file: path.join(__dirname, 'config.json') }) 16 | // 4. defaults 17 | .defaults({ 18 | GCLOUD_PROJECT: '', 19 | FOREIGN_SERVICE: '' 20 | }); 21 | 22 | // check required settings 23 | checkConfig('GCLOUD_PROJECT'); 24 | checkConfig('FOREIGN_SERVICE'); 25 | 26 | function checkConfig(setting) { 27 | if (!nconf.get(setting)) { 28 | throw new Error(`You must set ${setting} as an environment variable or in config.json!`); 29 | } 30 | } -------------------------------------------------------------------------------- /cron/cron-container/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cronjob", 3 | "version": "0.0.1", 4 | "description": "kubernetes blog series - cronjob", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jonbcampos/kubernetes-series" 9 | }, 10 | "author": { 11 | "name": "Jonathan Campos", 12 | "url": "https://github.com/jonbcampos" 13 | }, 14 | "contributors": [], 15 | "homepage": "http://jonbcampos.com/", 16 | "bugs": { 17 | "url": "https://github.com/jonbcampos/kubernetes-series/issues" 18 | }, 19 | "main": "app.js", 20 | "bin": "bin", 21 | "engines": { 22 | "node": ">=4.3.2" 23 | }, 24 | "scripts": { 25 | "start": "node ./app.js", 26 | "debug": "node $NODE_DEBUG_OPTION ./app.js", 27 | "auth": "gcloud auth application-default login" 28 | }, 29 | "dependencies": { 30 | "body-parser": "^1.18.3", 31 | "cookie-parser": "~1.4.3", 32 | "debug": "~2.6.9", 33 | "ejs": "~2.5.7", 34 | "express": "~4.16.0", 35 | "http-errors": "~1.6.2", 36 | "morgan": "~1.9.0", 37 | "nconf": "^0.10.0", 38 | "node-sass-middleware": "0.11.0", 39 | "request-promise": "^4.2.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cron/endpoint/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .dockerignore 3 | Dockerfile 4 | npm-debug.log 5 | .git 6 | .hg 7 | .svn 8 | test 9 | k8s 10 | .nyc_output 11 | coverage -------------------------------------------------------------------------------- /cron/endpoint/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store -------------------------------------------------------------------------------- /cron/endpoint/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jonathan Campos 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM gcr.io/google_appengine/nodejs 15 | MAINTAINER Jonathan Campos 16 | RUN /usr/local/bin/install_node '>=0.12.7' 17 | COPY . /app/ 18 | RUN npm install --unsafe-perm || \ 19 | ((if [ -f npm-debug.log ]; then \ 20 | cat npm-debug.log; \ 21 | fi) && false) 22 | CMD npm start -------------------------------------------------------------------------------- /cron/endpoint/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | const app = require('../app'); 7 | const debug = require('debug')('cron:server'); 8 | const http = require('http'); 9 | 10 | /** 11 | * Get port from environment and store in Express. 12 | */ 13 | const port = normalizePort(process.env.PORT || '3000'); 14 | app.set('port', port); 15 | 16 | /** 17 | * Create HTTP server. 18 | */ 19 | const server = http.createServer(app); 20 | 21 | /** 22 | * Listen on provided port, on all network interfaces. 23 | */ 24 | server.listen(port); 25 | server.on('error', onError); 26 | server.on('listening', onListening); 27 | 28 | /** 29 | * Normalize a port into a number, string, or false. 30 | */ 31 | function normalizePort(val) { 32 | const port = parseInt(val, 10); 33 | 34 | if (isNaN(port)) { 35 | // named pipe 36 | return val; 37 | } 38 | 39 | if (port >= 0) { 40 | // port number 41 | return port; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | /** 48 | * Event listener for HTTP server "error" event. 49 | */ 50 | function onError(error) { 51 | if (error.syscall !== 'listen') { 52 | throw error; 53 | } 54 | 55 | const bind = typeof port === 'string' 56 | ? 'Pipe ' + port 57 | : 'Port ' + port; 58 | 59 | // handle specific listen errors with friendly messages 60 | switch (error.code) { 61 | case 'EACCES': 62 | console.error(bind + ' requires elevated privileges'); 63 | process.exit(1); 64 | break; 65 | case 'EADDRINUSE': 66 | console.error(bind + ' is already in use'); 67 | process.exit(1); 68 | break; 69 | default: 70 | throw error; 71 | } 72 | } 73 | 74 | /** 75 | * Event listener for HTTP server "listening" event. 76 | */ 77 | function onListening() { 78 | const addr = server.address(); 79 | const bind = typeof addr === 'string' 80 | ? 'pipe ' + addr 81 | : 'port ' + addr.port; 82 | debug('Listening on ' + bind); 83 | } 84 | -------------------------------------------------------------------------------- /cron/endpoint/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nconf = module.exports = require('nconf'); 4 | const path = require('path'); 5 | 6 | nconf 7 | // 1. command-line arguments 8 | .argv() 9 | // 2. env variables 10 | .env([ 11 | 'GCLOUD_PROJECT', 12 | 'POD_ENDPOINT' 13 | ]) 14 | // 3. config file 15 | .file({ file: path.join(__dirname, 'config.json') }) 16 | // 4. defaults 17 | .defaults({ 18 | GCLOUD_PROJECT: '', 19 | POD_ENDPOINT: '', 20 | }); 21 | 22 | // check required settings 23 | checkConfig('GCLOUD_PROJECT'); 24 | checkConfig('POD_ENDPOINT'); 25 | 26 | function checkConfig(setting) { 27 | if (!nconf.get(setting)) { 28 | throw new Error(`You must set ${setting} as an environment variable or in config.json!`); 29 | } 30 | } -------------------------------------------------------------------------------- /cron/endpoint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cron", 3 | "version": "0.0.1", 4 | "description": "kubernetes blog series - cronjob", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jonbcampos/kubernetes-series" 9 | }, 10 | "author": { 11 | "name": "Jonathan Campos", 12 | "url": "https://github.com/jonbcampos" 13 | }, 14 | "contributors": [], 15 | "homepage": "http://jonbcampos.com/", 16 | "bugs": { 17 | "url": "https://github.com/jonbcampos/kubernetes-series/issues" 18 | }, 19 | "main": "app.js", 20 | "bin": "bin", 21 | "engines": { 22 | "node": ">=4.3.2" 23 | }, 24 | "scripts": { 25 | "start": "node ./bin/www", 26 | "debug": "node $NODE_DEBUG_OPTION ./app.js", 27 | "auth": "gcloud auth application-default login" 28 | }, 29 | "dependencies": { 30 | "body-parser": "^1.18.3", 31 | "cookie-parser": "~1.4.3", 32 | "debug": "~2.6.9", 33 | "ejs": "~2.5.7", 34 | "express": "~4.16.0", 35 | "express-healthcheck": "^0.1.0", 36 | "http-errors": "~1.6.2", 37 | "morgan": "~1.9.0", 38 | "nconf": "^0.10.0", 39 | "node-sass-middleware": "0.11.0", 40 | "request-promise": "^4.2.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cron/endpoint/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const config = require('./config'); 4 | const os = require('os'); 5 | 6 | const router = express.Router(); 7 | let callCount = 0; 8 | let lastCalledAt = null; 9 | //------------------------------------- 10 | // 11 | // Automatically parse request body as JSON 12 | // 13 | //------------------------------------- 14 | router.use(bodyParser.json()); 15 | 16 | //------------------------------------- 17 | // 18 | // Set Content-Type for all responses for these routes 19 | // 20 | //------------------------------------- 21 | router.use((req, res, next) => { 22 | res.set('Content-Type', 'application/json'); 23 | next(); 24 | }); 25 | 26 | //------------------------------------- 27 | // 28 | // Routes 29 | // 30 | //------------------------------------- 31 | router.get('/', function (req, res, next) { 32 | res.status(200).json({ hello: 'world' }); 33 | }); 34 | 35 | router.get('/api', function (req, res, next) { 36 | const remoteAddress = req.connection.remoteAddress; 37 | const hostName = os.hostname(); 38 | res.status(200).json({ remoteAddress, hostName }); 39 | }); 40 | 41 | router.get(`/${config.get('POD_ENDPOINT')}`, function (req, res, next) { 42 | const remoteAddress = req.connection.remoteAddress; 43 | const hostName = os.hostname(); 44 | const requestHost = req.headers.host; 45 | callCount += 1; 46 | lastCalledAt = new Date(); 47 | res.status(200).json({ remoteAddress, hostName, requestHost, callCount, lastCalledAt }); 48 | }); 49 | 50 | router.get('/data', function (req, res, next) { 51 | const remoteAddress = req.connection.remoteAddress; 52 | const hostName = os.hostname(); 53 | const requestHost = req.headers.host; 54 | res.status(200).json({ remoteAddress, hostName, requestHost, callCount, lastCalledAt }); 55 | }); 56 | 57 | module.exports = router; -------------------------------------------------------------------------------- /cron/k8s/cronjob.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1beta1 2 | kind: CronJob # it is a Cron Job 3 | metadata: 4 | name: endpoints-cronjob # name of the CronJob 5 | spec: 6 | schedule: "* * * * *" # run every minute 7 | startingDeadlineSeconds: 10 # if a job hasn't starting in this many seconds, skip 8 | concurrencyPolicy: Allow # either allow|forbid|replace 9 | successfulJobsHistoryLimit: 3 # how many completed jobs should be kept 10 | failedJobsHistoryLimit: 1 # how many failed jobs should be kept 11 | jobTemplate: 12 | spec: 13 | template: 14 | spec: 15 | restartPolicy: Never 16 | containers: 17 | - name: cron-container-cronjob 18 | image: gcr.io/PROJECT_NAME/cron-container-cronjob:latest 19 | # environment variables for the Pod 20 | env: 21 | - name: GCLOUD_PROJECT 22 | value: PROJECT_NAME 23 | # we are going to use this later 24 | # for now it creates a custom endpoint 25 | # for this pod 26 | - name: FOREIGN_SERVICE 27 | value: http://endpoints.default.svc.cluster.local/endpoint 28 | - name: NODE_ENV 29 | value: production 30 | ports: 31 | - containerPort: 80 -------------------------------------------------------------------------------- /cron/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service # a way for the outside world to reach the Pods 3 | metadata: 4 | # any Pods with matching labels are included in this Service 5 | name: endpoints 6 | spec: 7 | # Service ports 8 | ports: 9 | - name: http 10 | port: 80 11 | targetPort: 8080 12 | protocol: TCP 13 | # It includes a LoadBalancer between Pods 14 | type: LoadBalancer 15 | selector: 16 | app: kubernetes-series -------------------------------------------------------------------------------- /cron/scripts/check-endpoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Pass the name of a service to check ie: sh check-endpoint.sh endpoints 4 | # Will run forever... 5 | external_ip="" 6 | while [ -z $external_ip ]; do 7 | echo "Waiting for end point..." 8 | external_ip=$(kubectl get svc $1 --template="{{range .status.loadBalancer.ingress}}{{.ip}}{{end}}") 9 | [ -z "$external_ip" ] && sleep 10 10 | done 11 | echo 'End point ready:' && echo $external_ip -------------------------------------------------------------------------------- /cron/scripts/create_cronjob.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=cron 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "name replace" 15 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/cronjob.yaml 16 | 17 | echo "delete cronjob" 18 | kubectl create -f ../k8s/cronjob.yaml -------------------------------------------------------------------------------- /cron/scripts/delete_cronjob.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=cron 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "delete cronjob" 15 | kubectl delete cronjob endpoints-cronjob -------------------------------------------------------------------------------- /cron/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=cron 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "name replace" 15 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/deployment.yaml 16 | 17 | echo "create pod-replicaset" 18 | kubectl apply -f ../k8s/deployment.yaml 19 | kubectl apply -f ../k8s/service.yaml -------------------------------------------------------------------------------- /cron/scripts/startup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=cron 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "enable services" 15 | gcloud services enable compute.googleapis.com 16 | gcloud services enable container.googleapis.com 17 | 18 | echo "creating container engine cluster" 19 | gcloud container clusters create ${CLUSTER_NAME} --preemptible --zone ${INSTANCE_ZONE} --scopes cloud-platform --num-nodes 3 20 | 21 | echo "confirm cluster is running" 22 | gcloud container clusters list 23 | 24 | echo "get credentials" 25 | gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${INSTANCE_ZONE} 26 | 27 | echo "confirm connection to cluster" 28 | kubectl cluster-info 29 | 30 | echo "create cluster administrator" 31 | kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=$(gcloud config get-value account) 32 | 33 | echo "confirm the pod is running" 34 | kubectl get pods 35 | 36 | echo "list production services" 37 | kubectl get svc 38 | 39 | echo "enable services" 40 | gcloud services enable cloudbuild.googleapis.com 41 | 42 | echo "building containers" 43 | gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} ../endpoint 44 | gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME}-cronjob ../cron-container -------------------------------------------------------------------------------- /cron/scripts/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=cron 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "remove cluster" 15 | gcloud container clusters delete ${CLUSTER_NAME} --quiet 16 | gcloud container clusters list 17 | 18 | echo "remove container" 19 | gcloud container images delete gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} --force-delete-tags --quiet 20 | gcloud container images delete gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME}-cronjob --force-delete-tags --quiet -------------------------------------------------------------------------------- /daemon/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store -------------------------------------------------------------------------------- /daemon/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes - Daemon 2 | This is the daemon segment in a multipart series about Kubernetes and some of the finer points 3 | I've learned along the way when using [Kubernetes](https://kubernetes.io/) with 4 | [Google Cloud Platform](https://cloud.google.com/). 5 | 6 | You can read the accompanying post for this folder here: 7 | [Kubernetes - Run A Pod Per Node With Daemon Sets](https://medium.com/google-cloud/kubernetes-run-a-pod-per-node-with-daemon-sets-f77ce3f36bf1). 8 | 9 | To run this code here are the quick steps. 10 | 11 | ### To startup and deploy: 12 | ```bash 13 | git clone https://github.com/jonbcampos/kubernetes-series.git 14 | cd ~/kubernetes-series/daemon/scripts 15 | sh startup.sh 16 | sh deploy.sh 17 | sh check-endpoint.sh endpoints 18 | sh scale.sh 3 # to scale up 19 | ``` 20 | 21 | ### To Teardown: 22 | ```bash 23 | cd kubernetes-series/daemon/scripts # if necessary 24 | sh teardown.sh 25 | ``` -------------------------------------------------------------------------------- /daemon/daemon-container/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .dockerignore 3 | Dockerfile 4 | npm-debug.log 5 | .git 6 | .hg 7 | .svn 8 | test 9 | k8s 10 | .nyc_output 11 | coverage -------------------------------------------------------------------------------- /daemon/daemon-container/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store -------------------------------------------------------------------------------- /daemon/daemon-container/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jonathan Campos 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM gcr.io/google_appengine/nodejs 15 | MAINTAINER Jonathan Campos 16 | RUN /usr/local/bin/install_node '>=0.12.7' 17 | COPY . /app/ 18 | RUN npm install --unsafe-perm || \ 19 | ((if [ -f npm-debug.log ]; then \ 20 | cat npm-debug.log; \ 21 | fi) && false) 22 | CMD npm start -------------------------------------------------------------------------------- /daemon/daemon-container/app.js: -------------------------------------------------------------------------------- 1 | const createError = require('http-errors'); 2 | const express = require('express'); 3 | const logger = require('morgan'); 4 | const config = require('./config'); 5 | const healthCheck = require('express-healthcheck'); 6 | const pkg = require('./package'); 7 | 8 | //------------------------------------- 9 | // 10 | // setup the app 11 | // 12 | //------------------------------------- 13 | const app = express(); 14 | app.use(logger('dev')); 15 | app.set('json spaces', 2); 16 | app.set('trust proxy', true); 17 | app.set('case sensitive routing', true); 18 | app.use(express.json()); 19 | app.use(express.urlencoded({ extended: false })); 20 | 21 | //------------------------------------- 22 | // 23 | // setup some routes for us to hit and 24 | // other helpful routes when working with kubernetes 25 | // 26 | //------------------------------------- 27 | app.use('/', require('./routes')); 28 | app.use('/healthcheck', healthCheck()); 29 | app.use('/readiness', function (req, res, next) { 30 | res.status(200).json({ ready }); 31 | }); 32 | app.get('/version', function (req, res, next) { 33 | const version = pkg.version; 34 | res.status(200).json({ version }); 35 | }); 36 | process.on('SIGTERM', function () { 37 | // cleanup environment, this is about to be shut down 38 | process.exit(0); 39 | }); 40 | 41 | //------------------------------------- 42 | // 43 | // catch 404 and forward to error handler 44 | // 45 | //------------------------------------- 46 | app.use(function (req, res, next) { 47 | next(createError(404)); 48 | }); 49 | 50 | //------------------------------------- 51 | // 52 | // error handler 53 | // 54 | //------------------------------------- 55 | app.use(function (err, req, res, next) { 56 | // set locals, only providing error in development 57 | err.response = { 58 | message: err.message, 59 | internalCode: err.code 60 | }; 61 | // render the error page 62 | res.status(err.status || 500).json(err.response); 63 | }); 64 | 65 | //------------------------------------- 66 | // 67 | // create server if this is main 68 | // 69 | //------------------------------------- 70 | if (module === require.main) { 71 | // Start the server 72 | const server = app.listen(config.get('PORT'), () => { 73 | const port = server.address().port; 74 | console.log(`App listening on port ${port}`); 75 | }); 76 | } 77 | 78 | module.exports = app; 79 | -------------------------------------------------------------------------------- /daemon/daemon-container/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | const app = require('../app'); 7 | const debug = require('debug')('daemonset:server'); 8 | const http = require('http'); 9 | 10 | /** 11 | * Get port from environment and store in Express. 12 | */ 13 | const port = normalizePort(process.env.PORT || '3000'); 14 | app.set('port', port); 15 | 16 | /** 17 | * Create HTTP server. 18 | */ 19 | const server = http.createServer(app); 20 | 21 | /** 22 | * Listen on provided port, on all network interfaces. 23 | */ 24 | server.listen(port); 25 | server.on('error', onError); 26 | server.on('listening', onListening); 27 | 28 | /** 29 | * Normalize a port into a number, string, or false. 30 | */ 31 | function normalizePort(val) { 32 | const port = parseInt(val, 10); 33 | 34 | if (isNaN(port)) { 35 | // named pipe 36 | return val; 37 | } 38 | 39 | if (port >= 0) { 40 | // port number 41 | return port; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | /** 48 | * Event listener for HTTP server "error" event. 49 | */ 50 | function onError(error) { 51 | if (error.syscall !== 'listen') { 52 | throw error; 53 | } 54 | 55 | const bind = typeof port === 'string' 56 | ? 'Pipe ' + port 57 | : 'Port ' + port; 58 | 59 | // handle specific listen errors with friendly messages 60 | switch (error.code) { 61 | case 'EACCES': 62 | console.error(bind + ' requires elevated privileges'); 63 | process.exit(1); 64 | break; 65 | case 'EADDRINUSE': 66 | console.error(bind + ' is already in use'); 67 | process.exit(1); 68 | break; 69 | default: 70 | throw error; 71 | } 72 | } 73 | 74 | /** 75 | * Event listener for HTTP server "listening" event. 76 | */ 77 | function onListening() { 78 | const addr = server.address(); 79 | const bind = typeof addr === 'string' 80 | ? 'pipe ' + addr 81 | : 'port ' + addr.port; 82 | debug('Listening on ' + bind); 83 | } 84 | -------------------------------------------------------------------------------- /daemon/daemon-container/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nconf = module.exports = require('nconf'); 4 | const path = require('path'); 5 | 6 | nconf 7 | // 1. command-line arguments 8 | .argv() 9 | // 2. env variables 10 | .env([ 11 | 'GCLOUD_PROJECT', 12 | 'FOREIGN_SERVICE' 13 | ]) 14 | // 3. config file 15 | .file({ file: path.join(__dirname, 'config.json') }) 16 | // 4. defaults 17 | .defaults({ 18 | GCLOUD_PROJECT: '', 19 | FOREIGN_SERVICE: '' 20 | }); 21 | 22 | // check required settings 23 | checkConfig('GCLOUD_PROJECT'); 24 | 25 | function checkConfig(setting) { 26 | if (!nconf.get(setting)) { 27 | throw new Error(`You must set ${setting} as an environment variable or in config.json!`); 28 | } 29 | } -------------------------------------------------------------------------------- /daemon/daemon-container/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "daemon", 3 | "version": "0.0.1", 4 | "description": "kubernetes blog series - daemon", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jonbcampos/kubernetes-series" 9 | }, 10 | "author": { 11 | "name": "Jonathan Campos", 12 | "url": "https://github.com/jonbcampos" 13 | }, 14 | "contributors": [], 15 | "homepage": "http://jonbcampos.com/", 16 | "bugs": { 17 | "url": "https://github.com/jonbcampos/kubernetes-series/issues" 18 | }, 19 | "main": "app.js", 20 | "bin": "bin", 21 | "engines": { 22 | "node": ">=4.3.2" 23 | }, 24 | "scripts": { 25 | "start": "node ./bin/www", 26 | "debug": "node $NODE_DEBUG_OPTION ./app.js", 27 | "auth": "gcloud auth application-default login" 28 | }, 29 | "dependencies": { 30 | "body-parser": "^1.18.3", 31 | "cookie-parser": "~1.4.3", 32 | "debug": "~2.6.9", 33 | "ejs": "~2.5.7", 34 | "express": "~4.16.0", 35 | "express-healthcheck": "^0.1.0", 36 | "http-errors": "~1.6.2", 37 | "morgan": "~1.9.0", 38 | "nconf": "^0.10.0", 39 | "node-sass-middleware": "0.11.0", 40 | "request-promise": "^4.2.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /daemon/daemon-container/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const config = require('./config'); 4 | const os = require('os'); 5 | 6 | const router = express.Router(); 7 | //------------------------------------- 8 | // 9 | // Automatically parse request body as JSON 10 | // 11 | //------------------------------------- 12 | router.use(bodyParser.json()); 13 | 14 | //------------------------------------- 15 | // 16 | // Set Content-Type for all responses for these routes 17 | // 18 | //------------------------------------- 19 | router.use((req, res, next) => { 20 | res.set('Content-Type', 'application/json'); 21 | next(); 22 | }); 23 | 24 | //------------------------------------- 25 | // 26 | // Routes 27 | // 28 | //------------------------------------- 29 | router.get('/', function (req, res, next) { 30 | res.status(200).json({ hello: 'world' }); 31 | }); 32 | 33 | router.get('/api', function (req, res, next) { 34 | const remoteAddress = req.connection.remoteAddress; 35 | const hostName = os.hostname(); 36 | res.status(200).json({ remoteAddress, hostName }); 37 | }); 38 | 39 | module.exports = router; -------------------------------------------------------------------------------- /daemon/endpoint/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .dockerignore 3 | Dockerfile 4 | npm-debug.log 5 | .git 6 | .hg 7 | .svn 8 | test 9 | k8s 10 | .nyc_output 11 | coverage -------------------------------------------------------------------------------- /daemon/endpoint/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store -------------------------------------------------------------------------------- /daemon/endpoint/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jonathan Campos 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM gcr.io/google_appengine/nodejs 15 | MAINTAINER Jonathan Campos 16 | RUN /usr/local/bin/install_node '>=0.12.7' 17 | COPY . /app/ 18 | RUN npm install --unsafe-perm || \ 19 | ((if [ -f npm-debug.log ]; then \ 20 | cat npm-debug.log; \ 21 | fi) && false) 22 | CMD npm start -------------------------------------------------------------------------------- /daemon/endpoint/app.js: -------------------------------------------------------------------------------- 1 | const createError = require('http-errors'); 2 | const express = require('express'); 3 | const logger = require('morgan'); 4 | const config = require('./config'); 5 | const healthCheck = require('express-healthcheck'); 6 | const pkg = require('./package'); 7 | 8 | //------------------------------------- 9 | // 10 | // setup the app 11 | // 12 | //------------------------------------- 13 | const app = express(); 14 | app.use(logger('dev')); 15 | app.set('json spaces', 2); 16 | app.set('trust proxy', true); 17 | app.set('case sensitive routing', true); 18 | app.use(express.json()); 19 | app.use(express.urlencoded({ extended: false })); 20 | 21 | //------------------------------------- 22 | // 23 | // setup some routes for us to hit and 24 | // other helpful routes when working with kubernetes 25 | // 26 | //------------------------------------- 27 | app.use('/', require('./routes')); 28 | app.use('/unhealthy', function(req, res, next){ 29 | res.status(200).json({ healthy: true }); 30 | }); 31 | app.use('/healthcheck', healthCheck()); 32 | app.use('/readiness', function (req, res, next) { 33 | res.status(200).json({ ready: true }); 34 | }); 35 | app.get('/version', function (req, res, next) { 36 | const version = pkg.version; 37 | res.status(200).json({ version }); 38 | }); 39 | process.on('SIGTERM', function () { 40 | // cleanup environment, this is about to be shut down 41 | process.exit(0); 42 | }); 43 | 44 | //------------------------------------- 45 | // 46 | // catch 404 and forward to error handler 47 | // 48 | //------------------------------------- 49 | app.use(function (req, res, next) { 50 | next(createError(404)); 51 | }); 52 | 53 | //------------------------------------- 54 | // 55 | // error handler 56 | // 57 | //------------------------------------- 58 | app.use(function (err, req, res, next) { 59 | // set locals, only providing error in development 60 | err.response = { 61 | message: err.message, 62 | internalCode: err.code 63 | }; 64 | // render the error page 65 | res.status(err.status || 500).json(err.response); 66 | }); 67 | 68 | //------------------------------------- 69 | // 70 | // create server if this is main 71 | // 72 | //------------------------------------- 73 | if (module === require.main) { 74 | // Start the server 75 | const server = app.listen(config.get('PORT'), () => { 76 | const port = server.address().port; 77 | console.log(`App listening on port ${port}`); 78 | }); 79 | } 80 | 81 | module.exports = app; 82 | -------------------------------------------------------------------------------- /daemon/endpoint/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | const app = require('../app'); 7 | const debug = require('debug')('daemon:server'); 8 | const http = require('http'); 9 | 10 | /** 11 | * Get port from environment and store in Express. 12 | */ 13 | const port = normalizePort(process.env.PORT || '3000'); 14 | app.set('port', port); 15 | 16 | /** 17 | * Create HTTP server. 18 | */ 19 | const server = http.createServer(app); 20 | 21 | /** 22 | * Listen on provided port, on all network interfaces. 23 | */ 24 | server.listen(port); 25 | server.on('error', onError); 26 | server.on('listening', onListening); 27 | 28 | /** 29 | * Normalize a port into a number, string, or false. 30 | */ 31 | function normalizePort(val) { 32 | const port = parseInt(val, 10); 33 | 34 | if (isNaN(port)) { 35 | // named pipe 36 | return val; 37 | } 38 | 39 | if (port >= 0) { 40 | // port number 41 | return port; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | /** 48 | * Event listener for HTTP server "error" event. 49 | */ 50 | function onError(error) { 51 | if (error.syscall !== 'listen') { 52 | throw error; 53 | } 54 | 55 | const bind = typeof port === 'string' 56 | ? 'Pipe ' + port 57 | : 'Port ' + port; 58 | 59 | // handle specific listen errors with friendly messages 60 | switch (error.code) { 61 | case 'EACCES': 62 | console.error(bind + ' requires elevated privileges'); 63 | process.exit(1); 64 | break; 65 | case 'EADDRINUSE': 66 | console.error(bind + ' is already in use'); 67 | process.exit(1); 68 | break; 69 | default: 70 | throw error; 71 | } 72 | } 73 | 74 | /** 75 | * Event listener for HTTP server "listening" event. 76 | */ 77 | function onListening() { 78 | const addr = server.address(); 79 | const bind = typeof addr === 'string' 80 | ? 'pipe ' + addr 81 | : 'port ' + addr.port; 82 | debug('Listening on ' + bind); 83 | } 84 | -------------------------------------------------------------------------------- /daemon/endpoint/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nconf = module.exports = require('nconf'); 4 | const path = require('path'); 5 | 6 | nconf 7 | // 1. command-line arguments 8 | .argv() 9 | // 2. env variables 10 | .env([ 11 | 'GCLOUD_PROJECT', 12 | 'POD_ENDPOINT' 13 | ]) 14 | // 3. config file 15 | .file({ file: path.join(__dirname, 'config.json') }) 16 | // 4. defaults 17 | .defaults({ 18 | GCLOUD_PROJECT: '', 19 | POD_ENDPOINT: '', 20 | }); 21 | 22 | // check required settings 23 | checkConfig('GCLOUD_PROJECT'); 24 | checkConfig('POD_ENDPOINT'); 25 | 26 | function checkConfig(setting) { 27 | if (!nconf.get(setting)) { 28 | throw new Error(`You must set ${setting} as an environment variable or in config.json!`); 29 | } 30 | } -------------------------------------------------------------------------------- /daemon/endpoint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "endpoint", 3 | "version": "0.0.1", 4 | "description": "kubernetes blog series - daemon", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jonbcampos/kubernetes-series" 9 | }, 10 | "author": { 11 | "name": "Jonathan Campos", 12 | "url": "https://github.com/jonbcampos" 13 | }, 14 | "contributors": [], 15 | "homepage": "http://jonbcampos.com/", 16 | "bugs": { 17 | "url": "https://github.com/jonbcampos/kubernetes-series/issues" 18 | }, 19 | "main": "app.js", 20 | "bin": "bin", 21 | "engines": { 22 | "node": ">=4.3.2" 23 | }, 24 | "scripts": { 25 | "start": "node ./bin/www", 26 | "debug": "node $NODE_DEBUG_OPTION ./app.js", 27 | "auth": "gcloud auth application-default login" 28 | }, 29 | "dependencies": { 30 | "body-parser": "^1.18.3", 31 | "cookie-parser": "~1.4.3", 32 | "debug": "~2.6.9", 33 | "ejs": "~2.5.7", 34 | "express": "~4.16.0", 35 | "express-healthcheck": "^0.1.0", 36 | "http-errors": "~1.6.2", 37 | "morgan": "~1.9.0", 38 | "nconf": "^0.10.0", 39 | "node-sass-middleware": "0.11.0", 40 | "request-promise": "^4.2.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /daemon/endpoint/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const config = require('./config'); 4 | const os = require('os'); 5 | 6 | const router = express.Router(); 7 | let callCount = 0; 8 | let lastCalledAt = null; 9 | //------------------------------------- 10 | // 11 | // Automatically parse request body as JSON 12 | // 13 | //------------------------------------- 14 | router.use(bodyParser.json()); 15 | 16 | //------------------------------------- 17 | // 18 | // Set Content-Type for all responses for these routes 19 | // 20 | //------------------------------------- 21 | router.use((req, res, next) => { 22 | res.set('Content-Type', 'application/json'); 23 | next(); 24 | }); 25 | 26 | //------------------------------------- 27 | // 28 | // Routes 29 | // 30 | //------------------------------------- 31 | router.get('/', function (req, res, next) { 32 | res.status(200).json({ hello: 'world' }); 33 | }); 34 | 35 | router.get('/api', function (req, res, next) { 36 | const remoteAddress = req.connection.remoteAddress; 37 | const hostName = os.hostname(); 38 | res.status(200).json({ remoteAddress, hostName }); 39 | }); 40 | 41 | router.get(`/${config.get('POD_ENDPOINT')}`, function (req, res, next) { 42 | const remoteAddress = req.connection.remoteAddress; 43 | const hostName = os.hostname(); 44 | const requestHost = req.headers.host; 45 | callCount += 1; 46 | lastCalledAt = new Date(); 47 | res.status(200).json({ remoteAddress, hostName, requestHost, callCount, lastCalledAt }); 48 | }); 49 | 50 | router.get('/data', function (req, res, next) { 51 | const remoteAddress = req.connection.remoteAddress; 52 | const hostName = os.hostname(); 53 | const requestHost = req.headers.host; 54 | res.status(200).json({ remoteAddress, hostName, requestHost, callCount, lastCalledAt }); 55 | }); 56 | 57 | module.exports = router; -------------------------------------------------------------------------------- /daemon/k8s/daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet # it is a daemonset 3 | metadata: 4 | name: daemonset-pods # name of the daemon set 5 | labels: 6 | # any Pods with matching labels are included in this Daemonset 7 | app: kubernetes-series 8 | tier: monitor 9 | spec: 10 | selector: 11 | # Pods will match with the following labels 12 | matchLabels: 13 | name: daemonset-pods 14 | template: 15 | # Pod Template 16 | metadata: 17 | # Pod's labels 18 | labels: 19 | name: daemonset-pods 20 | spec: 21 | # the container(s) in this Pod 22 | containers: 23 | - name: daemon-container 24 | image: gcr.io/PROJECT_NAME/daemon-container-daemon:latest 25 | # environment variables for the Pod 26 | env: 27 | - name: GCLOUD_PROJECT 28 | value: PROJECT_NAME 29 | ports: 30 | - containerPort: 80 -------------------------------------------------------------------------------- /daemon/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service # a way for the outside world to reach the Pods 3 | metadata: 4 | # any Pods with matching labels are included in this Service 5 | name: endpoints 6 | spec: 7 | # Service ports 8 | ports: 9 | - name: http 10 | port: 80 11 | targetPort: 8080 12 | protocol: TCP 13 | # It includes a LoadBalancer between Pods 14 | type: LoadBalancer 15 | selector: 16 | app: kubernetes-series -------------------------------------------------------------------------------- /daemon/scripts/check-endpoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Pass the name of a service to check ie: sh check-endpoint.sh endpoints 4 | # Will run forever... 5 | external_ip="" 6 | while [ -z $external_ip ]; do 7 | echo "Waiting for end point..." 8 | external_ip=$(kubectl get svc $1 --template="{{range .status.loadBalancer.ingress}}{{.ip}}{{end}}") 9 | [ -z "$external_ip" ] && sleep 10 10 | done 11 | echo 'End point ready:' && echo $external_ip -------------------------------------------------------------------------------- /daemon/scripts/delete-scale.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=daemon 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "scale cluster" 15 | gcloud container node-pools delete my-pool \ 16 | --cluster=${CLUSTER_NAME} \ 17 | --quiet -------------------------------------------------------------------------------- /daemon/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=daemon 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "name replace" 15 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/deployment.yaml 16 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/daemonset.yaml 17 | 18 | echo "create pod-replicaset" 19 | kubectl apply -f ../k8s/deployment.yaml 20 | kubectl apply -f ../k8s/service.yaml 21 | kubectl apply -f ../k8s/daemonset.yaml -------------------------------------------------------------------------------- /daemon/scripts/scale.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=daemon 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "scale cluster" 15 | gcloud container node-pools delete my-pool \ 16 | --cluster=${CLUSTER_NAME} \ 17 | --quiet 18 | gcloud container node-pools create my-pool \ 19 | --cluster=${CLUSTER_NAME} \ 20 | --num-nodes=$1 -------------------------------------------------------------------------------- /daemon/scripts/startup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=daemon 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "enable services" 15 | gcloud services enable compute.googleapis.com 16 | gcloud services enable container.googleapis.com 17 | 18 | echo "creating container engine cluster" 19 | gcloud container clusters create ${CLUSTER_NAME} \ 20 | --preemptible \ 21 | --zone ${INSTANCE_ZONE} \ 22 | --scopes cloud-platform \ 23 | --enable-autoscaling \ 24 | --min-nodes 0 \ 25 | --max-nodes 100 \ 26 | --num-nodes 3 27 | 28 | echo "confirm cluster is running" 29 | gcloud container clusters list 30 | 31 | echo "get credentials" 32 | gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${INSTANCE_ZONE} 33 | 34 | echo "confirm connection to cluster" 35 | kubectl cluster-info 36 | 37 | echo "create cluster administrator" 38 | kubectl create clusterrolebinding cluster-admin-binding \ 39 | --clusterrole=cluster-admin \ 40 | --user=$(gcloud config get-value account) 41 | 42 | echo "confirm the pod is running" 43 | kubectl get pods 44 | 45 | echo "list production services" 46 | kubectl get svc 47 | 48 | echo "enable services" 49 | gcloud services enable cloudbuild.googleapis.com 50 | 51 | echo "building containers" 52 | gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} ../endpoint 53 | gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME}-daemon ../daemon-container -------------------------------------------------------------------------------- /daemon/scripts/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=daemon 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "remove cluster" 15 | gcloud container clusters delete ${CLUSTER_NAME} --quiet 16 | gcloud container clusters list 17 | 18 | echo "remove container" 19 | gcloud container images delete gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} --force-delete-tags --quiet 20 | gcloud container images delete gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME}-daemon --force-delete-tags --quiet -------------------------------------------------------------------------------- /deployment-manager/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .dockerignore 3 | Dockerfile 4 | npm-debug.log 5 | .git 6 | .hg 7 | .svn 8 | test 9 | k8s 10 | .nyc_output 11 | coverage -------------------------------------------------------------------------------- /deployment-manager/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /deployment-manager/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jonathan Campos 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM gcr.io/google_appengine/nodejs 15 | MAINTAINER Jonathan Campos 16 | RUN /usr/local/bin/install_node '>=0.12.7' 17 | COPY . /app/ 18 | RUN npm install --unsafe-perm || \ 19 | ((if [ -f npm-debug.log ]; then \ 20 | cat npm-debug.log; \ 21 | fi) && false) 22 | CMD npm start -------------------------------------------------------------------------------- /deployment-manager/NOTES.md: -------------------------------------------------------------------------------- 1 | ``` 2 | $ gcloud deployment-manager deployments create gke-environment 3 | ``` -------------------------------------------------------------------------------- /deployment-manager/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes - Deployment Manager 2 | This is the deployment manager segment in a multipart series about Kubernetes and some of the finer points 3 | I've learned along the way when using [Kubernetes](https://kubernetes.io/) with 4 | [Google Cloud Platform](https://cloud.google.com/). 5 | 6 | You can read the accompanying post for this folder here: 7 | [Kubernetes - Deployment Manager](https://medium.com/@jonbcampos/). 8 | 9 | To run this code here are the quick steps. 10 | 11 | ### To startup and deploy helm: 12 | ```bash 13 | git clone https://github.com/jonbcampos/kubernetes-series.git 14 | cd ~/kubernetes-series/helm/scripts 15 | sh startup.sh 16 | sh add_helm.sh 17 | sh add_redis.sh # add redis 18 | ``` 19 | 20 | ### To Teardown: 21 | ```bash 22 | cd ~/kubernetes-series/helm/scripts # if necessary 23 | sh teardown.sh 24 | ``` -------------------------------------------------------------------------------- /deployment-manager/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | const app = require('../app'); 7 | const debug = require('debug')('partone:server'); 8 | const http = require('http'); 9 | 10 | /** 11 | * Get port from environment and store in Express. 12 | */ 13 | const port = normalizePort(process.env.PORT || '3000'); 14 | app.set('port', port); 15 | 16 | /** 17 | * Create HTTP server. 18 | */ 19 | const server = http.createServer(app); 20 | 21 | /** 22 | * Listen on provided port, on all network interfaces. 23 | */ 24 | server.listen(port); 25 | server.on('error', onError); 26 | server.on('listening', onListening); 27 | 28 | /** 29 | * Normalize a port into a number, string, or false. 30 | */ 31 | function normalizePort(val) { 32 | const port = parseInt(val, 10); 33 | 34 | if (isNaN(port)) { 35 | // named pipe 36 | return val; 37 | } 38 | 39 | if (port >= 0) { 40 | // port number 41 | return port; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | /** 48 | * Event listener for HTTP server "error" event. 49 | */ 50 | function onError(error) { 51 | if (error.syscall !== 'listen') { 52 | throw error; 53 | } 54 | 55 | const bind = typeof port === 'string' 56 | ? 'Pipe ' + port 57 | : 'Port ' + port; 58 | 59 | // handle specific listen errors with friendly messages 60 | switch (error.code) { 61 | case 'EACCES': 62 | console.error(bind + ' requires elevated privileges'); 63 | process.exit(1); 64 | break; 65 | case 'EADDRINUSE': 66 | console.error(bind + ' is already in use'); 67 | process.exit(1); 68 | break; 69 | default: 70 | throw error; 71 | } 72 | } 73 | 74 | /** 75 | * Event listener for HTTP server "listening" event. 76 | */ 77 | function onListening() { 78 | const addr = server.address(); 79 | const bind = typeof addr === 'string' 80 | ? 'pipe ' + addr 81 | : 'port ' + addr.port; 82 | debug('Listening on ' + bind); 83 | } 84 | -------------------------------------------------------------------------------- /deployment-manager/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nconf = module.exports = require('nconf'); 4 | const path = require('path'); 5 | 6 | nconf 7 | // 1. command-line arguments 8 | .argv() 9 | // 2. env variables 10 | .env([ 11 | 'GCLOUD_PROJECT', 12 | 'POD_ENDPOINT' 13 | ]) 14 | // 3. config file 15 | .file({ file: path.join(__dirname, 'config.json') }) 16 | // 4. defaults 17 | .defaults({ 18 | GCLOUD_PROJECT: '', 19 | POD_ENDPOINT: '', 20 | }); 21 | 22 | // check required settings 23 | checkConfig('GCLOUD_PROJECT'); 24 | checkConfig('POD_ENDPOINT'); 25 | 26 | function checkConfig(setting) { 27 | if (!nconf.get(setting)) { 28 | throw new Error(`You must set ${setting} as an environment variable or in config.json!`); 29 | } 30 | } -------------------------------------------------------------------------------- /deployment-manager/dm/cluster.yaml: -------------------------------------------------------------------------------- 1 | imports: 2 | - path: templates/cluster.jinja 3 | name: cluster.jinja 4 | 5 | resources: 6 | - name: deploy 7 | type: cluster.jinja 8 | properties: 9 | description: "Deploy Cluster" 10 | zones: 11 | - us-central1-a 12 | - us-central1-b 13 | initialNodeCount: 1 14 | minNodeCount: 1 15 | maxNodeCount: 4 -------------------------------------------------------------------------------- /deployment-manager/dm/templates/cluster.jinja: -------------------------------------------------------------------------------- 1 | resources: 2 | - name: {{ env['name'] }}-cluster 3 | type: container.v1.cluster 4 | properties: 5 | zone: {{ properties['zones'][0] }} 6 | cluster: 7 | description: {{ properties['description'] }} 8 | initialClusterVersion: '1.9.7-gke.6' 9 | locations: {{ properties['zones'] }} 10 | nodePools: 11 | - name: {{ env['name'] }}-pool 12 | initialNodeCount: {{ properties['initialNodeCount'] | default(1) }} 13 | config: 14 | machineType: {{ properties['machineType'] | default('n1-standard-1') }} 15 | oauthScopes: 16 | - https://www.googleapis.com/auth/cloud-platform 17 | - https://www.googleapis.com/auth/compute 18 | - https://www.googleapis.com/auth/devstorage.read_only 19 | - https://www.googleapis.com/auth/logging.write 20 | - https://www.googleapis.com/auth/monitoring 21 | preemptible: true 22 | autoscaling: 23 | enabled: true 24 | minNodeCount: {{ properties['minNodeCount'] | default(1) }} 25 | maxNodeCount: {{ properties['maxNodeCount'] | default(3) }} 26 | management: 27 | autoUpgrade: true 28 | autoRepair: true 29 | 30 | outputs: 31 | - name: clusterName 32 | value: $(ref.{{ env['name'] }}-cluster.name) -------------------------------------------------------------------------------- /deployment-manager/dm/templates/cluster.jinja.schema: -------------------------------------------------------------------------------- 1 | info: 2 | title: Cluster Template 3 | author: Jonathan Campos 4 | description: Creates a Kubernetes (GKE) Cluster 5 | version: 1.0 6 | 7 | imports: 8 | - path: cluster.jinja 9 | 10 | required: 11 | - zones 12 | - description 13 | 14 | properties: 15 | zones: 16 | type: array 17 | description: array of zones 18 | 19 | description: 20 | type: string 21 | description: description of cluster 22 | 23 | initialNodeCount: 24 | type: integer 25 | description: initial node count 26 | default: 1 27 | 28 | machineType: 29 | type: string 30 | description: machine type for nodes 31 | efault: n1-standard-1 32 | 33 | minNodeCount: 34 | type: integer 35 | description: minimum allowed nodes for scaling 36 | default: 1 37 | 38 | maxNodeCount: 39 | type: integer 40 | description: maximum allowed nodes for scaling 41 | default: 3 42 | 43 | outputs: 44 | properties: 45 | - clusterName: 46 | description: cluster name 47 | type: string -------------------------------------------------------------------------------- /deployment-manager/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service # a way for the outside world to reach the Pods 3 | metadata: 4 | # any Pods with matching labels are included in this Service 5 | name: endpoints 6 | spec: 7 | # Service ports 8 | ports: 9 | - name: http 10 | port: 80 11 | targetPort: 8080 12 | protocol: TCP 13 | # It includes a LoadBalancer between Pods 14 | type: LoadBalancer 15 | selector: 16 | app: kubernetes-series -------------------------------------------------------------------------------- /deployment-manager/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deployment", 3 | "version": "0.0.1", 4 | "description": "kubernetes blog series", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jonbcampos/kubernetes-series" 9 | }, 10 | "author": { 11 | "name": "Jonathan Campos", 12 | "url": "https://github.com/jonbcampos" 13 | }, 14 | "contributors": [], 15 | "homepage": "http://jonbcampos.com/", 16 | "bugs": { 17 | "url": "https://github.com/jonbcampos/kubernetes-series/issues" 18 | }, 19 | "main": "app.js", 20 | "bin": "bin", 21 | "engines": { 22 | "node": ">=4.3.2" 23 | }, 24 | "scripts": { 25 | "start": "node ./bin/www", 26 | "debug": "node $NODE_DEBUG_OPTION ./app.js", 27 | "auth": "gcloud auth application-default login" 28 | }, 29 | "dependencies": { 30 | "body-parser": "^1.18.3", 31 | "cookie-parser": "~1.4.3", 32 | "debug": "~2.6.9", 33 | "ejs": "~2.5.7", 34 | "express": "~4.16.0", 35 | "express-healthcheck": "^0.1.0", 36 | "http-errors": "~1.6.2", 37 | "morgan": "~1.9.0", 38 | "nconf": "^0.10.0", 39 | "node-sass-middleware": "0.11.0", 40 | "request-promise": "^4.2.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /deployment-manager/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const config = require('./config'); 4 | const os = require('os'); 5 | 6 | const router = express.Router(); 7 | 8 | //------------------------------------- 9 | // 10 | // Automatically parse request body as JSON 11 | // 12 | //------------------------------------- 13 | router.use(bodyParser.json()); 14 | 15 | //------------------------------------- 16 | // 17 | // Set Content-Type for all responses for these routes 18 | // 19 | //------------------------------------- 20 | router.use((req, res, next) => { 21 | res.set('Content-Type', 'application/json'); 22 | next(); 23 | }); 24 | 25 | //------------------------------------- 26 | // 27 | // Routes 28 | // 29 | //------------------------------------- 30 | router.get('/', function (req, res, next) { 31 | res.status(200).json({ hello: 'world' }); 32 | }); 33 | 34 | router.get('/api', function (req, res, next) { 35 | const remoteAddress = req.connection.remoteAddress; 36 | const hostName = os.hostname(); 37 | res.status(200).json({ remoteAddress, hostName }); 38 | }); 39 | 40 | router.get(`/${config.get('POD_ENDPOINT')}`, function (req, res, next) { 41 | const remoteAddress = req.connection.remoteAddress; 42 | const hostName = os.hostname(); 43 | const requestHost = req.headers.host; 44 | res.status(200).json({ remoteAddress, hostName, requestHost }); 45 | }); 46 | 47 | module.exports = router; -------------------------------------------------------------------------------- /deployment-manager/scripts/check-endpoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Pass the name of a service to check ie: sh check-endpoint.sh endpoints 4 | # Will run forever... 5 | external_ip="" 6 | while [ -z $external_ip ]; do 7 | echo "Waiting for end point..." 8 | external_ip=$(kubectl get svc $1 --template="{{range .status.loadBalancer.ingress}}{{.ip}}{{end}}") 9 | [ -z "$external_ip" ] && sleep 10 10 | done 11 | echo 'End point ready:' && echo $external_ip -------------------------------------------------------------------------------- /deployment-manager/scripts/create-dm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=helm 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "deploy with deployment manager" 15 | gcloud deployment-manager deployments create dm-cluster --config ../dm/cluster.yaml -------------------------------------------------------------------------------- /deployment-manager/scripts/delete-dm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=helm 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "deploy with deployment manager" 15 | gcloud deployment-manager deployments delete dm-cluster --quiet -------------------------------------------------------------------------------- /deployment-manager/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=helm 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "name replace" 15 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/deployment.yaml 16 | 17 | echo "create pod-replicaset" 18 | kubectl apply -f ../k8s/deployment.yaml 19 | kubectl apply -f ../k8s/service.yaml -------------------------------------------------------------------------------- /deployment-manager/scripts/startup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=helm 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "enable services" 15 | gcloud services enable compute.googleapis.com 16 | gcloud services enable container.googleapis.com 17 | 18 | echo "creating container engine cluster" 19 | gcloud container clusters create ${CLUSTER_NAME} \ 20 | --preemptible \ 21 | --zone ${INSTANCE_ZONE} \ 22 | --scopes cloud-platform \ 23 | --enable-autorepair \ 24 | --enable-autoupgrade \ 25 | --enable-autoscaling --min-nodes 1 --max-nodes 4 \ 26 | --num-nodes 3 27 | 28 | echo "confirm cluster is running" 29 | gcloud container clusters list 30 | 31 | echo "get credentials" 32 | gcloud container clusters get-credentials ${CLUSTER_NAME} \ 33 | --zone ${INSTANCE_ZONE} 34 | 35 | echo "confirm connection to cluster" 36 | kubectl cluster-info 37 | 38 | echo "create cluster administrator" 39 | kubectl create clusterrolebinding cluster-admin-binding \ 40 | --clusterrole=cluster-admin --user=$(gcloud config get-value account) 41 | 42 | echo "confirm the pod is running" 43 | kubectl get pods 44 | 45 | echo "list production services" 46 | kubectl get svc 47 | 48 | echo "enable services" 49 | gcloud services enable cloudbuild.googleapis.com 50 | 51 | echo "building containers" 52 | gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} ../ -------------------------------------------------------------------------------- /deployment-manager/scripts/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=helm 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "remove cluster" 15 | gcloud container clusters delete ${CLUSTER_NAME} --quiet 16 | gcloud container clusters list 17 | 18 | echo "remove container" 19 | gcloud container images delete gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} --force-delete-tags --quiet -------------------------------------------------------------------------------- /external-communication/.dockerignore: -------------------------------------------------------------------------------- 1 | service-1/node_modules 2 | .dockerignore 3 | Dockerfile 4 | npm-debug.log 5 | .git 6 | .hg 7 | .svn 8 | test 9 | k8s 10 | .nyc_output 11 | coverage -------------------------------------------------------------------------------- /external-communication/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store -------------------------------------------------------------------------------- /external-communication/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jonathan Campos 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM gcr.io/google_appengine/nodejs 15 | MAINTAINER Jonathan Campos 16 | RUN /usr/local/bin/install_node '>=0.12.7' 17 | COPY . /app/ 18 | RUN npm install --unsafe-perm || \ 19 | ((if [ -f npm-debug.log ]; then \ 20 | cat npm-debug.log; \ 21 | fi) && false) 22 | CMD npm start -------------------------------------------------------------------------------- /external-communication/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes - External Communication 2 | This is the communication segment in a multipart series about Kubernetes and some of the finer points 3 | I've learned along the way when using [Kubernetes](https://kubernetes.io/) with 4 | [Google Cloud Platform](https://cloud.google.com/). 5 | 6 | You can read the accompanying post for this folder here: 7 | [Kubernetes - DNS Proxy With Services](https://medium.com/google-cloud/kubernetes-dns-proxy-with-services-d7d9e800c329). 8 | 9 | To run this code here are the quick steps. 10 | 11 | ### To startup and deploy: 12 | ```bash 13 | git clone https://github.com/jonbcampos/kubernetes-series.git 14 | cd ~/kubernetes-series/external-communication/scripts 15 | sh startup.sh 16 | sh deploy.sh 17 | sh check-endpoint.sh 18 | ``` 19 | 20 | ### To Teardown: 21 | ```bash 22 | cd ~/kubernetes-series/external-communication/scripts # if necessary 23 | sh teardown.sh 24 | ``` -------------------------------------------------------------------------------- /external-communication/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | const app = require('../app'); 7 | const debug = require('debug')('partone:server'); 8 | const http = require('http'); 9 | 10 | /** 11 | * Get port from environment and store in Express. 12 | */ 13 | const port = normalizePort(process.env.PORT || '3000'); 14 | app.set('port', port); 15 | 16 | /** 17 | * Create HTTP server. 18 | */ 19 | const server = http.createServer(app); 20 | 21 | /** 22 | * Listen on provided port, on all network interfaces. 23 | */ 24 | server.listen(port); 25 | server.on('error', onError); 26 | server.on('listening', onListening); 27 | 28 | /** 29 | * Normalize a port into a number, string, or false. 30 | */ 31 | function normalizePort(val) { 32 | const port = parseInt(val, 10); 33 | 34 | if (isNaN(port)) { 35 | // named pipe 36 | return val; 37 | } 38 | 39 | if (port >= 0) { 40 | // port number 41 | return port; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | /** 48 | * Event listener for HTTP server "error" event. 49 | */ 50 | function onError(error) { 51 | if (error.syscall !== 'listen') { 52 | throw error; 53 | } 54 | 55 | const bind = typeof port === 'string' 56 | ? 'Pipe ' + port 57 | : 'Port ' + port; 58 | 59 | // handle specific listen errors with friendly messages 60 | switch (error.code) { 61 | case 'EACCES': 62 | console.error(bind + ' requires elevated privileges'); 63 | process.exit(1); 64 | break; 65 | case 'EADDRINUSE': 66 | console.error(bind + ' is already in use'); 67 | process.exit(1); 68 | break; 69 | default: 70 | throw error; 71 | } 72 | } 73 | 74 | /** 75 | * Event listener for HTTP server "listening" event. 76 | */ 77 | function onListening() { 78 | const addr = server.address(); 79 | const bind = typeof addr === 'string' 80 | ? 'pipe ' + addr 81 | : 'port ' + addr.port; 82 | debug('Listening on ' + bind); 83 | } 84 | -------------------------------------------------------------------------------- /external-communication/config.js: -------------------------------------------------------------------------------- 1 | const nconf = module.exports = require('nconf'); 2 | const path = require('path'); 3 | 4 | nconf 5 | // 1. command-line arguments 6 | .argv() 7 | // 2. env variables 8 | .env([ 9 | 'GCLOUD_PROJECT', 10 | 'POD_ENDPOINT', 11 | 'FOREIGN_PATH', 12 | 'FOREIGN_SERVICE' 13 | ]) 14 | // 3. config file 15 | .file({ file: path.join(__dirname, 'config.json') }) 16 | // 4. defaults 17 | .defaults({ 18 | GCLOUD_PROJECT: '', 19 | POD_ENDPOINT: '', 20 | FOREIGN_PATH: '', 21 | FOREIGN_SERVICE: '' 22 | }); 23 | 24 | // check required settings 25 | checkConfig('GCLOUD_PROJECT'); 26 | checkConfig('POD_ENDPOINT'); 27 | checkConfig('FOREIGN_SERVICE'); 28 | checkConfig('FOREIGN_PATH'); 29 | 30 | function checkConfig(setting) { 31 | if (!nconf.get(setting)) { 32 | throw new Error(`You must set ${setting} as an environment variable or in config.json!`); 33 | } 34 | } -------------------------------------------------------------------------------- /external-communication/k8s/external-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service # a way for the outside world to reach the Pods 3 | metadata: 4 | # the name of our external Service 5 | name: external-service 6 | spec: 7 | # Service ports 8 | ports: 9 | - name: http 10 | port: 80 11 | targetPort: 80 12 | protocol: TCP 13 | # Points to external name 14 | type: ExternalName 15 | # the external url 16 | externalName: www.jonbcampos.com 17 | selector: 18 | app: kubernetes-series -------------------------------------------------------------------------------- /external-communication/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service # a way for the outside world to reach the Pods 3 | metadata: 4 | # any Pods with matching labels are included in this Service 5 | name: service 6 | spec: 7 | # Service ports 8 | ports: 9 | - name: http 10 | port: 80 11 | targetPort: 8080 12 | protocol: TCP 13 | - name: https 14 | port: 443 15 | targetPort: 8443 16 | protocol: TCP 17 | # It includes a LoadBalancer between Pods 18 | type: LoadBalancer 19 | selector: 20 | app: kubernetes-series -------------------------------------------------------------------------------- /external-communication/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "communication-service-1", 3 | "version": "0.0.1", 4 | "description": "kubernetes blog series - communication", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jonbcampos/kubernetes-series" 9 | }, 10 | "author": { 11 | "name": "Jonathan Campos", 12 | "url": "https://github.com/jonbcampos" 13 | }, 14 | "contributors": [], 15 | "homepage": "http://jonbcampos.com/", 16 | "bugs": { 17 | "url": "https://github.com/jonbcampos/kubernetes-series/issues" 18 | }, 19 | "main": "app.js", 20 | "bin": "bin", 21 | "engines": { 22 | "node": ">=4.3.2" 23 | }, 24 | "scripts": { 25 | "start": "node ./bin/www", 26 | "debug": "node $NODE_DEBUG_OPTION ./app.js", 27 | "auth": "gcloud auth application-default login" 28 | }, 29 | "dependencies": { 30 | "body-parser": "^1.18.3", 31 | "cookie-parser": "~1.4.3", 32 | "debug": "~2.6.9", 33 | "ejs": "~2.5.7", 34 | "express": "~4.16.0", 35 | "express-healthcheck": "^0.1.0", 36 | "http-errors": "~1.6.2", 37 | "morgan": "~1.9.0", 38 | "nconf": "^0.10.0", 39 | "node-sass-middleware": "0.11.0", 40 | "request-promise": "^4.2.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /external-communication/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const config = require('./config'); 4 | const os = require('os'); 5 | const http = require('http'); 6 | 7 | const router = express.Router(); 8 | 9 | //------------------------------------- 10 | // 11 | // Automatically parse request body as JSON 12 | // 13 | //------------------------------------- 14 | router.use(bodyParser.json()); 15 | 16 | //------------------------------------- 17 | // 18 | // Set Content-Type for all responses for these routes 19 | // 20 | //------------------------------------- 21 | router.use((req, res, next) => { 22 | res.set('Content-Type', 'application/json'); 23 | next(); 24 | }); 25 | 26 | //------------------------------------- 27 | // 28 | // Routes 29 | // 30 | //------------------------------------- 31 | router.get('/', function (req, res, next) { 32 | res.status(200).json({ hello: 'world' }); 33 | }); 34 | 35 | router.get('/api', function (req, res, next) { 36 | const remoteAddress = req.connection.remoteAddress; 37 | const hostName = os.hostname(); 38 | res.status(200).json({ remoteAddress, hostName }); 39 | }); 40 | 41 | router.get(`/${config.get('POD_ENDPOINT')}`, function (req, res, next) { 42 | const remoteAddress = req.connection.remoteAddress; 43 | const hostName = os.hostname(); 44 | const requestHost = req.headers.host; 45 | res.status(200).json({ remoteAddress, hostName, requestHost }); 46 | }); 47 | 48 | router.get('/foreign', function (req, res, next) { 49 | const url = config.get('FOREIGN_SERVICE') + config.get('FOREIGN_PATH'); 50 | http.get(url, response => { 51 | let data = ''; 52 | response.on('data', chunk => { 53 | data += chunk; 54 | }); 55 | response.on('end', () => { 56 | res.status(200).send(data); 57 | }); 58 | }).on('error', err => { 59 | throw err; 60 | }); 61 | }); 62 | 63 | module.exports = router; -------------------------------------------------------------------------------- /external-communication/scripts/check-endpoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Pass the name of a service to check ie: sh check-endpoint.sh endpoints 4 | # Will run forever... 5 | external_ip="" 6 | while [ -z $external_ip ]; do 7 | echo "Waiting for end point..." 8 | external_ip=$(kubectl get svc $1 --template="{{range .status.loadBalancer.ingress}}{{.ip}}{{end}}") 9 | [ -z "$external_ip" ] && sleep 10 10 | done 11 | echo 'End point ready:' && echo $external_ip -------------------------------------------------------------------------------- /external-communication/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=external-communication 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "name replace" 15 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/deployment.yaml 16 | 17 | echo "create pod-replicaset" 18 | kubectl apply -f ../k8s/deployment.yaml 19 | kubectl apply -f ../k8s/service.yaml 20 | kubectl apply -f ../k8s/external-service.yaml -------------------------------------------------------------------------------- /external-communication/scripts/startup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=external-communication 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "enable services" 15 | gcloud services enable compute.googleapis.com 16 | gcloud services enable container.googleapis.com 17 | 18 | echo "creating container engine cluster" 19 | gcloud container clusters create ${CLUSTER_NAME} \ 20 | --preemptible \ 21 | --zone ${INSTANCE_ZONE} \ 22 | --scopes cloud-platform \ 23 | --num-nodes 3 \ 24 | --machine-type f1-micro 25 | 26 | echo "confirm cluster is running" 27 | gcloud container clusters list 28 | 29 | echo "get credentials" 30 | gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${INSTANCE_ZONE} 31 | 32 | echo "confirm connection to cluster" 33 | kubectl cluster-info 34 | 35 | echo "create cluster administrator" 36 | kubectl create clusterrolebinding cluster-admin-binding \ 37 | --clusterrole=cluster-admin \ 38 | --user=$(gcloud config get-value account) 39 | 40 | echo "confirm the pod is running" 41 | kubectl get pods 42 | 43 | echo "list production services" 44 | kubectl get svc 45 | 46 | echo "enable services" 47 | gcloud services enable cloudbuild.googleapis.com 48 | 49 | echo "building containers" 50 | gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} ../ -------------------------------------------------------------------------------- /external-communication/scripts/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=external-communication 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "remove cluster" 15 | gcloud container clusters delete ${CLUSTER_NAME} --quiet 16 | gcloud container clusters list 17 | 18 | echo "remove container" 19 | gcloud container images delete gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} --force-delete-tags --quiet -------------------------------------------------------------------------------- /helm/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .dockerignore 3 | Dockerfile 4 | npm-debug.log 5 | .git 6 | .hg 7 | .svn 8 | test 9 | k8s 10 | .nyc_output 11 | coverage -------------------------------------------------------------------------------- /helm/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /helm/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jonathan Campos 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM gcr.io/google_appengine/nodejs 15 | MAINTAINER Jonathan Campos 16 | RUN /usr/local/bin/install_node '>=0.12.7' 17 | COPY . /app/ 18 | RUN npm install --unsafe-perm || \ 19 | ((if [ -f npm-debug.log ]; then \ 20 | cat npm-debug.log; \ 21 | fi) && false) 22 | CMD npm start -------------------------------------------------------------------------------- /helm/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes - Helm 2 | This is the helm segment in a multipart series about Kubernetes and some of the finer points 3 | I've learned along the way when using [Kubernetes](https://kubernetes.io/) with 4 | [Google Cloud Platform](https://cloud.google.com/). 5 | 6 | You can read the accompanying post for this folder here: 7 | [Kubernetes - Helm](https://medium.com/@jonbcampos/). 8 | 9 | To run this code here are the quick steps. 10 | 11 | ### To startup and deploy helm: 12 | ```bash 13 | git clone https://github.com/jonbcampos/kubernetes-series.git 14 | cd ~/kubernetes-series/helm/scripts 15 | sh startup.sh 16 | sh add_helm.sh 17 | sh add_redis.sh # add redis 18 | ``` 19 | 20 | ### To startup and deploy secure helm: 21 | ```bash 22 | git clone https://github.com/jonbcampos/kubernetes-series.git 23 | cd ~/kubernetes-series/helm/scripts 24 | sh startup.sh 25 | sh add_secure_helm.sh 26 | sh add_secure_redis.sh # add redis 27 | ``` 28 | 29 | ### To Teardown: 30 | ```bash 31 | cd ~/kubernetes-series/helm/scripts # if necessary 32 | sh teardown.sh 33 | ``` -------------------------------------------------------------------------------- /helm/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | const app = require('../app'); 7 | const debug = require('debug')('partone:server'); 8 | const http = require('http'); 9 | 10 | /** 11 | * Get port from environment and store in Express. 12 | */ 13 | const port = normalizePort(process.env.PORT || '3000'); 14 | app.set('port', port); 15 | 16 | /** 17 | * Create HTTP server. 18 | */ 19 | const server = http.createServer(app); 20 | 21 | /** 22 | * Listen on provided port, on all network interfaces. 23 | */ 24 | server.listen(port); 25 | server.on('error', onError); 26 | server.on('listening', onListening); 27 | 28 | /** 29 | * Normalize a port into a number, string, or false. 30 | */ 31 | function normalizePort(val) { 32 | const port = parseInt(val, 10); 33 | 34 | if (isNaN(port)) { 35 | // named pipe 36 | return val; 37 | } 38 | 39 | if (port >= 0) { 40 | // port number 41 | return port; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | /** 48 | * Event listener for HTTP server "error" event. 49 | */ 50 | function onError(error) { 51 | if (error.syscall !== 'listen') { 52 | throw error; 53 | } 54 | 55 | const bind = typeof port === 'string' 56 | ? 'Pipe ' + port 57 | : 'Port ' + port; 58 | 59 | // handle specific listen errors with friendly messages 60 | switch (error.code) { 61 | case 'EACCES': 62 | console.error(bind + ' requires elevated privileges'); 63 | process.exit(1); 64 | break; 65 | case 'EADDRINUSE': 66 | console.error(bind + ' is already in use'); 67 | process.exit(1); 68 | break; 69 | default: 70 | throw error; 71 | } 72 | } 73 | 74 | /** 75 | * Event listener for HTTP server "listening" event. 76 | */ 77 | function onListening() { 78 | const addr = server.address(); 79 | const bind = typeof addr === 'string' 80 | ? 'pipe ' + addr 81 | : 'port ' + addr.port; 82 | debug('Listening on ' + bind); 83 | } 84 | -------------------------------------------------------------------------------- /helm/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nconf = module.exports = require('nconf'); 4 | const path = require('path'); 5 | 6 | nconf 7 | // 1. command-line arguments 8 | .argv() 9 | // 2. env variables 10 | .env([ 11 | 'GCLOUD_PROJECT', 12 | 'POD_ENDPOINT' 13 | ]) 14 | // 3. config file 15 | .file({ file: path.join(__dirname, 'config.json') }) 16 | // 4. defaults 17 | .defaults({ 18 | GCLOUD_PROJECT: '', 19 | POD_ENDPOINT: '', 20 | }); 21 | 22 | // check required settings 23 | checkConfig('GCLOUD_PROJECT'); 24 | checkConfig('POD_ENDPOINT'); 25 | 26 | function checkConfig(setting) { 27 | if (!nconf.get(setting)) { 28 | throw new Error(`You must set ${setting} as an environment variable or in config.json!`); 29 | } 30 | } -------------------------------------------------------------------------------- /helm/endpoints/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /helm/endpoints/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "1.0" 3 | description: A Helm chart for Kubernetes 4 | name: endpoints 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /helm/endpoints/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range .Values.ingress.hosts }} 4 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }} 5 | {{- end }} 6 | {{- else if contains "NodePort" .Values.service.type }} 7 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "chart.fullname" . }}) 8 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 9 | echo http://$NODE_IP:$NODE_PORT 10 | {{- else if contains "LoadBalancer" .Values.service.type }} 11 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 12 | You can watch the status of by running 'kubectl get svc -w {{ template "chart.fullname" . }}' 13 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "chart.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 14 | echo http://$SERVICE_IP:{{ .Values.service.port }} 15 | {{- else if contains "ClusterIP" .Values.service.type }} 16 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "chart.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 17 | echo "Visit http://127.0.0.1:8080 to use your application" 18 | kubectl port-forward $POD_NAME 8080:80 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /helm/endpoints/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "chart.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "chart.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "chart.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /helm/endpoints/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "chart.fullname" . -}} 3 | {{- $ingressPath := .Values.ingress.path -}} 4 | apiVersion: extensions/v1beta1 5 | kind: Ingress 6 | metadata: 7 | name: {{ $fullName }} 8 | labels: 9 | app: {{ template "chart.name" . }} 10 | chart: {{ template "chart.chart" . }} 11 | release: {{ .Release.Name }} 12 | heritage: {{ .Release.Service }} 13 | {{- with .Values.ingress.annotations }} 14 | annotations: 15 | {{ toYaml . | indent 4 }} 16 | {{- end }} 17 | spec: 18 | {{- if .Values.ingress.tls }} 19 | tls: 20 | {{- range .Values.ingress.tls }} 21 | - hosts: 22 | {{- range .hosts }} 23 | - {{ . }} 24 | {{- end }} 25 | secretName: {{ .secretName }} 26 | {{- end }} 27 | {{- end }} 28 | rules: 29 | {{- range .Values.ingress.hosts }} 30 | - host: {{ . }} 31 | http: 32 | paths: 33 | - path: {{ $ingressPath }} 34 | backend: 35 | serviceName: {{ $fullName }} 36 | servicePort: http 37 | {{- end }} 38 | {{- end }} 39 | -------------------------------------------------------------------------------- /helm/endpoints/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ template "chart.fullname" . }} 5 | labels: 6 | app: {{ template "chart.name" . }} 7 | chart: {{ template "chart.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.port }} 14 | targetPort: {{ .Values.service.targetPort }} 15 | protocol: TCP 16 | name: http 17 | selector: 18 | app: {{ template "chart.name" . }} 19 | release: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /helm/endpoints/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for chart. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 3 6 | 7 | readiness: 8 | path: /readiness 9 | port: 8080 10 | 11 | liveness: 12 | path: /healthcheck 13 | port: 8080 14 | 15 | image: 16 | name: helm-container 17 | tag: latest 18 | pullPolicy: IfNotPresent 19 | project: PROJECT_NAME 20 | 21 | env: 22 | podEndpoint: endpoint 23 | 24 | service: 25 | type: LoadBalancer 26 | port: 80 27 | targetPort: 8080 28 | 29 | ingress: 30 | enabled: false 31 | annotations: {} 32 | # kubernetes.io/ingress.class: nginx 33 | # kubernetes.io/tls-acme: "true" 34 | path: / 35 | hosts: 36 | - chart-example.local 37 | tls: [] 38 | # - secretName: chart-example-tls 39 | # hosts: 40 | # - chart-example.local 41 | 42 | resources: {} 43 | # We usually recommend not to specify default resources and to leave this as a conscious 44 | # choice for the user. This also increases chances charts run on environments with little 45 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 46 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 47 | # limits: 48 | # cpu: 100m 49 | # memory: 128Mi 50 | # requests: 51 | # cpu: 100m 52 | # memory: 128Mi 53 | 54 | nodeSelector: {} 55 | 56 | tolerations: [] 57 | 58 | affinity: {} 59 | -------------------------------------------------------------------------------- /helm/index.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | entries: 3 | endpoints: 4 | - apiVersion: v1 5 | appVersion: "1.0" 6 | created: 2018-08-12T15:31:45.988163664-05:00 7 | description: A Helm chart for Kubernetes 8 | digest: c1cd52e22386f199289921a4504833045e5ce51a49e80e41b7ec2c713607bff9 9 | name: endpoints 10 | urls: 11 | - endpoints-0.1.0.tgz 12 | version: 0.1.0 13 | generated: 2018-08-12T15:31:45.98584058-05:00 14 | -------------------------------------------------------------------------------- /helm/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service # a way for the outside world to reach the Pods 3 | metadata: 4 | # any Pods with matching labels are included in this Service 5 | name: endpoints 6 | spec: 7 | # Service ports 8 | ports: 9 | - name: http 10 | port: 80 11 | targetPort: 8080 12 | protocol: TCP 13 | # It includes a LoadBalancer between Pods 14 | type: LoadBalancer 15 | selector: 16 | app: kubernetes-series -------------------------------------------------------------------------------- /helm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "helm", 3 | "version": "0.0.1", 4 | "description": "kubernetes blog series", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jonbcampos/kubernetes-series" 9 | }, 10 | "author": { 11 | "name": "Jonathan Campos", 12 | "url": "https://github.com/jonbcampos" 13 | }, 14 | "contributors": [], 15 | "homepage": "http://jonbcampos.com/", 16 | "bugs": { 17 | "url": "https://github.com/jonbcampos/kubernetes-series/issues" 18 | }, 19 | "main": "app.js", 20 | "bin": "bin", 21 | "engines": { 22 | "node": ">=4.3.2" 23 | }, 24 | "scripts": { 25 | "start": "node ./bin/www", 26 | "debug": "node $NODE_DEBUG_OPTION ./app.js", 27 | "auth": "gcloud auth application-default login" 28 | }, 29 | "dependencies": { 30 | "body-parser": "^1.18.3", 31 | "cookie-parser": "~1.4.3", 32 | "debug": "~2.6.9", 33 | "ejs": "~2.5.7", 34 | "express": "~4.16.0", 35 | "express-healthcheck": "^0.1.0", 36 | "http-errors": "~1.6.2", 37 | "morgan": "~1.9.0", 38 | "nconf": "^0.10.0", 39 | "node-sass-middleware": "0.11.0", 40 | "request-promise": "^4.2.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /helm/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const config = require('./config'); 4 | const os = require('os'); 5 | 6 | const router = express.Router(); 7 | 8 | //------------------------------------- 9 | // 10 | // Automatically parse request body as JSON 11 | // 12 | //------------------------------------- 13 | router.use(bodyParser.json()); 14 | 15 | //------------------------------------- 16 | // 17 | // Set Content-Type for all responses for these routes 18 | // 19 | //------------------------------------- 20 | router.use((req, res, next) => { 21 | res.set('Content-Type', 'application/json'); 22 | next(); 23 | }); 24 | 25 | //------------------------------------- 26 | // 27 | // Routes 28 | // 29 | //------------------------------------- 30 | router.get('/', function (req, res, next) { 31 | res.status(200).json({ hello: 'world' }); 32 | }); 33 | 34 | router.get('/api', function (req, res, next) { 35 | const remoteAddress = req.connection.remoteAddress; 36 | const hostName = os.hostname(); 37 | res.status(200).json({ remoteAddress, hostName }); 38 | }); 39 | 40 | router.get(`/${config.get('POD_ENDPOINT')}`, function (req, res, next) { 41 | const remoteAddress = req.connection.remoteAddress; 42 | const hostName = os.hostname(); 43 | const requestHost = req.headers.host; 44 | res.status(200).json({ remoteAddress, hostName, requestHost }); 45 | }); 46 | 47 | module.exports = router; -------------------------------------------------------------------------------- /helm/scripts/add__secure_redis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "install redis" 4 | helm install stable/redis \ 5 | --tls \ 6 | --values values/values-production.yaml \ 7 | --name redis-system -------------------------------------------------------------------------------- /helm/scripts/add_helm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "install helm" 4 | # installs helm with bash commands for easier command line integration 5 | curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get | bash 6 | # add a service account within a namespace to segregate tiller 7 | kubectl --namespace kube-system create sa tiller 8 | # create a cluster role binding for tiller 9 | kubectl create clusterrolebinding tiller \ 10 | --clusterrole cluster-admin \ 11 | --serviceaccount=kube-system:tiller 12 | 13 | echo "initialize helm" 14 | # initialized helm within the tiller service account 15 | helm init --service-account tiller 16 | # updates the repos for Helm repo integration 17 | helm repo update 18 | 19 | echo "verify helm" 20 | # verify that helm is installed in the cluster 21 | kubectl get deploy,svc tiller-deploy -n kube-system -------------------------------------------------------------------------------- /helm/scripts/add_redis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "install redis" 4 | helm install stable/redis \ 5 | --values values/values-production.yaml \ 6 | --name redis-system -------------------------------------------------------------------------------- /helm/scripts/check-endpoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Pass the name of a service to check ie: sh check-endpoint.sh endpoints 4 | # Will run forever... 5 | external_ip="" 6 | while [ -z $external_ip ]; do 7 | echo "Waiting for end point..." 8 | external_ip=$(kubectl get svc $1 --template="{{range .status.loadBalancer.ingress}}{{.ip}}{{end}}") 9 | [ -z "$external_ip" ] && sleep 10 10 | done 11 | echo 'End point ready:' && echo $external_ip -------------------------------------------------------------------------------- /helm/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=helm 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "name replace" 15 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/deployment.yaml 16 | 17 | echo "create pod-replicaset" 18 | kubectl apply -f ../k8s/deployment.yaml 19 | kubectl apply -f ../k8s/service.yaml -------------------------------------------------------------------------------- /helm/scripts/remove_redis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "uninstall redis" 4 | helm delete redis-system -------------------------------------------------------------------------------- /helm/scripts/remove_secure_redis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "uninstall redis" 4 | helm delete --tls redis-system -------------------------------------------------------------------------------- /helm/scripts/startup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=helm 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "enable services" 15 | gcloud services enable compute.googleapis.com 16 | gcloud services enable container.googleapis.com 17 | 18 | echo "creating container engine cluster" 19 | gcloud container clusters create ${CLUSTER_NAME} \ 20 | --preemptible \ 21 | --zone ${INSTANCE_ZONE} \ 22 | --scopes cloud-platform \ 23 | --enable-autorepair \ 24 | --enable-autoupgrade \ 25 | --enable-autoscaling --min-nodes 1 --max-nodes 4 \ 26 | --num-nodes 3 27 | 28 | echo "confirm cluster is running" 29 | gcloud container clusters list 30 | 31 | echo "get credentials" 32 | gcloud container clusters get-credentials ${CLUSTER_NAME} \ 33 | --zone ${INSTANCE_ZONE} 34 | 35 | echo "confirm connection to cluster" 36 | kubectl cluster-info 37 | 38 | echo "create cluster administrator" 39 | kubectl create clusterrolebinding cluster-admin-binding \ 40 | --clusterrole=cluster-admin --user=$(gcloud config get-value account) 41 | 42 | echo "confirm the pod is running" 43 | kubectl get pods 44 | 45 | echo "list production services" 46 | kubectl get svc 47 | 48 | echo "enable services" 49 | gcloud services enable cloudbuild.googleapis.com 50 | 51 | echo "building containers" 52 | gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} ../ -------------------------------------------------------------------------------- /helm/scripts/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=helm 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "remove cluster" 15 | gcloud container clusters delete ${CLUSTER_NAME} --quiet 16 | gcloud container clusters list 17 | 18 | echo "remove container" 19 | gcloud container images delete gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} --force-delete-tags --quiet 20 | 21 | echo "cleanup files" 22 | rm $(helm home) -rf 23 | rm ca.* tiller.* helm.* -------------------------------------------------------------------------------- /helm/scripts/values/rbac-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: tiller 5 | namespace: kube-system 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1beta1 8 | kind: ClusterRoleBinding 9 | metadata: 10 | name: tiller 11 | roleRef: 12 | apiGroup: rbac.authorization.k8s.io 13 | kind: ClusterRole 14 | name: cluster-admin 15 | subjects: 16 | - kind: ServiceAccount 17 | name: tiller 18 | namespace: kube-system -------------------------------------------------------------------------------- /helm/scripts/values/role-tiller.yaml: -------------------------------------------------------------------------------- 1 | kind: Role 2 | apiVersion: rbac.authorization.k8s.io/v1beta1 3 | metadata: 4 | name: tiller-manager 5 | namespace: tiller 6 | rules: 7 | - apiGroups: ["", "batch", "extensions", "apps"] 8 | resources: ["*"] 9 | verbs: ["*"] -------------------------------------------------------------------------------- /helm/scripts/values/rolebinding-tiller.yaml: -------------------------------------------------------------------------------- 1 | kind: RoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1beta1 3 | metadata: 4 | name: tiller-binding 5 | namespace: tiller 6 | subjects: 7 | - kind: ServiceAccount 8 | name: tiller 9 | namespace: tiller 10 | roleRef: 11 | kind: Role 12 | name: tiller-manager 13 | apiGroup: rbac.authorization.k8s.io -------------------------------------------------------------------------------- /partone/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .dockerignore 3 | Dockerfile 4 | npm-debug.log 5 | .git 6 | .hg 7 | .svn 8 | test 9 | k8s 10 | .nyc_output 11 | coverage -------------------------------------------------------------------------------- /partone/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store -------------------------------------------------------------------------------- /partone/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jonathan Campos 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM gcr.io/google_appengine/nodejs 15 | MAINTAINER Jonathan Campos 16 | RUN /usr/local/bin/install_node '>=0.12.7' 17 | COPY . /app/ 18 | RUN npm install --unsafe-perm || \ 19 | ((if [ -f npm-debug.log ]; then \ 20 | cat npm-debug.log; \ 21 | fi) && false) 22 | CMD npm start -------------------------------------------------------------------------------- /partone/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes - Part One 2 | This is the first part in a multipart series about Kubernetes and some of the finer points 3 | I've learned along the way when using [Kubernetes](https://kubernetes.io/) with 4 | [Google Cloud Platform](https://cloud.google.com/). 5 | 6 | You can read the accompanying post for this folder here: 7 | [Kubernetes - Day One](https://medium.com/@jonbcampos/kubernetes-day-one-30a80b5dcb29). 8 | 9 | and 10 | 11 | [Kubernetes - Liveness Checks](https://medium.com/@jonbcampos/kubernetes-liveness-checks-4e73c631661f) 12 | [Kubernetes - Readiness Probe](https://itnext.io/kubernetes-readiness-probe-83f8a06d33d3) 13 | 14 | To run this code here are the quick steps. 15 | 16 | ### To startup and deploy: 17 | ```bash 18 | git clone https://github.com/jonbcampos/kubernetes-series.git 19 | cd kubernetes-series/partone/scripts 20 | sh startup.sh 21 | sh deploy.sh 22 | sh check-endpoint.sh 23 | ``` 24 | 25 | ### To Teardown: 26 | ```bash 27 | cd kubernetes-series/partone/scripts # if necessary 28 | sh teardown.sh 29 | ``` -------------------------------------------------------------------------------- /partone/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | const app = require('../app'); 7 | const debug = require('debug')('partone:server'); 8 | const http = require('http'); 9 | 10 | /** 11 | * Get port from environment and store in Express. 12 | */ 13 | const port = normalizePort(process.env.PORT || '3000'); 14 | app.set('port', port); 15 | 16 | /** 17 | * Create HTTP server. 18 | */ 19 | const server = http.createServer(app); 20 | 21 | /** 22 | * Listen on provided port, on all network interfaces. 23 | */ 24 | server.listen(port); 25 | server.on('error', onError); 26 | server.on('listening', onListening); 27 | 28 | /** 29 | * Normalize a port into a number, string, or false. 30 | */ 31 | function normalizePort(val) { 32 | const port = parseInt(val, 10); 33 | 34 | if (isNaN(port)) { 35 | // named pipe 36 | return val; 37 | } 38 | 39 | if (port >= 0) { 40 | // port number 41 | return port; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | /** 48 | * Event listener for HTTP server "error" event. 49 | */ 50 | function onError(error) { 51 | if (error.syscall !== 'listen') { 52 | throw error; 53 | } 54 | 55 | const bind = typeof port === 'string' 56 | ? 'Pipe ' + port 57 | : 'Port ' + port; 58 | 59 | // handle specific listen errors with friendly messages 60 | switch (error.code) { 61 | case 'EACCES': 62 | console.error(bind + ' requires elevated privileges'); 63 | process.exit(1); 64 | break; 65 | case 'EADDRINUSE': 66 | console.error(bind + ' is already in use'); 67 | process.exit(1); 68 | break; 69 | default: 70 | throw error; 71 | } 72 | } 73 | 74 | /** 75 | * Event listener for HTTP server "listening" event. 76 | */ 77 | function onListening() { 78 | const addr = server.address(); 79 | const bind = typeof addr === 'string' 80 | ? 'pipe ' + addr 81 | : 'port ' + addr.port; 82 | debug('Listening on ' + bind); 83 | } 84 | -------------------------------------------------------------------------------- /partone/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nconf = module.exports = require('nconf'); 4 | const path = require('path'); 5 | 6 | nconf 7 | // 1. command-line arguments 8 | .argv() 9 | // 2. env variables 10 | .env([ 11 | 'GCLOUD_PROJECT', 12 | 'POD_ENDPOINT' 13 | ]) 14 | // 3. config file 15 | .file({ file: path.join(__dirname, 'config.json') }) 16 | // 4. defaults 17 | .defaults({ 18 | GCLOUD_PROJECT: '', 19 | POD_ENDPOINT: '', 20 | }); 21 | 22 | // check required settings 23 | checkConfig('GCLOUD_PROJECT'); 24 | checkConfig('POD_ENDPOINT'); 25 | 26 | function checkConfig(setting) { 27 | if (!nconf.get(setting)) { 28 | throw new Error(`You must set ${setting} as an environment variable or in config.json!`); 29 | } 30 | } -------------------------------------------------------------------------------- /partone/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service # a way for the outside world to reach the Pods 3 | metadata: 4 | # any Pods with matching labels are included in this Service 5 | name: endpoints 6 | spec: 7 | # Service ports 8 | ports: 9 | - name: http 10 | port: 80 11 | targetPort: 8080 12 | protocol: TCP 13 | - name: https 14 | port: 443 15 | targetPort: 8443 16 | protocol: TCP 17 | # It includes a LoadBalancer between Pods 18 | type: LoadBalancer 19 | selector: 20 | app: kubernetes-series -------------------------------------------------------------------------------- /partone/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "partone", 3 | "version": "0.0.1", 4 | "description": "kubernetes blog series part one", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jonbcampos/kubernetes-series" 9 | }, 10 | "author": { 11 | "name": "Jonathan Campos", 12 | "url": "https://github.com/jonbcampos" 13 | }, 14 | "contributors": [], 15 | "homepage": "http://jonbcampos.com/", 16 | "bugs": { 17 | "url": "https://github.com/jonbcampos/kubernetes-series/issues" 18 | }, 19 | "main": "app.js", 20 | "bin": "bin", 21 | "engines": { 22 | "node": ">=4.3.2" 23 | }, 24 | "scripts": { 25 | "start": "node ./bin/www", 26 | "debug": "node $NODE_DEBUG_OPTION ./app.js", 27 | "auth": "gcloud auth application-default login" 28 | }, 29 | "dependencies": { 30 | "body-parser": "^1.18.3", 31 | "cookie-parser": "~1.4.3", 32 | "debug": "~2.6.9", 33 | "ejs": "~2.5.7", 34 | "express": "~4.16.0", 35 | "express-healthcheck": "^0.1.0", 36 | "http-errors": "~1.6.2", 37 | "morgan": "~1.9.0", 38 | "nconf": "^0.10.0", 39 | "node-sass-middleware": "0.11.0", 40 | "request-promise": "^4.2.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /partone/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const config = require('./config'); 4 | const os = require('os'); 5 | 6 | const router = express.Router(); 7 | 8 | //------------------------------------- 9 | // 10 | // Automatically parse request body as JSON 11 | // 12 | //------------------------------------- 13 | router.use(bodyParser.json()); 14 | 15 | //------------------------------------- 16 | // 17 | // Set Content-Type for all responses for these routes 18 | // 19 | //------------------------------------- 20 | router.use((req, res, next) => { 21 | res.set('Content-Type', 'application/json'); 22 | next(); 23 | }); 24 | 25 | //------------------------------------- 26 | // 27 | // Routes 28 | // 29 | //------------------------------------- 30 | router.get('/', function (req, res, next) { 31 | res.status(200).json({ hello: 'world' }); 32 | }); 33 | 34 | router.get('/api', function (req, res, next) { 35 | const remoteAddress = req.connection.remoteAddress; 36 | const hostName = os.hostname(); 37 | res.status(200).json({ remoteAddress, hostName }); 38 | }); 39 | 40 | router.get(`/${config.get('POD_ENDPOINT')}`, function (req, res, next) { 41 | const remoteAddress = req.connection.remoteAddress; 42 | const hostName = os.hostname(); 43 | const requestHost = req.headers.host; 44 | res.status(200).json({ remoteAddress, hostName, requestHost }); 45 | }); 46 | 47 | module.exports = router; -------------------------------------------------------------------------------- /partone/scripts/check-endpoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Pass the name of a service to check ie: sh check-endpoint.sh endpoints 4 | # Will run forever... 5 | external_ip="" 6 | while [ -z $external_ip ]; do 7 | echo "Waiting for end point..." 8 | external_ip=$(kubectl get svc $1 --template="{{range .status.loadBalancer.ingress}}{{.ip}}{{end}}") 9 | [ -z "$external_ip" ] && sleep 10 10 | done 11 | echo 'End point ready:' && echo $external_ip -------------------------------------------------------------------------------- /partone/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=partone 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "name replace" 15 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/deployment.yaml 16 | 17 | echo "create pod-replicaset" 18 | kubectl apply -f ../k8s/deployment.yaml 19 | kubectl apply -f ../k8s/service.yaml -------------------------------------------------------------------------------- /partone/scripts/startup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=partone 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "enable services" 15 | gcloud services enable compute.googleapis.com 16 | gcloud services enable container.googleapis.com 17 | 18 | echo "creating container engine cluster" 19 | gcloud container clusters create ${CLUSTER_NAME} --preemptible --zone ${INSTANCE_ZONE} --scopes cloud-platform --num-nodes 3 20 | 21 | echo "confirm cluster is running" 22 | gcloud container clusters list 23 | 24 | echo "get credentials" 25 | gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${INSTANCE_ZONE} 26 | 27 | echo "confirm connection to cluster" 28 | kubectl cluster-info 29 | 30 | echo "create cluster administrator" 31 | kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=$(gcloud config get-value account) 32 | 33 | echo "confirm the pod is running" 34 | kubectl get pods 35 | 36 | echo "list production services" 37 | kubectl get svc 38 | 39 | echo "enable services" 40 | gcloud services enable cloudbuild.googleapis.com 41 | 42 | echo "building containers" 43 | gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} ../ -------------------------------------------------------------------------------- /partone/scripts/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=partone 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "remove cluster" 15 | gcloud container clusters delete ${CLUSTER_NAME} --quiet 16 | gcloud container clusters list 17 | 18 | echo "remove container" 19 | gcloud container images delete gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} --force-delete-tags --quiet -------------------------------------------------------------------------------- /secrets/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .dockerignore 3 | Dockerfile 4 | npm-debug.log 5 | .git 6 | .hg 7 | .svn 8 | test 9 | k8s 10 | .nyc_output 11 | coverage -------------------------------------------------------------------------------- /secrets/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /secrets/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Jonathan Campos 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | FROM gcr.io/google_appengine/nodejs 15 | MAINTAINER Jonathan Campos 16 | RUN /usr/local/bin/install_node '>=0.12.7' 17 | COPY . /app/ 18 | RUN npm install --unsafe-perm || \ 19 | ((if [ -f npm-debug.log ]; then \ 20 | cat npm-debug.log; \ 21 | fi) && false) 22 | CMD npm start -------------------------------------------------------------------------------- /secrets/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes - Helm 2 | This is the helm segment in a multipart series about Kubernetes and some of the finer points 3 | I've learned along the way when using [Kubernetes](https://kubernetes.io/) with 4 | [Google Cloud Platform](https://cloud.google.com/). 5 | 6 | You can read the accompanying post for this folder here: 7 | [Kubernetes - Helm](https://medium.com/@jonbcampos/). 8 | 9 | To run this code here are the quick steps. 10 | 11 | ### To startup and deploy helm: 12 | ```bash 13 | git clone https://github.com/jonbcampos/kubernetes-series.git 14 | cd ~/kubernetes-series/helm/scripts 15 | sh startup.sh 16 | sh add_helm.sh 17 | sh add_redis.sh # add redis 18 | ``` 19 | 20 | ### To startup and deploy secure helm: 21 | ```bash 22 | git clone https://github.com/jonbcampos/kubernetes-series.git 23 | cd ~/kubernetes-series/helm/scripts 24 | sh startup.sh 25 | sh add_secure_helm.sh 26 | sh add_secure_redis.sh # add redis 27 | ``` 28 | 29 | ### To Teardown: 30 | ```bash 31 | cd ~/kubernetes-series/helm/scripts # if necessary 32 | sh teardown.sh 33 | ``` -------------------------------------------------------------------------------- /secrets/app.js: -------------------------------------------------------------------------------- 1 | const createError = require('http-errors'); 2 | const express = require('express'); 3 | const logger = require('morgan'); 4 | const config = require('./config'); 5 | const healthCheck = require('express-healthcheck'); 6 | const pkg = require('./package'); 7 | 8 | //------------------------------------- 9 | // 10 | // setup the app 11 | // 12 | //------------------------------------- 13 | const app = express(); 14 | app.use(logger('dev')); 15 | app.set('json spaces', 2); 16 | app.set('trust proxy', true); 17 | app.set('case sensitive routing', true); 18 | app.use(express.json()); 19 | app.use(express.urlencoded({ extended: false })); 20 | 21 | const healthy = true; 22 | const ready = true; 23 | 24 | //------------------------------------- 25 | // 26 | // setup some routes for us to hit and 27 | // other helpful routes when working with kubernetes 28 | // 29 | //------------------------------------- 30 | app.use('/', require('./routes')); 31 | app.use('/healthcheck', healthCheck()); 32 | app.use('/readiness', function (req, res, next) { 33 | res.status(200).json({ ready }); 34 | }); 35 | app.get('/version', function (req, res, next) { 36 | const version = pkg.version; 37 | res.status(200).json({ version }); 38 | }); 39 | process.on('SIGTERM', function () { 40 | // cleanup environment, this is about to be shut down 41 | process.exit(0); 42 | }); 43 | 44 | //------------------------------------- 45 | // 46 | // catch 404 and forward to error handler 47 | // 48 | //------------------------------------- 49 | app.use(function (req, res, next) { 50 | next(createError(404)); 51 | }); 52 | 53 | //------------------------------------- 54 | // 55 | // error handler 56 | // 57 | //------------------------------------- 58 | app.use(function (err, req, res, next) { 59 | // set locals, only providing error in development 60 | err.response = { 61 | message: err.message, 62 | internalCode: err.code 63 | }; 64 | // render the error page 65 | res.status(err.status || 500).json(err.response); 66 | }); 67 | 68 | //------------------------------------- 69 | // 70 | // create server if this is main 71 | // 72 | //------------------------------------- 73 | if (module === require.main) { 74 | // Start the server 75 | const server = app.listen(config.get('PORT'), () => { 76 | const port = server.address().port; 77 | console.log(`App listening on port ${port}`); 78 | }); 79 | } 80 | 81 | module.exports = app; 82 | -------------------------------------------------------------------------------- /secrets/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | const app = require('../app'); 7 | const debug = require('debug')('partone:server'); 8 | const http = require('http'); 9 | 10 | /** 11 | * Get port from environment and store in Express. 12 | */ 13 | const port = normalizePort(process.env.PORT || '3000'); 14 | app.set('port', port); 15 | 16 | /** 17 | * Create HTTP server. 18 | */ 19 | const server = http.createServer(app); 20 | 21 | /** 22 | * Listen on provided port, on all network interfaces. 23 | */ 24 | server.listen(port); 25 | server.on('error', onError); 26 | server.on('listening', onListening); 27 | 28 | /** 29 | * Normalize a port into a number, string, or false. 30 | */ 31 | function normalizePort(val) { 32 | const port = parseInt(val, 10); 33 | 34 | if (isNaN(port)) { 35 | // named pipe 36 | return val; 37 | } 38 | 39 | if (port >= 0) { 40 | // port number 41 | return port; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | /** 48 | * Event listener for HTTP server "error" event. 49 | */ 50 | function onError(error) { 51 | if (error.syscall !== 'listen') { 52 | throw error; 53 | } 54 | 55 | const bind = typeof port === 'string' 56 | ? 'Pipe ' + port 57 | : 'Port ' + port; 58 | 59 | // handle specific listen errors with friendly messages 60 | switch (error.code) { 61 | case 'EACCES': 62 | console.error(bind + ' requires elevated privileges'); 63 | process.exit(1); 64 | break; 65 | case 'EADDRINUSE': 66 | console.error(bind + ' is already in use'); 67 | process.exit(1); 68 | break; 69 | default: 70 | throw error; 71 | } 72 | } 73 | 74 | /** 75 | * Event listener for HTTP server "listening" event. 76 | */ 77 | function onListening() { 78 | const addr = server.address(); 79 | const bind = typeof addr === 'string' 80 | ? 'pipe ' + addr 81 | : 'port ' + addr.port; 82 | debug('Listening on ' + bind); 83 | } 84 | -------------------------------------------------------------------------------- /secrets/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nconf = module.exports = require('nconf'); 4 | const path = require('path'); 5 | 6 | nconf 7 | // 1. command-line arguments 8 | .argv() 9 | // 2. env variables 10 | .env([ 11 | 'SOME_VALUE' 12 | ]) 13 | // 3. config file 14 | .file({ file: path.join(__dirname, 'config.json') }) 15 | // 4. defaults 16 | .defaults({ 17 | SOME_VALUE: '' 18 | }); 19 | 20 | // check required settings 21 | checkConfig('SOME_VALUE'); 22 | 23 | function checkConfig(setting) { 24 | if (!nconf.get(setting)) { 25 | throw new Error(`You must set ${setting} as an environment variable or in config.json!`); 26 | } 27 | } -------------------------------------------------------------------------------- /secrets/k8s/basic-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap # it is a config map 3 | data: # key value pairs 4 | NODE_ENV: "production" 5 | SOME_VALUE: "my config value" 6 | metadata: 7 | name: basic-config # name to reference -------------------------------------------------------------------------------- /secrets/k8s/files/serviceAccountKey.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "service_account", 3 | "project_id": "kubernetes-series-secrets", 4 | "private_key_id": "a1092659734ad0689208214e9a5a5b56fa92a52e", 5 | "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXTtwUxanydgaJ\nHNJelggI2Pu1aMEpi/DWjTUmzuJTWSe6LhGRcfU/c9RTppfqc7e0dz1wYC9wA8za\n7ITdRCTry5tFbOLVJ3ZZQNNRrmlqWxsNyYr/28z8w03lb+613wWcAp1xdTh8pW3y\newoLxdZD1TsvqIAKLXoJr49WWisZ84jBXi9nWgfUoqhdtcpBSsFUpK/QKPUQOR37\nojIymbkFIKa8Ngl6VFMmVmkWDmBNXzp/0YeaaJ4aHbaUQfhmjlvufe/RDz6NVJQ9\nZUBRrIWNmMGCMX/EngB0mx0aN0Jf87IecrFxsikkMKC5oDI7BKD6nmzOQAbcVJaq\nHwgudzVbAgMBAAECggEAEZHMqHTTaR4fYCiCnQ0a3bwkbko9s89WJfO7qkf3q+n8\nXPggfh0Xd0jB7Py5QzYwrLnz3mqs13r5fKpeOztAdkjsPMGKDWpJqc+HOkLgZsMx\nfwZaJ5+kS+15zByVQXUmN5luItCTwJWNEM0Mu5U7biECgGQ2ifgB5cIHCOXrf1XE\nU8ruRCEiue+KJRPk5iD2Laa8vuXoLS6H+K52OAjJFtGqGXPU01XMIdSWIqCNCbjg\nVPetE3h5KrncR4VGHkr9nd6ZXYMZDK+eQminn7et75uwAe6xEz65VESfs1GojtUh\nYmX4SsqNVzUOXNiVz30PcVv2++IMuNFHBc0n71Oi8QKBgQDPlubtOoXUZHo7S+P1\nw3GyaUG8x/eq6LN8PO9nNj5ASvyLrPIXqOFO4qw21nIdJC/UJGGCqv7rxSHKvklA\nfTBdNS6HeGV2aJBZIr3tCy0cWPYbxFJPSoAdMWmA2ASboYbSyz8+OYG9mGC5iDC+\nn72APPHGxsmpP8vYLUDWtxAJTwKBgQC6l/SIHExVg1LggEq7mOYq4yXHT/VOoCGM\nPej9fwxhQn3182nyycIn3i69JACnvDHR8Wd5KAKrbdfTTKsW7OVMRMYurwj2xp6W\n8LUd17YNy9NwMx3+sSiQ4OZCCcWHCdtFeMPPO5eORUoZDQFK1Rv4W96PBkipipII\nun2FWSQ4NQKBgGu3uxaIUp+Wj9KrG5wQkbPpaGIkRYDbBR/Hotak0AKeppN355ud\nV2SOvJwsBMTYXTUwt6SNRIBlJ5bDzND45RrImN9U+xlJQvXt0C3rqbnW88YOxkM8\nvmPnmv5vmVyEoahKLQQF5SvToQVFBT41N6kOdssVpdj8MLN5L/b6wQiDAoGADiUG\nvGqovrK9zZbsE1x71jRx4LIG/nbVFDR9PPosjhLkSYiyN11kAnGtSVk5U33IqQmL\ntnt5+FTTT4k4TvyWRTnRbCiInWxhCUAl+qUnf9Q7qv21AGGdmZ3y1n63Io10ucd1\n0HfL/VTzUQLTXkDZZfJQ5LatO2zttQuuCDXBsSECgYEAnkqEXycKcmVENUsoKn2I\nT5nbVI+89JffQtNQk4I/t6J+l9tNtX9MwMlh66XQDwl3kLU+znCVFxsIKa08/tCf\nGKTgTzkurDvg5CPH1wWPdhCNkVXdcKj4rSBueqWXM+1fr4nfidhYH5PPhVX+aXS3\nEke62IaT3Bgfe7FqQzfx9QU=\n-----END PRIVATE KEY-----\n", 6 | "client_email": "firebase-adminsdk-ze0pz@kubernetes-series-secrets.iam.gserviceaccount.com", 7 | "client_id": "116145878084226758643", 8 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", 9 | "token_uri": "https://oauth2.googleapis.com/token", 10 | "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", 11 | "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-ze0pz%40kubernetes-series-secrets.iam.gserviceaccount.com" 12 | } 13 | -------------------------------------------------------------------------------- /secrets/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service # a way for the outside world to reach the Pods 3 | metadata: 4 | # any Pods with matching labels are included in this Service 5 | name: endpoints 6 | spec: 7 | # Service ports 8 | ports: 9 | - name: http 10 | port: 80 11 | targetPort: 8080 12 | protocol: TCP 13 | # It includes a LoadBalancer between Pods 14 | type: LoadBalancer 15 | selector: 16 | app: kubernetes-series -------------------------------------------------------------------------------- /secrets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "helm", 3 | "version": "0.0.1", 4 | "description": "kubernetes blog series", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jonbcampos/kubernetes-series" 9 | }, 10 | "author": { 11 | "name": "Jonathan Campos", 12 | "url": "https://github.com/jonbcampos" 13 | }, 14 | "contributors": [], 15 | "homepage": "http://jonbcampos.com/", 16 | "bugs": { 17 | "url": "https://github.com/jonbcampos/kubernetes-series/issues" 18 | }, 19 | "main": "app.js", 20 | "bin": "bin", 21 | "engines": { 22 | "node": ">=4.3.2" 23 | }, 24 | "scripts": { 25 | "start": "node ./bin/www", 26 | "debug": "node $NODE_DEBUG_OPTION ./app.js", 27 | "auth": "gcloud auth application-default login" 28 | }, 29 | "dependencies": { 30 | "body-parser": "^1.18.3", 31 | "cookie-parser": "~1.4.3", 32 | "debug": "~2.6.9", 33 | "ejs": "~2.5.7", 34 | "express": "~4.16.0", 35 | "express-healthcheck": "^0.1.0", 36 | "firebase-admin": "^6.0.0", 37 | "http-errors": "~1.6.2", 38 | "morgan": "~1.9.0", 39 | "nconf": "^0.10.0", 40 | "node-sass-middleware": "0.11.0", 41 | "request-promise": "^4.2.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /secrets/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const config = require('./config'); 4 | const os = require('os'); 5 | 6 | const admin = require('firebase-admin'); 7 | const serviceAccount = require('/opt/firebase/serviceAccountKey.json'); 8 | admin.initializeApp({ 9 | credential: admin.credential.cert(serviceAccount), 10 | databaseUrl: 'https://kubernetes-series-secrets.firebaseio.com' 11 | }); 12 | 13 | const router = express.Router(); 14 | 15 | //------------------------------------- 16 | // 17 | // Automatically parse request body as JSON 18 | // 19 | //------------------------------------- 20 | router.use(bodyParser.json()); 21 | 22 | //------------------------------------- 23 | // 24 | // Set Content-Type for all responses for these routes 25 | // 26 | //------------------------------------- 27 | router.use((req, res, next) => { 28 | res.set('Content-Type', 'application/json'); 29 | next(); 30 | }); 31 | 32 | //------------------------------------- 33 | // 34 | // Routes 35 | // 36 | //------------------------------------- 37 | router.get('/', function (req, res, next) { 38 | res.status(200).json({ hello: 'world' }); 39 | }); 40 | 41 | router.get('/api', function (req, res, next) { 42 | const remoteAddress = req.connection.remoteAddress; 43 | const hostName = os.hostname(); 44 | const configVar = config.get('SOME_VALUE'); 45 | res.status(200).json({ remoteAddress, hostName, configVar }); 46 | }); 47 | 48 | router.get('/secret', function(req, res, next) { 49 | admin.auth().listUsers(100) 50 | .then(function(result){ 51 | res.status(200).json(result); 52 | }) 53 | .catch(function(error){ 54 | console.log(error); 55 | }); 56 | }); 57 | 58 | module.exports = router; -------------------------------------------------------------------------------- /secrets/scripts/check-endpoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Pass the name of a service to check ie: sh check-endpoint.sh endpoints 4 | # Will run forever... 5 | external_ip="" 6 | while [ -z $external_ip ]; do 7 | echo "Waiting for end point..." 8 | external_ip=$(kubectl get svc $1 --template="{{range .status.loadBalancer.ingress}}{{.ip}}{{end}}") 9 | [ -z "$external_ip" ] && sleep 10 10 | done 11 | echo 'End point ready:' && echo "http://${external_ip}" -------------------------------------------------------------------------------- /secrets/scripts/create-secret.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=secrets 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "create secret in cluster" 15 | kubectl create secret generic firebase-secret \ 16 | --from-file=../k8s/files/serviceAccountKey.json -------------------------------------------------------------------------------- /secrets/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=secrets 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "name replace" 15 | sed -i "s/PROJECT_NAME/${GCLOUD_PROJECT}/g" ../k8s/deployment.yaml 16 | 17 | echo "create pod-replicaset" 18 | kubectl apply -f ../k8s/basic-config.yaml 19 | kubectl apply -f ../k8s/deployment.yaml 20 | kubectl apply -f ../k8s/service.yaml -------------------------------------------------------------------------------- /secrets/scripts/startup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=secrets 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "enable services" 15 | gcloud services enable compute.googleapis.com 16 | gcloud services enable container.googleapis.com 17 | 18 | echo "creating container engine cluster" 19 | gcloud container clusters create ${CLUSTER_NAME} \ 20 | --preemptible \ 21 | --zone ${INSTANCE_ZONE} \ 22 | --scopes cloud-platform \ 23 | --enable-autorepair \ 24 | --enable-autoupgrade \ 25 | --enable-autoscaling --min-nodes 1 --max-nodes 4 \ 26 | --num-nodes 3 27 | 28 | echo "confirm cluster is running" 29 | gcloud container clusters list 30 | 31 | echo "get credentials" 32 | gcloud container clusters get-credentials ${CLUSTER_NAME} \ 33 | --zone ${INSTANCE_ZONE} 34 | 35 | echo "confirm connection to cluster" 36 | kubectl cluster-info 37 | 38 | echo "create cluster administrator" 39 | kubectl create clusterrolebinding cluster-admin-binding \ 40 | --clusterrole=cluster-admin --user=$(gcloud config get-value account) 41 | 42 | echo "confirm the pod is running" 43 | kubectl get pods 44 | 45 | echo "list production services" 46 | kubectl get svc 47 | 48 | echo "enable services" 49 | gcloud services enable cloudbuild.googleapis.com 50 | 51 | echo "building containers" 52 | gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} ../ -------------------------------------------------------------------------------- /secrets/scripts/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "preparing..." 4 | export GCLOUD_PROJECT=$(gcloud config get-value project) 5 | export INSTANCE_REGION=us-central1 6 | export INSTANCE_ZONE=us-central1-a 7 | export PROJECT_NAME=secrets 8 | export CLUSTER_NAME=${PROJECT_NAME}-cluster 9 | export CONTAINER_NAME=${PROJECT_NAME}-container 10 | 11 | echo "setup" 12 | gcloud config set compute/zone ${INSTANCE_ZONE} 13 | 14 | echo "remove cluster" 15 | gcloud container clusters delete ${CLUSTER_NAME} --quiet 16 | gcloud container clusters list 17 | 18 | echo "remove container" 19 | gcloud container images delete \ 20 | gcr.io/${GCLOUD_PROJECT}/${CONTAINER_NAME} \ 21 | --force-delete-tags --quiet 22 | 23 | echo "cleanup files" 24 | rm $(helm home) -rf 25 | rm ca.* tiller.* helm.* --------------------------------------------------------------------------------