So I've noodled the opencompose examples and I love their concise nature. What worries me is leaky abstractions and keeping in sync with all the detail in kubernetes and keeping in sync with the kubernetes resources which keep changing over time. e.g. from a quick look at the issue tracker there's already a fair few gaps: #134, #130, #128, #126, #125, #124, #123, #122, #120, #114, #101, #93, #91, #90, #85, #81, #24
As a thought experiment I had a little play with adding something opencompose-ish to the fabric8-maven-plugin. Here's a link to the issue with some example markup and what it generates along with the design rational.
Basically I tried to adding the smallest possible new resource kind while trying to be nice and concise but letting the real kubernetes structs shine through so that there's no leaky abstraction and minimal learning required for kubernetes savvy folks. It turns out I've a fix for these issues too ;) #134, #130, #128, #126, #125, #124, #123, #122, #120, #114, #101, #93, #91, #90, #85, #81, #24
Basically I:
- created a new resource kind called App its currently Java code but a go lang equivalent would be equally trivial to do as well
- the
App is-a PodSpec which solves lots of those issues referenced above for defining pods
- added the design time metadata properties like
name, labels, annotations etc
- added the properties from
DeploymentSpec other than the template which mostly just is the PodSpec anyways
- added a
service property for defining a service for the app (which uses ServiceSpec)
- added an optional ConfigMap data for easy adding of configuration data to an app
- added optional extra PVCs we could do the same with emptyDirs too I guess - its easy to also mount external volumes/secrets/configmaps already with the current
App as its a PodSpec already; the persistentVolumes property is more for defining the actual PVCs that are owned by this App
- given the closeness to the existing kubernetes structs its pretty trivial to write a faithful generator that generates the full kubernetes resources for a given
App
Example
here's a sample app.yml file using the App resource:
---
name: "cheese"
labels:
app: "cheese"
annotations:
com.acme: "foo"
replicas: 1
containers:
- image: "foo/bar:123"
env:
- name: "MY_ENV"
value: "CHEESE"
volumeMounts:
- mountPath: "/app/logs"
name: "log-data"
- mountPath: "/app/appstuff"
name: "app-data"
service:
ports:
- port: 80
targetPort: 8080
configData:
application.properties: "foo.bar = abcd"
persistentVolumes:
log-data:
accessModes:
- "ReadWriteOnce"
resources:
limits:
storage: "1Gi"
app-data:
accessModes:
- "ReadWriteMany"
resources:
limits:
storage: "5Gi"
Generated these resources:
---
apiVersion: "v1"
kind: "List"
items:
- apiVersion: "v1"
kind: "Service"
metadata:
annotations:
com.acme: "foo"
finalizers: []
labels:
app: "cheese"
name: "cheese"
ownerReferences: []
spec:
deprecatedPublicIPs: []
externalIPs: []
loadBalancerSourceRanges: []
ports:
- port: 80
targetPort: 8080
selector:
app: "cheese"
- apiVersion: "v1"
kind: "PersistentVolumeClaim"
metadata:
annotations:
com.acme: "foo"
finalizers: []
labels:
app: "cheese"
name: "app-data"
ownerReferences: []
spec:
accessModes:
- "ReadWriteMany"
resources:
limits:
storage: "5Gi"
requests: {}
- apiVersion: "v1"
kind: "PersistentVolumeClaim"
metadata:
annotations:
com.acme: "foo"
finalizers: []
labels:
app: "cheese"
name: "log-data"
ownerReferences: []
spec:
accessModes:
- "ReadWriteOnce"
resources:
limits:
storage: "1Gi"
requests: {}
- apiVersion: "v1"
kind: "ConfigMap"
metadata:
annotations:
com.acme: "foo"
finalizers: []
labels:
app: "cheese"
name: "cheese"
ownerReferences: []
data:
application.properties: "foo.bar = abcd"
- apiVersion: "extensions/v1beta1"
kind: "Deployment"
metadata:
annotations:
com.acme: "foo"
finalizers: []
labels:
app: "cheese"
name: "cheese"
ownerReferences: []
spec:
paused: false
replicas: 1
selector:
matchExpressions: []
matchLabels:
app: "cheese"
template:
metadata:
annotations:
com.acme: "foo"
finalizers: []
labels:
app: "cheese"
name: "cheese"
ownerReferences: []
spec:
containers:
- args: []
command: []
env:
- name: "MY_ENV"
value: "CHEESE"
image: "foo/bar:123"
ports: []
volumeMounts:
- mountPath: "/app/logs"
name: "log-data"
- mountPath: "/app/appstuff"
name: "app-data"
imagePullSecrets: []
nodeSelector: {}
volumes:
- name: "log-data"
persistentVolumeClaim:
claimName: "log-data"
- name: "app-data"
persistentVolumeClaim:
claimName: "app-data"
Comparing App to opencompose
Whats interesting is its very similar really to opencompose in feel, conciseness and markup. The main difference is the header part; opencompose looks more like docker compose at the top (the version: and services:).
We already have kcompose for migrating docker compose to kubernetes so I'd rather opencompose focus on whats best for kubernetes; using its conventions.
Though it'd be trivial to define an AppList type resource to be a collection of Apps and be more like opencompose; though the kubernetes default for lists of structs is not to use maps like:
services:
foo:
cheese: edam
but to use a list
kind: AppList
items:
- name: foo
cheese: edam
so if I were re-inventing the 2.0 of opencompose I'd probably ditch this docker compose convention anyways?
Other than that things are fairly similar really; the main difference is the focus on reusing the Spec structs from kubernetes and composing them together in a concise way rather than inventing a brand new schema.
Interestingly on the services: versus AppList front; with the focus on microservices (one app that does one thing well), I think its going to be easiest for developers to have an app YAML file for each app they work on. Typically the default case will be 1 app.yml per microservice (i.e. per git repo) anyway.
A typical docker compose example often shows a front end and a database; which is great for demos. However in the real world usually the database is owned by one team and provisioned and upgraded on a totally different schedule to the front end app anyway; in which case having 2 separate app.yml files for them makes loads more sense really.
Plus developers find it hard to grok large nested yaml files anyway ;) so I do actually prefer the default of keeping apps in different files.
Closing thoughts
Note that this was just a thought experiment really. I was really just thinking at the time of Java folks and reducing their typing when hacking YAML in some validating/completing IDE. I'm still mulling over how this may or may not affect opencompose really.
One interesting further thought is with the new API Groups in kubernetes we can add new schemas to kubernetes along with controllers/operators that work on them. So we could create a new App resource (maybe even upstream it to kubernetes itself - or worst case make an API Group and controller for it) so that folks could use kubectl directly:
$ kubectl get apps
$ kubectl apply -f myapp.yml
etc which then whenever an App is created/updated/deleted it runs the canonical generator to make the resulting Deployment, Service, ConfigMap, PVCs etc and apply those chanages.
i.e. then folks could work at this App abstraction as a simpler way of working with the actual underlying kubernetes abstractions.
If we did go in this direction though, we'd maybe wanna make a minor change to App and move the metadata fields out, rename App to AppSpec and then add a new App class/struct containingMetadata and AppSpec so that it follows the conventions of all the other kubernetes resources. Then we'd add AppList and then the kubectl / REST API would be consistent.
So I've noodled the opencompose examples and I love their concise nature. What worries me is leaky abstractions and keeping in sync with all the detail in kubernetes and keeping in sync with the kubernetes resources which keep changing over time. e.g. from a quick look at the issue tracker there's already a fair few gaps: #134, #130, #128, #126, #125, #124, #123, #122, #120, #114, #101, #93, #91, #90, #85, #81, #24
As a thought experiment I had a little play with adding something opencompose-ish to the fabric8-maven-plugin. Here's a link to the issue with some example markup and what it generates along with the design rational.
Basically I tried to adding the smallest possible new resource kind while trying to be nice and concise but letting the real kubernetes structs shine through so that there's no leaky abstraction and minimal learning required for kubernetes savvy folks. It turns out I've a fix for these issues too ;) #134, #130, #128, #126, #125, #124, #123, #122, #120, #114, #101, #93, #91, #90, #85, #81, #24
Basically I:
Appis-aPodSpecwhich solves lots of those issues referenced above for defining podsname,labels,annotationsetcDeploymentSpecother than thetemplatewhich mostly just is thePodSpecanywaysserviceproperty for defining a service for the app (which usesServiceSpec)Appas its aPodSpecalready; thepersistentVolumesproperty is more for defining the actual PVCs that are owned by thisAppAppExample
here's a sample
app.ymlfile using theAppresource:Generated these resources:
Comparing
Appto opencomposeWhats interesting is its very similar really to opencompose in feel, conciseness and markup. The main difference is the header part; opencompose looks more like docker compose at the top (the
version:andservices:).We already have kcompose for migrating docker compose to kubernetes so I'd rather opencompose focus on whats best for kubernetes; using its conventions.
Though it'd be trivial to define an
AppListtype resource to be a collection ofAppsand be more like opencompose; though the kubernetes default for lists of structs is not to use maps like:but to use a list
so if I were re-inventing the 2.0 of opencompose I'd probably ditch this docker compose convention anyways?
Other than that things are fairly similar really; the main difference is the focus on reusing the
Specstructs from kubernetes and composing them together in a concise way rather than inventing a brand new schema.Interestingly on the
services:versusAppListfront; with the focus on microservices (one app that does one thing well), I think its going to be easiest for developers to have an app YAML file for each app they work on. Typically the default case will be 1 app.yml per microservice (i.e. per git repo) anyway.A typical docker compose example often shows a front end and a database; which is great for demos. However in the real world usually the database is owned by one team and provisioned and upgraded on a totally different schedule to the front end app anyway; in which case having 2 separate app.yml files for them makes loads more sense really.
Plus developers find it hard to grok large nested yaml files anyway ;) so I do actually prefer the default of keeping apps in different files.
Closing thoughts
Note that this was just a thought experiment really. I was really just thinking at the time of Java folks and reducing their typing when hacking YAML in some validating/completing IDE. I'm still mulling over how this may or may not affect opencompose really.
One interesting further thought is with the new API Groups in kubernetes we can add new schemas to kubernetes along with controllers/operators that work on them. So we could create a new
Appresource (maybe even upstream it to kubernetes itself - or worst case make an API Group and controller for it) so that folks could use kubectl directly:etc which then whenever an App is created/updated/deleted it runs the canonical generator to make the resulting
Deployment,Service,ConfigMap,PVCsetc and apply those chanages.i.e. then folks could work at this
Appabstraction as a simpler way of working with the actual underlying kubernetes abstractions.If we did go in this direction though, we'd maybe wanna make a minor change to
Appand move the metadata fields out, renameApptoAppSpecand then add a newAppclass/struct containingMetadataandAppSpecso that it follows the conventions of all the other kubernetes resources. Then we'd addAppListand then the kubectl / REST API would be consistent.