Published on

Managing Traefik Configuration with Consul on Docker Swarm

Authors

Today we will Setup Consul with Traefik on Docker Swarm

Resources:

Create Consul in the Swarm:

Investigate using Consul with Traefik in Docker Swarm.

I have configured consul with constraints to be placed only on my one manager, as I was bind mounting the data directory to /mnt, as this was for testing on a small docker swarm cluster, but with Docker for AWS, we will use cloudstor with EFS, or GlusterFS, NFS for data persistency across any nodes.

Create Compose Files:

  • consul.yml
  • traefik.yml
  • apps.yml

consul.yml

$ cat > consul.yml << EOF
version: '3.3'

services:
  consul:
    image: progrium/consul
    command: -server -bootstrap -log-level debug -ui-dir /ui
    networks:
      - appnet
    deploy:
      placement:
        constraints: [node.role == manager]
    volumes:
      - type: bind
        source: /mnt/consul
        target: /data
    ports:
      - "8400:8400"
      - "8500:8500"
      - "8600:53/udp"

networks:
  appnet:
    external: true

EOF

traefik.yml

$ cat > traefik.yml << EOF
version: '3.3'

services:
  traefik:
    image: traefik
    networks:
      - appnet
    command: --consul --consul.endpoint=consul:8500
    ports:
      - "80:80"
      - "8080:8080"

networks:
  appnet:
    external: true

EOF

apps.yml

$ cat > apps.yml << EOF
version: '3.3'

services:
  whoami1:
    image: emilevauge/whoami
    networks:
      - appnet

  whoami2:
    image: emilevauge/whoami
    networks:
      - appnet

  whoami3:
    image: emilevauge/whoami
    networks:
      - appnet

  whoami4:
    image: emilevauge/whoami
    networks:
      - appnet

  whoami5:
    image: rbekker87/flask-containername
    networks:
      - appnet

  whoami6:
    image: rbekker87/flask-containername
    networks:
      - appnet

networks:
  appnet:
    external: true

EOF

Create Overlay Network and Deploy Stacks

Create the overlay network, and deploy the 3 stacks:

$ docker network create --driver=overlay appnet
$ docker stack deploy --compose-file consul.yml kvstore
$ docker stack deploy --compose-file traefik.yml proxy
$ docker stack deploy --compose-file apps.yml apps

Populate Configs and Push to Consul KV Store:

Config for Traefik:

$ cat > create_traefik_config.sh << EOF
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/loglevel -d 'DEBUG'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/defaultentrypoints/0 -d 'http'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/entrypoints/http/address -d ':80'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/consul/endpoint -d 'consul:8500'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/consul/watch -d 'true'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/consul/prefix -d 'traefik'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/web/address -d ':8081'
EOF

Config for WhoAmI Web Apps:

$ cat > create_whoami_config.sh << EOF
# backend-1
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend1/circuitbreaker/expression -d 'NetworkErrorRatio() > 0.5'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend1/servers/server1/url -d 'http://whoami1:80'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend1/servers/server1/weight -d '10'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend1/servers/server2/url -d 'http://whoami2:80'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend1/servers/server2/weight -d '1'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend1/servers/server2/tags -d 'api,helloworld'

# backend-2
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend2/maxconn/amount -d '10'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend2/maxconn/extractorfunc -d 'request.host'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend2/loadbalancer/method -d 'drr'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend2/servers/server1/url -d 'http://whoami3:80'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend2/servers/server1/weight -d '1'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend2/servers/server2/url -d 'http://whoami4:80'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend2/servers/server2/weight -d '2'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend2/servers/server2/tags -d 'web'

# frontend-1
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/frontends/frontend1/backend -d 'backend2'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/frontends/frontend1/routes/test_1/rule -d 'Host:test.localhost'

# frontend-2
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/frontends/frontend2/backend -d 'backend1'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/frontends/frontend2/passHostHeader -d 'true'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/frontends/frontend2/priority -d '10'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/frontends/frontend2/entrypoints -d 'http'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/frontends/frontend2/routes/test_2/rule -d 'PathPrefix:/test'
EOF

Config for Flask Container Name Web Apps:

$ cat > create_flask_config.sh << EOF
# backends
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend3/amount -d '5'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend3/maxconn/extractorfunc -d 'request.host'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend3/loadbalancer/method -d 'drr'

curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend3/servers/server1/url -d 'http://whoami5:5000'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend3/servers/server1/weight -d '1'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend3/servers/server1/tags -d 'flask'

curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend3/servers/server2/url -d 'http://whoami6:5000'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend3/servers/server2/weight -d '2'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/backends/backend3/servers/server2/tags -d 'flask'

# frontend:
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/frontends/frontend3/backend -d 'backend3'
curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/frontends/frontend3/routes/test_1/rule -d 'Host:flask.localhost'
EOF

Push Configs to Consul:

$ sh create_traefik_config.sh
$ sh create_whoami_config.sh
$ sh create_flask_config.sh

Testing Applications:

Test Frontend with Host Header: test.localhost

$ curl -H "Host:test.localhost" http://127.0.0.1:80
Hostname: 88c29de3aeb0
IP: 127.0.0.1
IP: 10.0.0.6
IP: 10.0.0.7
IP: 172.18.0.3
GET / HTTP/1.1
Host: test.localhost
User-Agent: curl/7.47.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 10.255.0.2
X-Forwarded-Host: test.localhost
X-Forwarded-Proto: http
X-Forwarded-Server: 2f827d04fbfb

Test Frontend with PathPrefix: /test

$ curl http://127.0.0.1:80/test
Hostname: 14bd4dc0ab00
IP: 127.0.0.1
IP: 10.0.0.12
IP: 10.0.0.13
IP: 172.18.0.4
GET /test HTTP/1.1
Host: 127.0.0.1
User-Agent: curl/7.47.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 10.255.0.2
X-Forwarded-Host: 127.0.0.1
X-Forwarded-Proto: http
X-Forwarded-Server: 2f827d04fbfb

Test with failure expected:

$ curl http://127.0.0.1:80
404 page not found

$ curl -H "Host:foo.localhost" http://127.0.0.1:80
404 page not found

Change the frontend rule to foo.localhost and test again:

$ curl -XPUT http://127.0.0.1:8500/v1/kv/traefik/frontends/frontend1/routes/test_1/rule -d 'Host:foo.localhost'

Testing with foo.localhost :

$ curl -H "Host:foo.localhost" http://127.0.0.1:80
Hostname: 88c29de3aeb0
IP: 127.0.0.1
IP: 10.0.0.6
IP: 10.0.0.7
IP: 172.18.0.3
GET / HTTP/1.1
Host: test.localhost
User-Agent: curl/7.47.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 10.255.0.2
X-Forwarded-Host: foo.localhost
X-Forwarded-Proto: http
X-Forwarded-Server: 2f827d04fbfb

Test Flask Web Apps, with RoundRobin + Weight:

$ curl -H "Host:flask.localhost" http://127.0.0.1:80
Container Hostname: c94e41420ec7 , UUID: 8d536277-1041-4140-b28a-91630d69ab15

$ curl -H "Host:flask.localhost" http://127.0.0.1:80
Container Hostname: 786a3b15a32e , UUID: 50bb435f-dac3-4cb0-8ecf-250f13a4d7a5

$ curl -H "Host:flask.localhost" http://127.0.0.1:80
Container Hostname: c94e41420ec7 , UUID: 680ca88c-8048-4044-a31b-1e922545dc8c

$ curl -H "Host:flask.localhost" http://127.0.0.1:80
Container Hostname: c94e41420ec7 , UUID: 5506792c-04e5-4517-b58e-fad57b5d1da5

$ curl -H "Host:flask.localhost" http://127.0.0.1:80
Container Hostname: 786a3b15a32e , UUID: 930056e5-4667-4c2c-976b-246cf891a351

$ curl -H "Host:flask.localhost" http://127.0.0.1:80
Container Hostname: c94e41420ec7 , UUID: 7c757b50-0e3a-47e2-8636-ae0583802afb

$ curl -H "Host:flask.localhost" http://127.0.0.1:80
Container Hostname: c94e41420ec7 , UUID: 918bf337-1476-480c-aacb-72fc89392c45

$ curl -H "Host:flask.localhost" http://127.0.0.1:80
Container Hostname: 786a3b15a32e , UUID: 5c2c6a0e-f2ea-4d5f-a6ce-a7df2ef4886b

Data Persistent Test:

List the Stacks:

$ docker stack ls
NAME                SERVICES
apps                6
kvstore             1
proxy               1

Kill the Consul Container to ensure data is persistent:

$ docker kill $(docker ps -f name=consul -q)

Verify that the replica has been fulfilled to its desired state:

$ docker stack ps kvstore
ID                  NAME                   IMAGE                    NODE                DESIRED STATE       CURRENT STATE                ERROR                         PORTS
j80kxxei6lyx        kvstore_consul.1       progrium/consul:latest   ip-10-1-4-51        Running             Running about a minute ago
lsvww0z8g24c         \_ kvstore_consul.1   progrium/consul:latest   ip-10-1-4-51        Shutdown            Failed about a minute ago    "task: non-zero exit (137)"
amkmsfodslwk         \_ kvstore_consul.1   progrium/consul:latest   ip-10-1-4-51        Shutdown            Shutdown 33 minutes ago

Read the Value of the Backend3 URL Key:

$ curl -XGET http://127.0.0.1:8500/v1/kv/traefik/backends/backend3/servers/server1/url?raw
http://whoami5:5000

Test The Service:

$ curl -H "Host:flask.localhost" http://127.0.0.1:80
Container Hostname: 786a3b15a32e , UUID: 4cf985e0-3f47-4320-ac38-42745b00ba1e

Inspect Services:

Consul:

$ docker service inspect kvstore_consul
[
    {
        "ID": "ppe2c1ld5eyvby6x62fr649z8",
        "Version": {
            "Index": 2097
        },
        "CreatedAt": "2017-10-03T12:40:27.342669337Z",
        "UpdatedAt": "2017-10-03T12:55:22.93080059Z",
        "Spec": {
            "Name": "kvstore_consul",
            "Labels": {
                "com.docker.stack.image": "progrium/consul",
                "com.docker.stack.namespace": "kvstore"
            },
            "TaskTemplate": {
                "ContainerSpec": {
                    "Image": "progrium/consul:latest@sha256:8cc8023462905929df9a79ff67ee435a36848ce7a10f18d6d0faba9306b97274",
                    "Labels": {
                        "com.docker.stack.namespace": "kvstore"
                    },
                    "Args": [
                        "-server",
                        "-bootstrap",
                        "-log-level",
                        "debug",
                        "-ui-dir",
                        "/ui"
                    ],
                    "Privileges": {
                        "CredentialSpec": null,
                        "SELinuxContext": null
                    },
                    "Mounts": [
                        {
                            "Type": "bind",
                            "Source": "/mnt/consul",
                            "Target": "/data"
                        }
                    ],
                    "StopGracePeriod": 10000000000,
                    "DNSConfig": {}
                },
                "Resources": {},
                "RestartPolicy": {
                    "Condition": "any",
                    "Delay": 5000000000,
                    "MaxAttempts": 0
                },
                "Placement": {
                    "Constraints": [
                        "node.role == manager"
                    ],
                    "Platforms": [
                        {
                            "Architecture": "amd64",
                            "OS": "linux"
                        }
                    ]
                },
                "Networks": [
                    {
                        "Target": "5q3puzw9pfxa5gokx2mx1kn9j",
                        "Aliases": [
                            "consul"
                        ]
                    }
                ],
                "ForceUpdate": 0,
                "Runtime": "container"
            },
            "Mode": {
                "Replicated": {
                    "Replicas": 1
                }
            },
            "UpdateConfig": {
                "Parallelism": 1,
                "FailureAction": "pause",
                "Monitor": 5000000000,
                "MaxFailureRatio": 0,
                "Order": "stop-first"
            },
            "RollbackConfig": {
                "Parallelism": 1,
                "FailureAction": "pause",
                "Monitor": 5000000000,
                "MaxFailureRatio": 0,
                "Order": "stop-first"
            },
            "EndpointSpec": {
                "Mode": "vip",
                "Ports": [
                    {
                        "Protocol": "tcp",
                        "TargetPort": 8400,
                        "PublishedPort": 8400,
                        "PublishMode": "ingress"
                    },
                    {
                        "Protocol": "tcp",
                        "TargetPort": 8500,
                        "PublishedPort": 8500,
                        "PublishMode": "ingress"
                    },
                    {
                        "Protocol": "udp",
                        "TargetPort": 53,
                        "PublishedPort": 8600,
                        "PublishMode": "ingress"
                    }
                ]
            }
        },
        "PreviousSpec": {
            "Name": "kvstore_consul",
            "Labels": {
                "com.docker.stack.image": "progrium/consul",
                "com.docker.stack.namespace": "kvstore"
            },
            "TaskTemplate": {
                "ContainerSpec": {
                    "Image": "progrium/consul:latest@sha256:8cc8023462905929df9a79ff67ee435a36848ce7a10f18d6d0faba9306b97274",
                    "Labels": {
                        "com.docker.stack.namespace": "kvstore"
                    },
                    "Args": [
                        "-server",
                        "-bootstrap",
                        "-log-level",
                        "debug",
                        "-ui-dir",
                        "/ui"
                    ],
                    "Privileges": {
                        "CredentialSpec": null,
                        "SELinuxContext": null
                    },
                    "Mounts": [
                        {
                            "Type": "bind",
                            "Source": "/mnt/consul",
                            "Target": "/data"
                        }
                    ]
                },
                "Resources": {},
                "Placement": {
                    "Platforms": [
                        {
                            "Architecture": "amd64",
                            "OS": "linux"
                        }
                    ]
                },
                "Networks": [
                    {
                        "Target": "5q3puzw9pfxa5gokx2mx1kn9j",
                        "Aliases": [
                            "consul"
                        ]
                    }
                ],
                "ForceUpdate": 0,
                "Runtime": "container"
            },
            "Mode": {
                "Replicated": {
                    "Replicas": 1
                }
            },
            "EndpointSpec": {
                "Mode": "vip",
                "Ports": [
                    {
                        "Protocol": "tcp",
                        "TargetPort": 8400,
                        "PublishedPort": 8400,
                        "PublishMode": "ingress"
                    },
                    {
                        "Protocol": "tcp",
                        "TargetPort": 8500,
                        "PublishedPort": 8500,
                        "PublishMode": "ingress"
                    },
                    {
                        "Protocol": "udp",
                        "TargetPort": 53,
                        "PublishedPort": 8600,
                        "PublishMode": "ingress"
                    }
                ]
            }
        },
        "Endpoint": {
            "Spec": {
                "Mode": "vip",
                "Ports": [
                    {
                        "Protocol": "tcp",
                        "TargetPort": 8400,
                        "PublishedPort": 8400,
                        "PublishMode": "ingress"
                    },
                    {
                        "Protocol": "tcp",
                        "TargetPort": 8500,
                        "PublishedPort": 8500,
                        "PublishMode": "ingress"
                    },
                    {
                        "Protocol": "udp",
                        "TargetPort": 53,
                        "PublishedPort": 8600,
                        "PublishMode": "ingress"
                    }
                ]
            },
            "Ports": [
                {
                    "Protocol": "tcp",
                    "TargetPort": 8400,
                    "PublishedPort": 8400,
                    "PublishMode": "ingress"
                },
                {
                    "Protocol": "tcp",
                    "TargetPort": 8500,
                    "PublishedPort": 8500,
                    "PublishMode": "ingress"
                },
                {
                    "Protocol": "udp",
                    "TargetPort": 53,
                    "PublishedPort": 8600,
                    "PublishMode": "ingress"
                }
            ],
            "VirtualIPs": [
                {
                    "NetworkID": "zz458844j2msbqa6a1g2es8re",
                    "Addr": "10.255.0.5/16"
                },
                {
                    "NetworkID": "5q3puzw9pfxa5gokx2mx1kn9j",
                    "Addr": "10.0.0.2/24"
                }
            ]
        },
        "UpdateStatus": {
            "State": "completed",
            "StartedAt": "2017-10-03T12:55:09.724524449Z",
            "CompletedAt": "2017-10-03T12:55:22.930760766Z",
            "Message": "update completed"
        }
    }
]

Traefik:

$ docker service inspect proxy_traefik
[
    {
        "ID": "oqb0lyiprpwby9xkb4n1mn5kl",
        "Version": {
            "Index": 2037
        },
        "CreatedAt": "2017-10-03T12:40:37.696749025Z",
        "UpdatedAt": "2017-10-03T12:40:37.698038856Z",
        "Spec": {
            "Name": "proxy_traefik",
            "Labels": {
                "com.docker.stack.image": "traefik",
                "com.docker.stack.namespace": "proxy"
            },
            "TaskTemplate": {
                "ContainerSpec": {
                    "Image": "traefik:latest@sha256:90697fb79a104520f350a3a1db6402584f473301ab6d1a71d264758b65fa232e",
                    "Labels": {
                        "com.docker.stack.namespace": "proxy"
                    },
                    "Args": [
                        "--consul",
                        "--consul.endpoint=consul:8500"
                    ],
                    "Privileges": {
                        "CredentialSpec": null,
                        "SELinuxContext": null
                    },
                    "StopGracePeriod": 10000000000,
                    "DNSConfig": {}
                },
                "Resources": {},
                "RestartPolicy": {
                    "Condition": "any",
                    "Delay": 5000000000,
                    "MaxAttempts": 0
                },
                "Placement": {
                    "Platforms": [
                        {
                            "Architecture": "amd64",
                            "OS": "linux"
                        },
                        {
                            "OS": "linux"
                        },
                        {
                            "Architecture": "arm64",
                            "OS": "linux"
                        }
                    ]
                },
                "Networks": [
                    {
                        "Target": "5q3puzw9pfxa5gokx2mx1kn9j",
                        "Aliases": [
                            "traefik"
                        ]
                    }
                ],
                "ForceUpdate": 0,
                "Runtime": "container"
            },
            "Mode": {
                "Replicated": {
                    "Replicas": 1
                }
            },
            "UpdateConfig": {
                "Parallelism": 1,
                "FailureAction": "pause",
                "Monitor": 5000000000,
                "MaxFailureRatio": 0,
                "Order": "stop-first"
            },
            "RollbackConfig": {
                "Parallelism": 1,
                "FailureAction": "pause",
                "Monitor": 5000000000,
                "MaxFailureRatio": 0,
                "Order": "stop-first"
            },
            "EndpointSpec": {
                "Mode": "vip",
                "Ports": [
                    {
                        "Protocol": "tcp",
                        "TargetPort": 80,
                        "PublishedPort": 80,
                        "PublishMode": "ingress"
                    },
                    {
                        "Protocol": "tcp",
                        "TargetPort": 8080,
                        "PublishedPort": 8080,
                        "PublishMode": "ingress"
                    }
                ]
            }
        },
        "Endpoint": {
            "Spec": {
                "Mode": "vip",
                "Ports": [
                    {
                        "Protocol": "tcp",
                        "TargetPort": 80,
                        "PublishedPort": 80,
                        "PublishMode": "ingress"
                    },
                    {
                        "Protocol": "tcp",
                        "TargetPort": 8080,
                        "PublishedPort": 8080,
                        "PublishMode": "ingress"
                    }
                ]
            },
            "Ports": [
                {
                    "Protocol": "tcp",
                    "TargetPort": 80,
                    "PublishedPort": 80,
                    "PublishMode": "ingress"
                },
                {
                    "Protocol": "tcp",
                    "TargetPort": 8080,
                    "PublishedPort": 8080,
                    "PublishMode": "ingress"
                }
            ],
            "VirtualIPs": [
                {
                    "NetworkID": "zz458844j2msbqa6a1g2es8re",
                    "Addr": "10.255.0.7/16"
                },
                {
                    "NetworkID": "5q3puzw9pfxa5gokx2mx1kn9j",
                    "Addr": "10.0.0.4/24"
                }
            ]
        }
    }
]

Flask App:

$ docker service inspect apps_whoami5
[
    {
        "ID": "hy0443u03j8pygaegdorsrjot",
        "Version": {
            "Index": 2052
        },
        "CreatedAt": "2017-10-03T12:41:06.582341297Z",
        "UpdatedAt": "2017-10-03T12:41:06.583398389Z",
        "Spec": {
            "Name": "apps_whoami5",
            "Labels": {
                "com.docker.stack.image": "rbekker87/flask-containername",
                "com.docker.stack.namespace": "apps"
            },
            "TaskTemplate": {
                "ContainerSpec": {
                    "Image": "rbekker87/flask-containername:latest@sha256:fa4dc5905a10130d4309ffbc877155b9f61956980dc51ee2eaa16ac4255bcc2b",
                    "Labels": {
                        "com.docker.stack.namespace": "apps"
                    },
                    "Privileges": {
                        "CredentialSpec": null,
                        "SELinuxContext": null
                    },
                    "StopGracePeriod": 10000000000,
                    "DNSConfig": {}
                },
                "Resources": {},
                "RestartPolicy": {
                    "Condition": "any",
                    "Delay": 5000000000,
                    "MaxAttempts": 0
                },
                "Placement": {
                    "Platforms": [
                        {
                            "Architecture": "amd64",
                            "OS": "linux"
                        }
                    ]
                },
                "Networks": [
                    {
                        "Target": "5q3puzw9pfxa5gokx2mx1kn9j",
                        "Aliases": [
                            "whoami5"
                        ]
                    }
                ],
                "ForceUpdate": 0,
                "Runtime": "container"
            },
            "Mode": {
                "Replicated": {
                    "Replicas": 1
                }
            },
            "UpdateConfig": {
                "Parallelism": 1,
                "FailureAction": "pause",
                "Monitor": 5000000000,
                "MaxFailureRatio": 0,
                "Order": "stop-first"
            },
            "RollbackConfig": {
                "Parallelism": 1,
                "FailureAction": "pause",
                "Monitor": 5000000000,
                "MaxFailureRatio": 0,
                "Order": "stop-first"
            },
            "EndpointSpec": {
                "Mode": "vip"
            }
        },
        "Endpoint": {
            "Spec": {
                "Mode": "vip"
            },
            "VirtualIPs": [
                {
                    "NetworkID": "5q3puzw9pfxa5gokx2mx1kn9j",
                    "Addr": "10.0.0.8/24"
                }
            ]
        }
    }
}

Thank You

Thanks for reading, feel free to check out my website, feel free to subscribe to my newsletter or follow me at @ruanbekker on Twitter.