Skip to content

experimental new App resource kind #138

@jstrachan

Description

@jstrachan

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions