├── .gitignore ├── state ├── top.sls ├── docker │ ├── files │ │ ├── docker-default │ │ └── docker.pgp │ └── init.sls ├── containers │ ├── api │ │ ├── pull.sls │ │ ├── remove.sls │ │ ├── api.sls │ │ ├── api-phase1.sls │ │ └── etcd │ │ │ ├── add.sls │ │ │ └── update.sls │ ├── etcd.sls │ ├── registry.sls │ ├── haproxy.sls │ └── files │ │ └── haproxy.cfg └── python-pip │ └── init.sls ├── reactor ├── deploy-phase1.sls └── deploy-phase2.sls └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | sync.sh 2 | -------------------------------------------------------------------------------- /state/top.sls: -------------------------------------------------------------------------------- 1 | base: 2 | '*': 3 | - docker 4 | 5 | 'dockyard': 6 | - containers.etcd 7 | - containers.registry 8 | -------------------------------------------------------------------------------- /state/docker/files/docker-default: -------------------------------------------------------------------------------- 1 | # Docker Upstart and SysVinit configuration file 2 | # Use DOCKER_OPTS to modify the daemon startup options. 3 | DOCKER_OPTS="-g /var/lib/docker --registry-mirror={{ URL }}:5000 -H tcp://0.0.0.0:4243 -H unix:///var/run/docker.sock" 4 | -------------------------------------------------------------------------------- /state/containers/api/pull.sls: -------------------------------------------------------------------------------- 1 | {% set name = 'node-demo' %} 2 | {% set registryname = 'jacksoncage' %} 3 | {% set tag = salt['pillar.get']('containers:api:tag:new', '') %} 4 | 5 | {{ name }}-image: 6 | docker.pulled: 7 | - name: {{ registryname }}/{{ name }} 8 | - tag: {{ tag }} 9 | - force: True 10 | -------------------------------------------------------------------------------- /state/containers/api/remove.sls: -------------------------------------------------------------------------------- 1 | {% set name = 'node-demo' %} 2 | {% set amount = salt['pillar.get']('containers:api:amount:last', '') %} 3 | {% set containerid = salt['pillar.get']('containers:api:unactive', '') %} 4 | 5 | # docker stop and remove 6 | {% for no in range(0, amount|int + 1) %} 7 | {{ name }}-stop-unactive-{{ no }}: 8 | docker.absent: 9 | - name: {{ containerid }}-{{ name }}-{{ no }} 10 | {%- endfor %} 11 | -------------------------------------------------------------------------------- /state/python-pip/init.sls: -------------------------------------------------------------------------------- 1 | pkg-deps: 2 | pkg.latest: 3 | - pkgs: 4 | - python-pip 5 | - python-dev 6 | - python-apt 7 | - libffi-dev 8 | - libssl-dev 9 | 10 | docker-py-api: 11 | pip.installed: 12 | - name: docker-py == 0.5.0 13 | - require: 14 | - pkg: pkg-deps 15 | 16 | python-etcd: 17 | pip.installed: 18 | - index_url: https://pypi.binstar.org/pypi/simple 19 | - require: 20 | - pkg: pkg-deps 21 | 22 | python-cherrypy3: 23 | pkg.installed: 24 | - name: python-cherrypy3 25 | - require: 26 | - pkg: pkg-deps 27 | -------------------------------------------------------------------------------- /state/containers/etcd.sls: -------------------------------------------------------------------------------- 1 | {% set hostip = grains['ip_interfaces']['eth0'][0] %} 2 | etcd-image: 3 | docker.pulled: 4 | - name: bloglovin/etcd:latest 5 | - require_in: etcd-container 6 | - force: True 7 | 8 | etcd-container: 9 | docker.installed: 10 | - name: etcd01 11 | - hostname: etcd01 12 | - image: bloglovin/etcd:latest 13 | - ports: 14 | - "4001/tcp" 15 | - "7001/tcp" 16 | - require_in: etcd 17 | 18 | etcd: 19 | docker.running: 20 | - container: etcd01 21 | - port_bindings: 22 | "4001/tcp": 23 | HostIp: "{{ hostip }}" 24 | HostPort: "4001" 25 | "7001/tcp": 26 | HostIp: "{{ hostip }}" 27 | HostPort: "7001" 28 | -------------------------------------------------------------------------------- /state/docker/files/docker.pgp: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Version: GnuPG v1.4.11 (GNU/Linux) 3 | 4 | mQENBFIOqEUBCADsvqwefcPPQArws9jHF1PaqhXxkaXzeE5uHHtefdoRxQdjoGok 5 | HFmHWtCd9zR7hDpHE7Q4dwJtSFWZAM3zaUtlvRAgvMmfLm08NW9QQn0CP5khjjF1 6 | cgckhjmzQAzpEHO5jiSwl0ZU8ouJrLDgmbhT6knB1XW5/VmeECqKRyhlEK0zRz1a 7 | XV+4EVDySlORmFyqlmdIUmiU1/6pKEXyRBBVCHNsbnpZOOzgNhfMz8VE8Hxq7Oh8 8 | 1qFaFXjNGCrNZ6xr/DI+iXlsZ8urlZjke5llm4874N8VPUeFQ/szmsbSqmCnbd15 9 | LLtrpvpSMeyRG+LoTYvyTG9QtAuewL9EKJPfABEBAAG0OURvY2tlciBSZWxlYXNl 10 | IFRvb2wgKHJlbGVhc2Vkb2NrZXIpIDxkb2NrZXJAZG90Y2xvdWQuY29tPokBOAQT 11 | AQIAIgUCUg6oRQIbLwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ2Fdqi6iN 12 | IenM+QgAnOiozhHDAYGO92SmZjib6PK/1djbrDRMreCT8bnzVpriTOlEtARDXsmX 13 | njKSFa+HTxHi/aTNo29TmtHDfUupcfmaI2mXbZt1ixXLuwcMv9sJXKoeWwKZnN3i 14 | 9vAM9/yAJz3aq+sTXeG2dDrhZr34B3nPhecNkKQ4v6pnQy43Mr59Fvv5CzKFa9oZ 15 | IoZf+Ul0F90HSw5WJ1NsDdHGrAaHLZfzqAVrqHzazw7ghe94k460T8ZAaovCaTQV 16 | HzTcMfJdPz/uTim6J0OergT9njhtdg2ugUj7cPFUTpsxQ1i2S8qDEQPL7kabAZZo 17 | Pim0BXdjsHVftivqZqfWeVFKMorchQ== 18 | =fRgo 19 | -----END PGP PUBLIC KEY BLOCK----- 20 | -------------------------------------------------------------------------------- /reactor/deploy-phase1.sls: -------------------------------------------------------------------------------- 1 | # This script is to be called from the reactor system 2 | # 3 | # 1. Sync states 4 | # 2. Deploy new containers to cluster 5 | # 6 | # Example start deploy via curl: 7 | #curl -H 'X-Salt-Deploy-Key: QmpaDa8T3UdFVBUHWo@T' \ 8 | # -H 'Content-Type: application/json' \ 9 | # -d '{"application":"api","container":"api-phase1","node":"ship01","tag":"1.0","amount":"2"}' \ 10 | # http://127.0.0.1:8000/hook/api/deploy/phase2/success 11 | # 12 | {% set secret_key = data.get('headers', {}).get('X-Salt-Deploy-Key') %} 13 | {% set deploy = data.get('post', {}) %} 14 | {% if secret_key == 'QmpaDa8T3UdFVBUHWo@T' %} 15 | # 1. Sync states 16 | sync-states: 17 | cmd.saltutil.sync_states: 18 | - tgt: {{ deploy.node }} 19 | 20 | # 2. Deploy new containers to cluster 21 | deploy: 22 | cmd.state.sls: 23 | - tgt: {{ deploy.node }} 24 | - arg: 25 | - containers.api.{{ deploy.container }} 26 | - kwarg: 27 | pillar: 28 | containers: 29 | imagetag: {{ deploy.tag }} 30 | amount: {{ deploy.amount }} 31 | 32 | {% endif %} 33 | -------------------------------------------------------------------------------- /state/containers/registry.sls: -------------------------------------------------------------------------------- 1 | {% set name = 'registry' %} 2 | {% set hostip = grains['ip_interfaces']['eth0'][0] %} 3 | 4 | {{ name }}-dir: 5 | file.directory: 6 | - name: /data/docker/mirror 7 | - user: root 8 | - group: users 9 | - mode: 755 10 | - makedirs: True 11 | 12 | {{ name }}-image: 13 | docker.pulled: 14 | - name: h3nrik/simple-registry-mirror:latest 15 | - require_in: {{ name }}-container 16 | - force: True 17 | 18 | {{ name }}-container: 19 | docker.installed: 20 | - name: {{ name }}-mirror 21 | - hostname: {{ name }}-mirror 22 | - image: h3nrik/simple-registry-mirror:latest 23 | - ports: 24 | - "5000/tcp" 25 | - volumes: 26 | - /data/docker/mirror:/opt/registry 27 | - require_in: {{ name }} 28 | 29 | {{ name }}: 30 | docker.running: 31 | - container: {{ name }}-mirror 32 | - port_bindings: 33 | "5000/tcp": 34 | HostIp: "{{ hostip }}" 35 | HostPort: "5000" 36 | - binds: 37 | /data/docker/mirror/: 38 | bind: /opt/registry 39 | rw: True 40 | -------------------------------------------------------------------------------- /state/containers/api/api.sls: -------------------------------------------------------------------------------- 1 | {% set name = 'node-demo' %} 2 | {% set registryname = 'jacksoncage' %} 3 | {% set tag = salt['pillar.get']('containers:api:tag:new', '') %} 4 | {% set amount = salt['pillar.get']('containers:api:amount:new', '') %} 5 | {% set containerid = salt['pillar.get']('containers:api:unactive', '') %} 6 | {% set hostip = grains['ip_interfaces']['docker0'][0] %} 7 | 8 | {% for no in range(0, amount|int + 1) %} 9 | {{ name }}-container-{{ no }}: 10 | docker.installed: 11 | - name: {{ containerid }}-{{ name }}-{{ no }} 12 | - hostname: {{ containerid }}-{{ name }}-{{ no }} 13 | - image: {{ registryname }}/{{ name }}:{{ tag }} 14 | - ports: 15 | - "8080/tcp" 16 | - environment: 17 | - EXECUTER: "node" 18 | - APP: "index.js" 19 | - require_in: {{ name }}-{{ no }} 20 | 21 | {{ name }}-{{ no }}: 22 | docker.running: 23 | - container: {{ containerid }}-{{ name }}-{{ no }} 24 | - port_bindings: 25 | "8080/tcp": 26 | HostIp: "{{ hostip }}" 27 | HostPort: "" 28 | {%- endfor %} 29 | -------------------------------------------------------------------------------- /state/docker/init.sls: -------------------------------------------------------------------------------- 1 | {% set url = salt['pillar.get']('containers:registry:url', 'http://127.0.0.1') %} 2 | 3 | include: 4 | - python-pip 5 | 6 | docker-default: 7 | file.managed: 8 | - name: /etc/default/docker 9 | - source: salt://docker/files/docker-default 10 | - template: jinja 11 | - context: 12 | URL: {{ url }} 13 | - require_in: 14 | - service: docker 15 | 16 | docker-python-apt: 17 | pkg.installed: 18 | - name: python-apt 19 | 20 | docker-dependencies: 21 | pkg.installed: 22 | - pkgs: 23 | - iptables 24 | - ca-certificates 25 | - lxc 26 | 27 | docker_repo: 28 | pkgrepo.managed: 29 | - repo: 'deb http://get.docker.io/ubuntu docker main' 30 | - file: '/etc/apt/sources.list.d/docker.list' 31 | - key_url: salt://docker/files/docker.pgp 32 | - require_in: 33 | - pkg: lxc-docker 34 | - require: 35 | - pkg: docker-python-apt 36 | 37 | lxc-docker: 38 | pkg.latest: 39 | - require: 40 | - pkg: docker-dependencies 41 | 42 | docker: 43 | service.running: 44 | - name: docker 45 | - watch: 46 | - file: docker-default 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Salt container deploy orchestration demo 2 | 3 | Used for prestation at [SaltConf15](http://saltconf.com/). Discussed in more detail in blog post [Salt container deploy orchestration demo](http://jacksoncage.se) 4 | 5 | 6 | # Architecture 7 | 8 | *Servers* 9 | - **Dockyard** - Running salt master and api. Master controls etcd and docker registry mirror 10 | - **Ship's** - Running salt minion and docker. 11 | 12 | ## Config 13 | 14 | There are some configuration regarding this demo which are not present, such as salt master/minion config. That configuration will be needed to deploy containers via salt api and reactor. 15 | 16 | 17 | ### Reactor 18 | 19 | `/etc/salt/master.d/reactor.conf` 20 | 21 | ``` 22 | reactor: 23 | - 'salt/netapi/hook/api/deploy/phase1/*': 24 | - /srv/reactor/deploy-phase1.sls 25 | 26 | - 'salt/netapi/hook/api/deploy/phase2/*': 27 | - /srv/reactor/deploy-phase2.sls 28 | ``` 29 | 30 | ### External pillars 31 | 32 | `/etc/salt/master` 33 | 34 | ``` 35 | etcd_config: 36 | etcd.host: etcd.lab.jacksoncage.se 37 | etcd.port: 4001 38 | 39 | ext_pillar: 40 | - etcd: etcd_config root=/salt/shared 41 | - etcd: etcd_config root=/salt/private/%(minion_id)s 42 | ``` 43 | 44 | ### Salt API 45 | 46 | `/etc/salt/master` 47 | 48 | ``` 49 | rest_cherrypy: 50 | port: 8000 51 | host: 0.0.0.0 52 | debug: True 53 | disable_ssl: True 54 | webhook_disable_auth: True 55 | webhook_url: /hook 56 | ``` 57 | -------------------------------------------------------------------------------- /state/containers/haproxy.sls: -------------------------------------------------------------------------------- 1 | # Docker run command 2 | # docker run -d --name my-running-haproxy -v /path/to/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro haproxy:latest 3 | {% set name = 'haproxy' -%} 4 | {% set hostip = grains['ip_interfaces']['eth0'][0] -%} 5 | 6 | {{ name }}-dir: 7 | file.directory: 8 | - name: /data/docker/haproxy 9 | - user: root 10 | - group: users 11 | - mode: 755 12 | - makedirs: True 13 | 14 | {{ name }}-cfg: 15 | file.managed: 16 | - name: /data/docker/haproxy/haproxy.cfg 17 | - source: salt://containers/files/haproxy.cfg 18 | - template: jinja 19 | - user: root 20 | - group: root 21 | - mode: 644 22 | 23 | {{ name }}-image: 24 | docker.pulled: 25 | - name: haproxy 26 | - tag: latest 27 | - require_in: {{ name }}-container 28 | - force: True 29 | 30 | {{ name }}-container: 31 | docker.installed: 32 | - name: {{ name }} 33 | - hostname: {{ name }} 34 | - image: haproxy:latest 35 | - ports: 36 | - "80/tcp" 37 | - volumes: 38 | - /data/docker/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg 39 | - require_in: {{ name }} 40 | 41 | {{ name }}: 42 | docker.running: 43 | - container: {{ name }} 44 | - port_bindings: 45 | "80/tcp": 46 | HostIp: "{{ hostip }}" 47 | HostPort: "80" 48 | - binds: 49 | /data/docker/haproxy/haproxy.cfg: 50 | bind: /usr/local/etc/haproxy/haproxy.cfg 51 | ro: False 52 | 53 | {{ name }}-reload: 54 | cmd.wait: 55 | - name: docker restart haproxy 56 | - user: root 57 | - group: root 58 | - watch: 59 | - file: haproxy-cfg 60 | -------------------------------------------------------------------------------- /state/containers/api/api-phase1.sls: -------------------------------------------------------------------------------- 1 | {% set name = 'node-demo' %} 2 | {% set registryname = 'jacksoncage' %} 3 | {% set tag = salt['pillar.get']('containers:api:tag:new', '') %} 4 | {% set amount = salt['pillar.get']('containers:api:amount', '') %} 5 | {% set containerid = salt['pillar.get']('containers:api:unactive', '') %} 6 | {% set hostip = grains['ip_interfaces']['lo'][0] %} 7 | 8 | {{ name }}-image: 9 | docker.pulled: 10 | - name: {{ registryname }}/{{ name }} 11 | - tag: {{ tag }} 12 | - force: True 13 | 14 | {% for no in range(1, amount + 1) %} 15 | {{ name }}-stop-if-old-{{ no }}: 16 | cmd.run: 17 | - name: docker stop {{ containerid }}-{{ name }}-{{ no }} 18 | - unless: docker inspect --format '{% raw %}{{ .Image }}{% endraw %}' {{ containerid }}-{{ name }}-{{ no }} | grep $(docker images --no-trunc | grep "{{ registryname }}/{{ name }}" | awk '{ print $3 }') 19 | - require: 20 | - docker: {{ name }}-image 21 | 22 | {{ name }}-remove-if-old-{{ no }}: 23 | cmd.run: 24 | - name: docker rm {{ containerid }}-{{ name }}-{{ no }} 25 | - unless: docker inspect --format '{% raw %}{{ .Image }}{% endraw %}' {{ containerid }}-{{ name }}-{{ no }} | grep $(docker images --no-trunc | grep "{{ registryname }}/{{ name }}" | awk '{ print $3 }') 26 | - require: 27 | - cmd: {{ name }}-stop-if-old-{{ no }} 28 | 29 | {{ name }}-container-{{ no }}: 30 | docker.installed: 31 | - name: {{ containerid }}-{{ name }}-{{ no }} 32 | - hostname: {{ containerid }}-{{ name }}-{{ no }} 33 | - image: {{ registryname }}/{{ name }}:{{ tag }} 34 | - ports: 35 | - "8080/tcp" 36 | - environment: 37 | - EXECUTER: "node" 38 | - APP: "index.js" 39 | - require_in: {{ name }}-{{ no }} 40 | - require: 41 | - docker: {{ name }}-image 42 | 43 | {{ name }}-{{ no }}: 44 | docker.running: 45 | - container: {{ containerid }}-{{ name }}-{{ no }} 46 | - port_bindings: 47 | "8080/tcp": 48 | HostIp: "{{ hostip }}" 49 | HostPort: "" 50 | {%- endfor %} 51 | -------------------------------------------------------------------------------- /state/containers/api/etcd/add.sls: -------------------------------------------------------------------------------- 1 | # Set/change danamic in etcd 2 | # 3 | # 1. Set current tag 4 | # 2. Set new tag 5 | # 3. Set current amount 6 | # 4. Set new amount 7 | # 5. Set image 8 | # 9 | {% set tagcurrent = salt['pillar.get']('containers:api:tag:current', '') %} 10 | {% set tagnew = salt['pillar.get']('containers:imagetag', '') %} 11 | {% set amountcurrent = salt['pillar.get']('containers:api:amount:current', '') %} 12 | {% set amountnew = salt['pillar.get']('containers:amount', '') %} 13 | {% set name = salt['pillar.get']('containers:name', '') %} 14 | {% set container = salt['pillar.get']('containers:container', '') %} 15 | {% set image = salt['pillar.get']('containers:image', '') %} 16 | 17 | # 1. Set current tag 18 | set-etcd-tag-last: 19 | module.run: 20 | - name: etcd.set 21 | - key: /salt/shared/containers/api/tag/last 22 | - value: "{{ tagcurrent }}" 23 | - profile: etcd_config 24 | - reload_pillar: True 25 | 26 | # 2. Set new tag 27 | set-etcd-tag-new: 28 | module.run: 29 | - name: etcd.set 30 | - key: /salt/shared/containers/api/tag/new 31 | - value: "{{ tagnew }}" 32 | - profile: etcd_config 33 | - reload_pillar: True 34 | 35 | # 3. Set current amount 36 | set-etcd-amount-last: 37 | module.run: 38 | - name: etcd.set 39 | - key: /salt/shared/containers/api/amount/last 40 | - value: {{ amountcurrent }} 41 | - profile: etcd_config 42 | - reload_pillar: True 43 | 44 | # 4. Set new amount 45 | set-etcd-amount-new: 46 | module.run: 47 | - name: etcd.set 48 | - key: /salt/shared/containers/api/amount/new 49 | - value: {{ amountnew }} 50 | - profile: etcd_config 51 | - reload_pillar: True 52 | 53 | # 5. Set image 54 | set-etcd-image: 55 | module.run: 56 | - name: etcd.set 57 | - key: /salt/shared/containers/api/image 58 | - value: "{{ image }}" 59 | - profile: etcd_config 60 | - reload_pillar: True 61 | 62 | # 5. Set name 63 | set-etcd-name: 64 | module.run: 65 | - name: etcd.set 66 | - key: /salt/shared/containers/api/name 67 | - value: "{{ name }}" 68 | - profile: etcd_config 69 | - reload_pillar: True 70 | -------------------------------------------------------------------------------- /state/containers/files/haproxy.cfg: -------------------------------------------------------------------------------- 1 | {% set amount = salt['pillar.get']('containers:api:amount:new', '') -%} 2 | {% set active = salt['pillar.get']('containers:api:active', '') -%} 3 | {% set username = salt['pillar.get']('haproxy:username', '') -%} 4 | {% set password = salt['pillar.get']('haproxy:password', '') -%} 5 | {% set name = salt['pillar.get']('containers:api:name', '') -%} 6 | {% set id = grains['id'] -%} 7 | 8 | global 9 | log 127.0.0.1 local0 10 | log 127.0.0.1 local1 notice 11 | maxconn 2048 12 | tune.ssl.default-dh-param 2048 13 | tune.bufsize 10240000 14 | 15 | defaults 16 | log global 17 | mode http 18 | option httplog 19 | option dontlognull 20 | option redispatch 21 | timeout connect 5000 22 | timeout client 10000 23 | timeout server 10000 24 | stats enable 25 | stats uri /haproxy?stats 26 | stats realm Strictly\ Private 27 | stats auth {{ username }}:{{ password }} 28 | 29 | frontend http-in 30 | bind 0.0.0.0:80 31 | acl host_api hdr_dom(host) -i api 32 | use_backend api-{{ active }} if host_api 33 | default_backend api-{{ active }} 34 | 35 | backend api-blue 36 | option httpclose 37 | option forwardfor 38 | {% for no in range(0, amount|int + 1) %} 39 | {% if salt['docker.inspect_container']('blue-node-demo-' + no|string)['out']['NetworkSettings']['Ports']['8080/tcp'][0] -%} 40 | {%- set container = salt['docker.inspect_container']('blue-node-demo-' + no|string)['out']['NetworkSettings']['Ports']['8080/tcp'][0] -%} 41 | server container {{ container['HostIp'] }}:{{ container['HostPort'] }} check 42 | {% endif -%} 43 | {% endfor -%} 44 | 45 | backend api-green 46 | option httpclose 47 | option forwardfor 48 | {% for no in range(0, amount|int + 1) %} 49 | {% if salt['docker.inspect_container']('green-node-demo-' + no|string)['out']['NetworkSettings']['Ports']['8080/tcp'][0] -%} 50 | {%- set container = salt['docker.inspect_container']('green-node-demo-' + no|string)['out']['NetworkSettings']['Ports']['8080/tcp'][0] -%} 51 | server container {{ container['HostIp'] }}:{{ container['HostPort'] }} check 52 | {% endif -%} 53 | {% endfor -%} 54 | -------------------------------------------------------------------------------- /reactor/deploy-phase2.sls: -------------------------------------------------------------------------------- 1 | # Phase2 reactor sls 2 | # 3 | # 1. Sync states 4 | # 2. Update etcd tags 5 | # 3. Pull docker image 6 | # 4. deploy new containers to cluster 7 | # 5. Add new containers to etcd 8 | # 6. Update haproxy using etcd 9 | # 7. Close down containers 10 | # 11 | # Example start deploy via curl: 12 | #curl -H 'X-Salt-Deploy-Key: QmpaDa8T3UdFVBUHWo@T' \ 13 | # -H 'Content-Type: application/json' \ 14 | # -d '{"application":"api","container":"api","node":"ship01","tag":"1.0","amount":"2"}' \ 15 | # http://127.0.0.1:8000/hook/api/deploy/phase2/success 16 | # 17 | {% set secret_key = data.get('headers', {}).get('X-Salt-Deploy-Key') %} 18 | {% set deploy = data.get('post', {}) %} 19 | {% if secret_key == 'QmpaDa8T3UdFVBUHWo@T' %} 20 | # 1. Sync states 21 | # salt ship01 saltutil.sync_states 22 | sync-states: 23 | cmd.saltutil.sync_states: 24 | - tgt: {{ deploy.node }} 25 | 26 | # 2. Update etcd tags 27 | # salt ship01 state.sls containers.api.etcd.add 'pillar={ containers: { imagetag: latest, image: node-demo, name: api, container: api, amount: 2 } }' 28 | etcd-add: 29 | cmd.state.sls: 30 | - tgt: {{ deploy.node }} 31 | - arg: 32 | - containers.api.etcd.add 33 | - kwarg: 34 | pillar: 35 | containers: 36 | imagetag: {{ deploy.tag }} 37 | image: {{ deploy.image }} 38 | name: {{ deploy.application }} 39 | container: {{ deploy.container }} 40 | amount: {{ deploy.amount }} 41 | 42 | # 3. Pull docker image 43 | # salt ship01 state.sls containers.api.pull 44 | pull: 45 | cmd.state.sls: 46 | - tgt: {{ deploy.node }} 47 | - arg: 48 | - containers.api.pull 49 | 50 | # 4. deploy new containers to cluster 51 | # salt ship01 state.sls containers.api.api 52 | deploy: 53 | cmd.state.sls: 54 | - tgt: {{ deploy.node }} 55 | - arg: 56 | - containers.api.{{ deploy.container }} 57 | - failhard: True 58 | 59 | # 5. Add new containers to etcd 60 | # salt ship01 state.sls containers.api.etcd.update 61 | etcd-update: 62 | cmd.state.sls: 63 | - tgt: {{ deploy.node }} 64 | - arg: 65 | - containers.api.etcd.update 66 | - failhard: True 67 | 68 | # 6. Update haproxy using etcd 69 | # salt ship01 state.sls containers.haproxy 70 | loadbalancer: 71 | cmd.state.sls: 72 | - tgt: {{ deploy.node }} 73 | - arg: 74 | - containers.haproxy 75 | 76 | # 7. Close down containers 77 | # salt ship01 state.sls containers.api.remove 78 | remove: 79 | cmd.state.sls: 80 | - tgt: {{ deploy.node }} 81 | - arg: 82 | - containers.api.remove 83 | 84 | {% endif %} 85 | -------------------------------------------------------------------------------- /state/containers/api/etcd/update.sls: -------------------------------------------------------------------------------- 1 | # Set and change api's in etcd 2 | # 3 | # 1. Check new still uncative containers if failer stop 4 | # 2. Set new/update containers in etcd 5 | # 3. Set new container color to be active 6 | # 7 | {% set unactive = salt['pillar.get']('containers:api:unactive', '') -%} 8 | {% set active = salt['pillar.get']('containers:api:active', '') -%} 9 | {% set tag = salt['pillar.get']('containers:api:tag:new', '') -%} 10 | {% set amount = salt['pillar.get']('containers:api:amount:new', '') -%} 11 | 12 | {% for no in range(0, amount|int + 1) -%} 13 | {%- if unactive == 'green' -%} 14 | {%- set cid = salt['docker.inspect_container']('green-node-demo')['id'] -%} 15 | {%- set container = salt['docker.inspect_container']('green-node-demo-' + no|string)['out']['NetworkSettings']['Ports']['8080/tcp'][0] -%} 16 | {%- elif unactive == 'blue' -%} 17 | {%- set cid = salt['docker.inspect_container']('blue-node-demo')['id'] -%} 18 | {%- set container = salt['docker.inspect_container']('blue-node-demo-' + no|string)['out']['NetworkSettings']['Ports']['8080/tcp'][0] -%} 19 | {%- endif -%} 20 | 21 | # 1. Check new still uncative containers if failer stop 22 | unactive-healtcheck-{{ no }}: 23 | cmd.run: 24 | - name: 'curl -f http://{{ container['HostIp'] }}:{{ container['HostPort'] }}/' 25 | - failhard: True 26 | 27 | # 2. Set new/update containers in etcd 28 | set-etcd-for-api-{{ no }}: 29 | module.run: 30 | - name: etcd.set 31 | - key: /salt/shared/containers/api/{{ grains['id'] }}/{{ cid }}/{{ no }}/network 32 | - value: "{{ container['HostIp'] }}:{{ container['HostPort'] }}" 33 | - profile: etcd_config 34 | - reload_pillar: True 35 | {% endfor -%} 36 | 37 | # 3. Set new container color to be active 38 | set-etcd-unactive: 39 | module.run: 40 | - name: etcd.set 41 | - key: /salt/shared/containers/api/unactive 42 | - value: "{{ active }}" 43 | - profile: etcd_config 44 | - reload_pillar: True 45 | 46 | set-etcd-active: 47 | module.run: 48 | - name: etcd.set 49 | - key: /salt/shared/containers/api/active 50 | - value: "{{ unactive }}" 51 | - profile: etcd_config 52 | - reload_pillar: True 53 | 54 | set-etcd-tag-current: 55 | module.run: 56 | - name: etcd.set 57 | - key: /salt/shared/containers/api/tag/current 58 | - value: "{{ tag }}" 59 | - profile: etcd_config 60 | - reload_pillar: True 61 | 62 | set-etcd-amount-current: 63 | module.run: 64 | - name: etcd.set 65 | - key: /salt/shared/containers/api/amount/current 66 | - value: "{{ amount }}" 67 | - profile: etcd_config 68 | - reload_pillar: True 69 | --------------------------------------------------------------------------------