Swarmkit: Docker service update via API removes the configuration of ommited keys

Created on 18 Aug 2016  ยท  6Comments  ยท  Source: docker/swarmkit

I create a simple service using the API and setting some information (such as labels, name, env variables...)

When I try to update the service via the api, by sending the following form data, ommited json key (such as labels, environment variables, and even the service name) are resetted.

{
  "TaskTemplate": {
    "ContainerSpec": {
      "Image": "my.serv/myimage:mytag"
    }
  }
}

It seems to be a design choice, since using the docker cli utility, and a docker service update myservice --image my.serv/myimage:mytag sends the full json, containing ulterior labels, name and environment variables.
Why has this choice been made? I can't find any other ulterior related issue.

~/docker
โฏ docker version
Client:
 Version:      1.12.1-rc1
 API version:  1.24
 Go version:   go1.6.3
 Git commit:   7889dc7
 Built:        Fri Aug 12 18:35:53 2016
 OS/Arch:      darwin/amd64
 Experimental: true

Server:
 Version:      1.12.1-rc1
 API version:  1.24
 Go version:   go1.6.3
 Git commit:   7889dc7
 Built:        Fri Aug 12 18:35:53 2016
 OS/Arch:      linux/amd64
 Experimental: true
kinquestion

Most helpful comment

For services, Spec are changed as a unit. The pattern, in pseudo-code, looks like this:

service := getCurrentService()
spec := service.Spec
spec.Image = "my.serv/myimage:mytag"
update(spec, service.Version)

We do this to avoid ambiguity in how the target object should look. For a simple field, like image, this isn't interesting, but when we look at more complex fields, like mounts, patching an object requires a semantic to express whether you are adding, removing or updating entries. Such systems generally also require cross language "Tri-state" field semantics, meaning that you have to be able to discern empty, null and remove. Usually such systems require a separate patch language, with instructions on which fields to modify and how to make insertions, resulting in a complex and error prone API. By having the client express _exactly_ what they want the object to have within it, this problem is completely avoided, for the cost of sending up the whole specification, which is relatively small anyways.

The other component of this approach is the Version field on the specification. By requiring the client to send this, along with the modified specification, we ensure that the change will _only_ apply when made to that version. We see similar provisions in other Consistent-PartitionTolerant systems such as zookeeper and etcd. This ensures that changes from two separate actors don't unintentionally overwrite each other, providing safe concurrency and determinism when operating on the API.

These two design aspects complement each other well and allow API users to get the safety guarantees provided by raft in a relatively simple JSON API.

All 6 comments

I'm not sure why the JSON API was designed this way, but I would guess it was to avoid ambiguity between removing attributes from the service and leaving them unchanged. For example, if you omitted EndpointSpec, would that mean that you want to remove all the port bindings, or that you didn't want to change them?

The JSON API is implemented outside this repository, so you may be able to get a better answer by filing an issue against https://github.com/docker/docker.

ping @stevvooe

For services, Spec are changed as a unit. The pattern, in pseudo-code, looks like this:

service := getCurrentService()
spec := service.Spec
spec.Image = "my.serv/myimage:mytag"
update(spec, service.Version)

We do this to avoid ambiguity in how the target object should look. For a simple field, like image, this isn't interesting, but when we look at more complex fields, like mounts, patching an object requires a semantic to express whether you are adding, removing or updating entries. Such systems generally also require cross language "Tri-state" field semantics, meaning that you have to be able to discern empty, null and remove. Usually such systems require a separate patch language, with instructions on which fields to modify and how to make insertions, resulting in a complex and error prone API. By having the client express _exactly_ what they want the object to have within it, this problem is completely avoided, for the cost of sending up the whole specification, which is relatively small anyways.

The other component of this approach is the Version field on the specification. By requiring the client to send this, along with the modified specification, we ensure that the change will _only_ apply when made to that version. We see similar provisions in other Consistent-PartitionTolerant systems such as zookeeper and etcd. This ensures that changes from two separate actors don't unintentionally overwrite each other, providing safe concurrency and determinism when operating on the API.

These two design aspects complement each other well and allow API users to get the safety guarantees provided by raft in a relatively simple JSON API.

@MagicMicky Does this answer your question well enough? If so, can you close the issue? If not, can you let us know what we need to clarify?

@MagicMicky Let us know and we'll re-open if not.

Yes it absolutely does answer my question. Thank you very much!

Was this page helpful?
0 / 5 - 0 ratings