Created
June 4, 2020 11:07
-
-
Save lawrencejones/35ae5c842deb0d34305a1fb3e48fd2ba to your computer and use it in GitHub Desktop.
Draft gc-app
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
local app = import 'app.libsonnet'; | |
app { | |
_config+:: { | |
release: 'release-bot', | |
namespace: 'connect-team', | |
environment: 'production', | |
app: 'release-bot', | |
image: 'eu.gcr.io/gc-containers/gocardless/release-bot', | |
tag: 'TODO', | |
google_project: 'gc-prd-effc', | |
env: [ | |
{ name: 'PGDATABASE', value: 'release_bot' }, | |
], | |
}, | |
resources: { | |
server_default: | |
$.server.new(containers=[ | |
$.container.new(name='app', command=['./serve']) + | |
$.container.withHealthCheck(path='/health_check', port=8080) + | |
$.container.withPorts( | |
$.container.newPort(8080), | |
), | |
]), | |
}, | |
} |
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
local k = import '../../../kubernetes/vendor/k8s.libsonnet'; | |
{ | |
local cfg = self._config, | |
_config:: { | |
// Common labels that should be applied to all resources. Find more | |
// information at: | |
// | |
// https://github.com/gocardless/anu/wiki/Kubernetes%3a-Labels | |
release: error 'required', | |
namespace: error 'required', | |
app: error 'required', | |
// Unique environment name, used to identify this service via the service | |
// registry | |
environment: error 'required', | |
// Each app has a primary image, usually linked to the git repo in which it | |
// lives. All resources are grouped into a release (set by the above label) | |
// and each type of workload will default to a single container pod, running | |
// code from this image. | |
// | |
// The image tag is provided as an external variable by our deployment | |
// system, or override in the service file if it should be static. | |
image: error 'required', | |
tag: std.extVar('tag') || 'unknown', | |
// This is the GCP project in which this release lives, so the home of the | |
// GKE cluster that hosts it. That differs from the GCP project that hosts | |
// the infrastructure, or what the config connector is configured to manage. | |
// | |
// It might make more sense for the service account to live in the GCP | |
// project that homes its resources, but workload identity enforces the | |
// restriction that you can bind only to accounts in the GKE cluster | |
// project. | |
google_project: error 'required', | |
google_service_account: '%s-%s@%s.iam.gserviceaccount.com' % [ | |
cfg.namespace, | |
cfg.release, | |
cfg.google_project, | |
], | |
// This field is a list of environment variables which will be populated | |
// into the default container configuration created by the default workload | |
// constructors. | |
// | |
// Use kubectl to explain the structure of this list: | |
// $ kubectl explain deployment.spec.template.spec.containers.env | |
env: [ | |
// { | |
// name: 'PGDATABASE', | |
// value: 'database', | |
// }, | |
// { | |
// name: 'PGPASSWORD', | |
// valueFrom: { | |
// secretKeyRef: { | |
// key: 'release-secret', | |
// name: 'key', | |
// }, | |
// }, | |
// }, | |
], | |
}, | |
// Collect common RBAC resource types together, to avoid different resources | |
// using different versions. | |
local clusterRole = k.rbac.v1.clusterRole, | |
local clusterRoleBinding = k.rbac.v1.clusterRoleBinding, | |
local role = k.rbac.v1.role, | |
local roleBinding = k.rbac.v1.roleBinding, | |
local ruleSpec = role.rulesType, | |
local serviceAccount = k.core.v1.serviceAccount, | |
local subject = roleBinding.subjectsType, | |
helpers:: { | |
// Generate ServiceAccount, Role, RoleBinding triple, all sharing the same | |
// name. | |
namespacedRBAC(name, rules):: { | |
service_account: | |
serviceAccount.new(name) + | |
serviceAccount.mixin.metadata.withNamespace(cfg.namespace), | |
role: | |
role.new() + | |
role.mixin.metadata.withName(name) + | |
role.mixin.metadata.withNamespace(cfg.namespace) + | |
role.withRules(rules), | |
role_binding: | |
roleBinding.new() + | |
roleBinding.mixin.metadata.withName(name) + | |
roleBinding.mixin.metadata.withNamespace(cfg.namespace) + | |
roleBinding.mixin.roleRef.withApiGroup('rbac.authorization.k8s.io') + | |
roleBinding.mixin.roleRef.withKind('Role') + | |
roleBinding.mixin.roleRef.withName(name) + | |
roleBinding.withSubjects([ | |
subject.new() + | |
subject.withKind('ServiceAccount') + | |
subject.withName(name) + | |
subject.withNamespace($._config.namespace), | |
]), | |
}, | |
}, | |
// Each release has a default Kubernetes service account, bound to a Google | |
// service account (if required). The service account is bound to a role that | |
// has no permissions. | |
// | |
// Extending the release Kubernetes service account with additional | |
// permissions is possible by mixing-in additional rules: | |
// | |
// app { | |
// identity+: { | |
// role+: role.withRuleMixin([ | |
// -- additional rules go here | |
// ]), | |
// }, | |
// } | |
// | |
identity: $.helpers.namespacedRBAC(cfg.release, []) { | |
service_account+: | |
serviceAccount.mixin.metadata.withAnnotationsMixin({ | |
'iam.gke.io/gcp-service-account': cfg.google_service_account, | |
}), | |
}, | |
// As with RBAC, collect common workload resources together, ensuring we use | |
// consistent versions. | |
local deployment = k.apps.v1.deployment, | |
local podTemplate = deployment.mixin.spec.templateType, | |
local container = deployment.mixin.spec.template.spec.containersType, | |
local containerPort = container.portsType, | |
container:: container { | |
new(name, command, env=cfg.env, ports=[], probe=null):: | |
container.new(name) + | |
container.withImage('%s:%s' % [cfg.image, cfg.tag]) + | |
container.withEnv(env) + | |
container.withCommand(command) + | |
container.withPorts(ports), | |
// Create a container port suitable for entering into | |
// $ kubectl explain pod.spec.containers.ports | |
newPort(containerPort, name='metrics', protocol='TCP'):: | |
{ | |
name: name, | |
protocol: protocol, | |
containerPort: containerPort, | |
}, | |
// Configure a health check on the liveness and readiness probes. Readiness | |
// is set to be the same as the liveness check, unless specific overrides | |
// are configured. | |
withHealthCheck(path='/health_check', port=8080, readyPath=path, readyPort=port):: | |
container.mixin.livenessProbe.httpGet.withPath(path) + | |
container.mixin.livenessProbe.httpGet.withPort(port) + | |
container.mixin.readinessProbe.httpGet.withPath(path) + | |
container.mixin.readinessProbe.httpGet.withPort(port), | |
}, | |
// Used to model the default workload type. Concrete types should extend this | |
// with a custom new() method. | |
workload:: { | |
new(name, containers, replicas, role):: | |
local labels = { | |
app: cfg.app, | |
release: cfg.release, | |
version: cfg.tag, | |
role: role, | |
component: name, | |
}; | |
super.new() + | |
self.mixin.metadata.withName('%s-%s' % [role, name]) + | |
self.mixin.metadata.withNamespace(cfg.namespace) + | |
// Apply the same labels to both the kind (deployment, statefulset, | |
// whatever) as we use for the selector. Labels used in a selector can't | |
// change, so put metadata in annotations if necessary. | |
self.mixin.metadata.withLabels(labels) + | |
self.mixin.spec.selector.withMatchLabels(labels) + | |
self.mixin.spec.template.metadata.withLabels(labels) + | |
// Each workload needs replicas- default to 1, so we run everything by | |
// default | |
self.mixin.spec.withReplicas(replicas) + | |
// TODO: Disable auto-mount by default, in favour of a mixin that uses | |
// projected volume mounts to provide a Kubernetes service account. This | |
// would help opt-in containers that want Kubernetes connectivity, while | |
// keeping sidecars/untrusted code from exercising those paths. | |
self.mixin.spec.template.spec.withServiceAccountName(cfg.release) + | |
self.mixin.spec.template.spec.withAutomountServiceAccountToken(true) + | |
self.mixin.spec.template.spec.withContainers(containers), | |
}, | |
// Workloads that provide a network service of some type, whether an HTTP API | |
// or some type of RPC server. Previously called backends. | |
server:: deployment + self.workload { | |
new(name='default', ports=(error 'servers must have ports'), containers=[], replicas=1):: | |
super.new(name=name, containers=containers, replicas=replicas, role='server'), | |
}, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment