docker深入2-API示例

2018/1/19


一、目的
1、前言
演示 http API 如何使用,作为入门指引,或许能帮助到人,因为 docker 的 API 文档是自动生成的(看起来是用的 Swagger 有待后续深入研究),我比较笨,没找到太多有用的示例,在自己探索的路上毕竟浪费了点时间,本文若能节约你的时间,是我的幸运。

首先,当然是建议先看官方文档 Docker Engine API and SDKs
https://docs.docker.com/engine/api/

有3个类型的示例:go, python, curl

如果熟悉 go 或者 python 的童鞋,要使用 SDK 来达到自己的目的,建议结合 API 文档看 json output 的字段是否有你需要的,然后去获取它。
https://docs.docker.com/engine/api/v1.30


go 的示例:
https://github.com/opera443399/ops/blob/master/doc/Go/src/abc.com/demo/demoDockerAPI/app.go



题外话:如果要完成任务,其实可以考虑最简单的 API 调用,即利用 docker client 来做(而不是自己去写 shell/go/python 来达到目的):
~] docker -H your_remote_docker_node_ip version



注1:本次实例是在 docker swarm mode 下使用的,目的是:更新指定服务的镜像。
注2:要在 swarm manager node 上执行。


2、版本
~]# docker version
Client:
 Version:      17.06.0-ce
 API version:  1.30
 Go version:   go1.8.3
 Git commit:   02c1d87
 Built:        Fri Jun 23 21:20:36 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.06.0-ce
 API version:  1.30 (minimum version 1.12)
 Go version:   go1.8.3
 Git commit:   02c1d87
 Built:        Fri Jun 23 21:21:56 2017
 OS/Arch:      linux/amd64
 Experimental: false



二、实例
1、创建一个服务
docker service create --name t001 --publish 22222:80 --detach=true opera443399/whoami:0.9


2、更新服务
1) 目标
service_image_latest="opera443399/whoami:0.7"
service_name="t001"

2) 获取当前服务的版本            
service_version_index=$(curl -s -g \
    --unix-socket /var/run/docker.sock \
    http:/v1.30/services?filters='{"name":["'${service_name}'"]}' \
    |jq '.[].Version.Index')


3) 执行更新
curl -s \
    --unix-socket /var/run/docker.sock \
    "http:/v1.30/services/${service_name}/update?version=${service_version_index}" \
    -X POST \
    -H "Content-Type: application/json" \
    -d "
    {
        \"Name\": \"${service_name}\",
        \"TaskTemplate\": {
            \"ContainerSpec\": {
                \"Image\": \"${service_image_latest}\"
            }
        }
    }
    " |jq '.'
    

4) 查看服务现状
curl -s -g \
    --unix-socket /var/run/docker.sock \
    http:/services?filters='{"name":["'${service_name}'"]}' \
    |jq '.'
   


   
三、问题
1、如果创建 service 时,使用自定义的网络,怎么办?
状态:未解决

1)如果直接使用上述方案,则报错如下
{
  "message": "rpc error: code = 12 desc = networks must be migrated to TaskSpec before being changed"
}

2)注意下述关于 Networks 的注释

引用自:https://github.com/moby/moby/blob/master/api/types/swarm/service.go

// ServiceSpec represents the spec of a service.
type ServiceSpec struct {
	Annotations

	// TaskTemplate defines how the service should construct new tasks when
	// orchestrating this service.
	TaskTemplate   TaskSpec      `json:",omitempty"`
	Mode           ServiceMode   `json:",omitempty"`
	UpdateConfig   *UpdateConfig `json:",omitempty"`
	RollbackConfig *UpdateConfig `json:",omitempty"`

	// Networks field in ServiceSpec is deprecated. The
	// same field in TaskSpec should be used instead.
	// This field will be removed in a future release.
	Networks     []NetworkAttachmentConfig `json:",omitempty"`
	EndpointSpec *EndpointSpec             `json:",omitempty"`
}


引用自:https://github.com/moby/moby/blob/master/api/types/swarm/task.go
// TaskSpec represents the spec of a task.
type TaskSpec struct {
	// ContainerSpec and PluginSpec are mutually exclusive.
	// PluginSpec will only be used when the `Runtime` field is set to `plugin`
	ContainerSpec *ContainerSpec      `json:",omitempty"`
	PluginSpec    *runtime.PluginSpec `json:",omitempty"`

	Resources     *ResourceRequirements     `json:",omitempty"`
	RestartPolicy *RestartPolicy            `json:",omitempty"`
	Placement     *Placement                `json:",omitempty"`
	Networks      []NetworkAttachmentConfig `json:",omitempty"`

	// LogDriver specifies the LogDriver to use for tasks created from this
	// spec. If not present, the one on cluster default on swarm.Spec will be
	// used, finally falling back to the engine default if not specified.
	LogDriver *Driver `json:",omitempty"`

	// ForceUpdate is a counter that triggers an update even if no relevant
	// parameters have been changed.
	ForceUpdate uint64

	Runtime RuntimeType `json:",omitempty"`
}


3)尝试过多种在 API 中增加网络相关的配置,均未找到合理的方法
示例:
TaskTemplate.Networks

    {
        \"Name\": \"${service_name}\",
        \"TaskTemplate\": {
            \"ContainerSpec\": {
                \"Image\": \"${service_image_latest}\"
            },
            \"Networks\": [
                {
                    \"Target\": \"xxx\"
                }
            ]
        }
    }


注:执行有风险,效果是:
ingress网络消失,该 service 对外发布的端口消失。

因为,创建 service 时:
docker service create --name t001 --network t001only --publish 22222:80 --detach=true opera443399/whoami:0.7

使用 --network 将关联到一个网络 t001only
使用 --publish 将关联到一个网络 ingress

因此,实际上有2个网络。
注1:在反复测试的过程中,出现一个奇怪的现象,,创建 service 时,,容器处于 new 的状态,无法上线,暂时还未找到原因,因而中止了测试。
注2:或许可以尝试在更新 service 的过程中,指定 ip 和 port 等信息,待后续测试后再更新本段信息。


相关报错:
~]# journalctl -u docker -a -S "2017-09-18 14:00" |sed -r "s/(.*)msg=(.*)/\2/g" |grep rpc |sort |uniq
"Error updating service t001: rpc error: code = 12 desc = networks must be migrated to TaskSpec before being changed"
"Error creating service t001: rpc error: code = 4 desc = context deadline exceeded"
"Error removing service t001: rpc error: code = 4 desc = context deadline exceeded"
"Error updating service t001: rpc error: code = 2 desc = update out of sequence"

"Handler for POST /v1.30/services/t001/update returned error: rpc error: code = 12 desc = networks must be migrated to TaskSpec before being changed"
"Handler for POST /services/create returned error: rpc error: code = 4 desc = context deadline exceeded"
"Handler for DELETE /v1.30/services/t001 returned error: rpc error: code = 4 desc = context deadline exceeded"
"Handler for POST /v1.30/services/t001/update returned error: rpc error: code = 2 desc = update out of sequence"



2、昨天的测试后,出现创建的 service 无法正常运行的错误追踪
从头开始,继续排错。

1)创建一个 global 类型的服务
~]# docker service create --name t002 --publish 10008:80 --mode global --detach=true nginx:latest

2)查看服务状态
~]# docker service ls -f 'name=t002'
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
5a8nps5pssho        t002                global              0/4                 nginx:latest        *:10008->80/tcp
~]# docker service ps t002
ID              NAME                    IMAGE           NODE                DESIRED STATE       CURRENT STATE        ERROR   PORTS
4vr4ehhj17l3    t002.node1_id_here      nginx:latest    node1.test.com      Running             New 21 minutes ago                       
0k2ttjveis6t    t002.node2_id_here      nginx:latest    node2.test.com      Running             New 21 minutes ago                       
wwopgjy8pfxj    t002.node3_id_here      nginx:latest    node3.test.com      Running             New 21 minutes ago                                     

果然,没有端口映射,服务还没上线:
~]# docker service inspect --pretty t002

ID:		5a8nps5psshoemixl8w6iygss
Name:		t002
Service Mode:	Global
Placement:
UpdateConfig:
 Parallelism:	1
 On failure:	pause
 Monitoring Period: 5s
 Max failure ratio: 0
 Update order:      stop-first
RollbackConfig:
 Parallelism:	1
 On failure:	pause
 Monitoring Period: 5s
 Max failure ratio: 0
 Rollback order:    stop-first
ContainerSpec:
 Image:		nginx:latest
Resources:
Endpoint Mode:	vip

查看 task 信息:
curl -s --unix-socket /var/run/docker.sock http:/tasks |jq '.' >tmp.1
  {
    "ID": "wwopgjy8pfxj4baa3jrn438rm",
    "Version": {
      "Index": 5070352
    },
    "CreatedAt": "2017-09-19T03:22:07.779333332Z",
    "UpdatedAt": "2017-09-19T03:22:07.779333332Z",
    "Labels": {},
    "Spec": {
      "ContainerSpec": {
        "Image": "nginx:latest"
      },
      "Placement": {},
      "ForceUpdate": 0
    },
    "ServiceID": "5a8nps5psshoemixl8w6iygss",
    "NodeID": "node2_id_here",
    "Status": {
      "Timestamp": "2017-09-19T03:22:07.77932103Z",
      "State": "new",
      "Message": "created",
      "ContainerStatus": {},
      "PortStatus": {}
    },
    "DesiredState": "running"
  }

该 task 的状态不符合预期,最终 service 启动失败。


3)日志
~]# journalctl -u docker -a -S "2017-09-19 11:00" |sed -r "s/(.*)msg=(.*)/\2/g" |sort |uniq |grep -vE "(Logs begin|taskmanager|no pull access|Node join event|for pull|authentication|raft)"
"Could not parse VIP address  while releasing"
"error deallocating vip" error="invalid CIDR address: " vip.addr= vip.network=fjvkitgqzewmtujakidzo38pk
"Failed allocation for service 5a8nps5psshoemixl8w6iygss" error="could not find an available IP while allocating VIP" module=node node.id=node2_id_here
"task allocation failure" error="service 5a8nps5psshoemixl8w6iygss to which this task 0k2ttjveis6tliuxqyuxqyp97 belongs has pending allocations" module=node node.id=node2_id_here
"task allocation failure" error="service 5a8nps5psshoemixl8w6iygss to which this task 4vr4ehhj17l3f768ksc14w6rz belongs has pending allocations" module=node node.id=node2_id_here
"task allocation failure" error="service 5a8nps5psshoemixl8w6iygss to which this task wwopgjy8pfxj4baa3jrn438rm belongs has pending allocations" module=node node.id=node2_id_here



初步看起来,似乎是网络方面的问题,没有可用的 VIP 了?神奇,,待查证。
截止到目前,并未找到原因,为了节约时间,只能采取重启 docker 服务后,恢复集群到正常的状态,最后调用前文提及的 docker client 通过远程 API 来执行的方式完成任务先,后续有空再尝试重放现场。





    
    
ZYXW、参考
1、API
https://docs.docker.com/engine/api/
https://docs.docker.com/engine/api/v1.30

2、moby src
https://github.com/moby/moby/blob/master/api/types/swarm/task.go
https://github.com/moby/moby/blob/master/api/types/swarm/service.go

3、portainer src
https://github.com/portainer/portainer/blob/04ea81e7cd8401690058c4b4264452bf9d7a05eb/app/components/service/serviceController.js