Skip to content

Instantly share code, notes, and snippets.

@keatz55
Last active June 8, 2018 00:36
Show Gist options
  • Save keatz55/59508a453251fcdfaa1156f41f5ef154 to your computer and use it in GitHub Desktop.
Save keatz55/59508a453251fcdfaa1156f41f5ef154 to your computer and use it in GitHub Desktop.
Example K8s Microservice Yamls
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);
}
})();
---
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
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: app-name-here
spec:
rules:
---
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