Last active
June 8, 2018 00:36
-
-
Save keatz55/59508a453251fcdfaa1156f41f5ef154 to your computer and use it in GitHub Desktop.
Example K8s Microservice Yamls
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const request = require('request-promise'); | |
const shell = require('shelljs'); | |
const dotenv = require('dotenv'); | |
const minimist = require('minimist'); | |
const yaml = require('js-yaml'); | |
const fs = require('fs'); | |
const semver = require('semver'); | |
const glob = require('glob'); | |
const snakeCase = require('snake-case'); | |
const project = require('../../package.json'); | |
dotenv.config(); | |
const allowedEnvironmentTypes = ['production', 'qa', 'development']; | |
const allowedReleaseTypes = ['stable', 'canary']; | |
const allowedVersionTypes = ['major', 'minor', 'patch']; | |
const defaultHost = 'domain.com'; | |
const hostMap = { | |
production: 'domain.com', | |
development: defaultHost, | |
qa: 'qa.domain.com', | |
}; | |
const replicaMap = { 'production-stable': 2 }; | |
const writeFileAsync = ({ filename, data }) => | |
new Promise((resolve, reject) => { | |
fs.writeFile(filename, data, 'utf8', err => { | |
if (err) return reject(err); | |
return resolve(data); | |
}); | |
}); | |
const globAsync = ({ pattern, options }) => | |
new Promise((resolve, reject) => { | |
glob(pattern, options, (err, files) => { | |
if (err) return reject(err); | |
return resolve(files); | |
}); | |
}); | |
function incrementVersion({ type, latest }) { | |
let [major, minor, patch] = latest.replace('v', '').split('.'); | |
switch (type) { | |
case 'major': | |
major = parseInt(major, 10) + 1; | |
minor = 0; | |
patch = 0; | |
break; | |
case 'minor': | |
minor = parseInt(minor, 10) + 1; | |
patch = 0; | |
break; | |
default: | |
patch = parseInt(patch, 10) + 1; | |
} | |
return `${major}.${minor}.${patch}`; | |
} | |
(async () => { | |
try { | |
console.log('Deployment started...'); | |
const argv = minimist(process.argv.slice(2)); | |
const debug = argv.d || false; | |
// -------------------------------------- | |
// GET ENVIRONMENT TYPE | |
// -------------------------------------- | |
const environmentType = argv.e || argv.environment || 'development'; | |
if (!allowedEnvironmentTypes.includes(environmentType)) { | |
throw new Error( | |
`Sorry, the Environment Type '${environmentType}' was not found.`, | |
); | |
} | |
// -------------------------------------- | |
// GET RELEASE TYPE | |
// -------------------------------------- | |
const releaseType = argv.r || argv.release || 'stable'; | |
if (!allowedReleaseTypes.includes(releaseType)) { | |
throw new Error( | |
`Sorry, the Release Type '${releaseType}' was not found.`, | |
); | |
} | |
// -------------------------------------- | |
// GET VERSION TYPE | |
// -------------------------------------- | |
const versionType = argv.v || argv.version || 'patch'; | |
if (!allowedVersionTypes.includes(versionType)) { | |
throw new Error( | |
`Sorry, the Version Type '${versionType}' was not found.`, | |
); | |
} | |
// -------------------------------------- | |
// START FROM CLEAN STATE | |
// -------------------------------------- | |
shell.exec('rm -rf tmp', { silent: !debug }); | |
// -------------------------------------- | |
// CHECK FOR SHELL DEPENDENCIES | |
// -------------------------------------- | |
if (!shell.which('az')) { | |
throw new Error('This script requires the azure-cli.'); | |
} | |
if (!shell.which('docker')) { | |
throw new Error('This script requires docker-cli.'); | |
} | |
if (!shell.which('kubectl')) { | |
throw new Error('This script requires kubectl-cli.'); | |
} | |
// -------------------------------------- | |
// LOGIN AS SERVICE PRINCIPAL | |
// -------------------------------------- | |
const spId = process.env.AZURE_SERVICE_PRINCIPAL_CLIENT_ID; | |
const spSecret = process.env.AZURE_SERVICE_PRINCIPAL_SECRET; | |
const spTenantId = process.env.AZURE_SERVICE_PRINCIPAL_TENANT_ID; | |
shell.exec( | |
`az login --service-principal --username ${spId} --password ${spSecret} --tenant ${spTenantId}`, | |
{ silent: !debug }, | |
); | |
// -------------------------------------- | |
// EXTRACT NEW IMAGE VERSION | |
// -------------------------------------- | |
const registryName = process.env.AZURE_REGISTRY_NAME; | |
const tagScript = shell.exec( | |
`az acr repository show-tags -n ${registryName} --repository ${ | |
project.name | |
} -o json`, | |
{ silent: !debug }, | |
); | |
let version = incrementVersion({ version: versionType, latest: '0.0.0' }); | |
if (!tagScript.stderr) { | |
const [latestVersion] = JSON.parse(tagScript.stdout).sort( | |
semver.rcompare, | |
); | |
version = incrementVersion({ | |
version: versionType, | |
latest: latestVersion, | |
}); | |
} | |
const imageName = `${registryName}.azurecr.io/${project.name}:${version}`; | |
// -------------------------------------- | |
// BUILD & SHIP NEW DOCKER IMAGE | |
// -------------------------------------- | |
console.log(`Building Image: ${imageName}`); | |
const dockerBuild = shell.exec(`docker build -t ${imageName} .`, { | |
silent: !debug, | |
}); | |
if (dockerBuild.stderr) { | |
console.log(dockerBuild.stdout); | |
throw new Error(dockerBuild.stderr); | |
} | |
console.log(`Pushing Image: ${imageName}`); | |
const dockerPush = shell.exec( | |
`echo ${spSecret} | docker login ${registryName}.azurecr.io -u ${spId} --password-stdin && docker push ${imageName}`, | |
{ silent: !debug }, | |
); | |
if (dockerPush.stderr) { | |
console.log(dockerPush.stdout); | |
throw new Error(dockerPush.stderr); | |
} | |
// -------------------------------------- | |
// FETCH SECRETSs | |
// -------------------------------------- | |
console.log('Getting Secrets'); | |
const availSecrets = JSON.parse( | |
shell.exec('kubectl get secrets -o=json', { silent: !debug }).stdout, | |
); | |
// -------------------------------------- | |
// BUILD YAML SECRETS | |
// -------------------------------------- | |
const secrets = project.k8s.secrets.map(secret => { | |
// console.log('availSecrets: ', JSON.stringify(availSecrets, null, 2)); | |
const [selectedSecret] = availSecrets.items.filter( | |
s => s.metadata.name === secret.key && s.data[secret.value], | |
); | |
// console.log('selectedSecret: ', JSON.stringify(selectedSecret, null, 2)); | |
if (selectedSecret) { | |
return { | |
name: snakeCase(secret.value).toUpperCase(), | |
valueFrom: { | |
secretKeyRef: { | |
name: secret.key, | |
key: secret.value, | |
}, | |
}, | |
}; | |
} | |
throw new Error( | |
`Secret name, '${secret.key}', and Value, ${ | |
secret.value | |
}, not found in response of 'kubectl get secrets -o=json'!`, | |
); | |
}); | |
// -------------------------------------- | |
// FETCH KUBERNETES BASE YAML FILES | |
// -------------------------------------- | |
console.log('Configure K8s YAML Files'); | |
const rand = Math.random(); | |
const [deployment, ingress, service] = await Promise.all([ | |
request( | |
`https://my-company.azureedge.net/k8s-basics/deployment.yaml?v=${rand}`, | |
), | |
request( | |
`https://my-company.azureedge.net/k8s-basics/ingress.yaml?v=${rand}`, | |
), | |
request( | |
`https://my-company.azureedge.net/k8s-basics/service.yaml?v=${rand}`, | |
), | |
]); | |
// -------------------------------------- | |
// CONFIGURE DEPLOYMENT YAML | |
// -------------------------------------- | |
const deploymentObj = yaml.safeLoad(deployment); | |
deploymentObj.metadata.name = `${ | |
project.name | |
}-${environmentType}-${releaseType}-deployment`; | |
deploymentObj.spec.replicas = | |
replicaMap[`${environmentType}-${releaseType}`] || 1; | |
deploymentObj.spec.selector.matchLabels.app = project.name; | |
deploymentObj.spec.selector.matchLabels.release = releaseType; | |
deploymentObj.spec.selector.matchLabels.environment = environmentType; | |
deploymentObj.spec.template.metadata.labels.app = project.name; | |
deploymentObj.spec.template.metadata.labels.release = releaseType; | |
deploymentObj.spec.template.metadata.labels.environment = environmentType; | |
deploymentObj.spec.template.spec.containers = deploymentObj.spec.template.spec.containers.map( | |
container => ({ | |
...container, | |
name: project.name, | |
image: imageName, | |
ports: container.ports.map(port => ({ | |
...port, | |
containerPort: project.k8s.port, | |
})), | |
env: secrets.filter(s => s), | |
}), | |
); | |
const deploymentYaml = yaml.safeDump(deploymentObj); | |
// -------------------------------------- | |
// CONFIGURE SERVICE YAML | |
// -------------------------------------- | |
const serviceObj = yaml.safeLoad(service); | |
serviceObj.metadata.name = `${project.name}-${environmentType}-service`; | |
serviceObj.metadata.labels.app = `${ | |
project.name | |
}-${environmentType}-service`; | |
serviceObj.spec.selector.app = project.name; | |
serviceObj.spec.selector.environment = environmentType; | |
serviceObj.spec.ports = serviceObj.spec.ports.map(port => ({ | |
...port, | |
targetPort: project.k8s.port, | |
})); | |
const serviceYaml = yaml.safeDump(serviceObj); | |
// -------------------------------------- | |
// CONFIGURE INGRESS YAML | |
// -------------------------------------- | |
const ingressObj = yaml.safeLoad(ingress); | |
ingressObj.metadata.name = `${project.name}-${environmentType}-ingress`; | |
ingressObj.metadata.annotations = { | |
'kubernetes.io/ingress.class': 'nginx', | |
'kubernetes.io/tls-acme': 'true', | |
}; | |
if (project.k8s.target) { | |
ingressObj.metadata.annotations['ingress.kubernetes.io/rewrite-target'] = | |
project.k8s.target; | |
} | |
const host = hostMap[environmentType] || defaultHost; | |
const secret = `tls-cert-${environmentType}`; | |
ingressObj.spec.tls = [ | |
{ | |
hosts: [host], | |
secretName: secret, | |
}, | |
]; | |
ingressObj.spec.rules = [ | |
{ | |
host, | |
http: { | |
paths: project.k8s.paths.map(path => ({ | |
path, | |
backend: { | |
serviceName: `${project.name}-${environmentType}-service`, | |
servicePort: 80, | |
}, | |
})), | |
}, | |
}, | |
]; | |
const ingressYaml = yaml.safeDump(ingressObj); | |
// -------------------------------------- | |
// WRITE YAML FILES FOR KUBECTL | |
// -------------------------------------- | |
shell.exec('mkdir tmp', { silent: !debug }); | |
const writePromises = [ | |
writeFileAsync({ | |
filename: `tmp/${ | |
project.name | |
}-${environmentType}-${releaseType}-deployment.yaml`, | |
data: deploymentYaml, | |
}), | |
writeFileAsync({ | |
filename: `tmp/${project.name}-${environmentType}-service.yaml`, | |
data: serviceYaml, | |
}), | |
writeFileAsync({ | |
filename: `tmp/${project.name}-${environmentType}-ingress.yaml`, | |
data: ingressYaml, | |
}), | |
]; | |
await Promise.all(writePromises); | |
// -------------------------------------- | |
// APPLY KUBERNETES YAML FILES | |
// -------------------------------------- | |
const files = await globAsync({ pattern: 'tmp/*.yaml' }); | |
files.forEach(file => { | |
const k8sApply = shell.exec( | |
`kubectl apply -f ${file} --overwrite --record`, | |
{ silent: !debug }, | |
); | |
if (k8sApply.stderr) { | |
console.log(k8sApply.stdout); | |
throw new Error(k8sApply.stderr); | |
} | |
}); | |
// -------------------------------------- | |
// CLEAN UP | |
// -------------------------------------- | |
shell.exec('rm -rf tmp', { silent: !debug }); | |
console.log( | |
` Successfully built & shipped the image '${imageName}', and constructed & applied the following yaml files to K8s: `, | |
files, | |
); | |
} catch (error) { | |
console.log(' ERROR: ', error); | |
} | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
apiVersion: apps/v1beta2 | |
kind: Deployment | |
metadata: | |
name: app-deployment-name-here | |
spec: | |
replicas: 1 | |
selector: | |
matchLabels: | |
app: app-name-here | |
template: | |
metadata: | |
labels: | |
app: app-name-here | |
spec: | |
containers: | |
- name: app-name-here | |
image: image-name-here | |
ports: | |
- containerPort: 3000 | |
resources: | |
requests: | |
cpu: "10m" | |
limits: | |
cpu: "1" | |
memory: "500Mi" | |
imagePullSecrets: | |
- name: wasatch-registry-creds |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
apiVersion: extensions/v1beta1 | |
kind: Ingress | |
metadata: | |
name: app-name-here | |
spec: | |
rules: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
apiVersion: v1 | |
kind: Service | |
metadata: | |
name: app-name-here | |
labels: | |
app: app-name-here | |
spec: | |
type: NodePort | |
ports: | |
- port: 80 | |
targetPort: 3000 | |
protocol: TCP | |
name: http | |
selector: | |
app: app-name-here |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment