使用Salt管理和部署Docker

  Salt是一个开源配置管理和远程执行应用,Salt目地是简化中央系统管理和配置的管理。而Docker是一个开源应用自动部署项目,在软件容器中提供附加抽象层和操作系统虚拟化。

Docker和Salt都能让裸机服务器和云服务一样使用,Salt提供基础系统,而Docker能包容每个应用,提供微服务功能。

假设以下情况:

  • 目标: 很多主机都需要部署Docker容器,而每个主机上有很多容器。
  • Not covered: 构建docker容器,通常和Jenkins 一起使用,然后推送到一个私人仓储或使用 Docker Hub.

Salt有一个state modules 系统和execution module执行模块,状态是整个情况的表示,而执行模块是用来乏味系统内部执行的命令,但是不能从状态中执行。可见 salt docs.

 

1.设置变量

开始设置很多变量以后会用得着,使用pillars和 grains能让状态更易于重用,noofcontainers是用于设置启动多少个容器。

{% set name = 'node-demo' %}

{% set registryname = 'jacksoncage' %}

{% set tag = salt['pillar.get']('imagetag', "latest") %}

{% set containerid = salt['grains.get']('id') %}

{% set hostport = '808' %}

{% set hostip = grains['ip_interfaces']['eth0'][0] %}

{% set noofcontainers = range(10) ‰}

 

2.拉取docker image (docker pull)

使用一个 nodejs demo application 作为image, 我们需要从Docker Hub拉取。

{{ name }}-image:

  docker.pulled:

    - name: {{ registryname }}/{{ name }}

    - tag: {{ tag }}

    - force: True

 

3.停止旧的容器

如果有旧的容器在运行我们需要停止:

{{ name }}-stop-if-old-{{ no }}:

  cmd.run:

    - name: docker stop {{ containerid }}-{{ name }}-{{ no }}

    - unless: docker inspect --format '{{ .Image }}' {{ containerid }}-{{ name }}-{{ nr }} | grep $(docker images --no-trunc | grep "{{ registryname }}/{{ name }}" | awk '{ print $3 }')

    - require:

      - docker: {{ name }}-image

 

4.去除旧容器

停止以后删除旧容器

{{ name }}-remove-if-old-{{ no }}:

  cmd.run:

    - name: docker rm {{ containerid }}-{{ name }}-{{ no }}

    - unless: docker inspect --format '{{ .Image }}' {{ containerid }}-{{ name }}-{{ nr }} | grep $(docker images --no-trunc | grep "{{ registryname }}/{{ name }}" | awk '{ print $3 }')

    - require:

      - cmd: {{ name }}-stop-if-old-{{ no }}

 

5.启动容器

通过docker run/docker.installed安装新容器,设置主机名 端口和环境变量:

{{ name }}-container-{{ no }}:

  docker.installed:

    - name: {{ containerid }}-{{ name }}-{{ no }}

    - hostname: {{ containerid }}-{{ name }}-{{ no }}

    - image: {{ registryname }}/{{ name }}:{{ tag }}

    - ports:

        - "8080/tcp"

    - environment:

        - EXECUTER: "forever -f start"

        - APP: "index.js"

    - require_in: {{ name }}-{{ no }}

    - require:

      - docker: {{ name }}-image

 

6.运行容器

通过运行docker.running运行容器,这也将端口和主机绑定:

{{ name }}-{{ no }}:

  docker.running:

    - container: {{ containerid }}-{{ name }}-{{ no }}

    - port_bindings:

        "8080/tcp":

            HostIp: "{{ hostip }}"

            HostPort: "{{ hostport }}{{ no }}"

 

完成状态文件和top文件

/srv/salt/containers/applications/node-demo.sls

{% set name           = 'node-demo'   %}

{% set registryname   = 'jacksoncage' %}

{% set tag            = salt['pillar.get']('imagetag', "latest") %}

{% set containerid    = salt['grains.get']('id') %}

{% set hostport       = '808' %}

{% set hostip         = grains['ip_interfaces']['eth0'][0] %}

{% set noofcontainers = range(5) ‰}

 

{{ name }}-image:

  docker.pulled:

    - name: {{ registryname }}/{{ name }}

    - tag: {{ tag }}

    - force: True

 

{% for no in noofcontainers %}

{{ name }}-stop-if-old-{{ no }}:

  cmd.run:

    - name: docker stop {{ containerid }}-{{ name }}-{{ no }}

    - unless: docker inspect --format '{{ .Image }}' {{ containerid }}-{{ name }}-{{ nr }} | grep $(docker images --no-trunc | grep "{{ registryname }}/{{ name }}" | awk '{ print $3 }')

    - require:

      - docker: {{ name }}-image

 

fetch_out_of_band:

module.run:

  - name: docker.stop

  - opts: 'timeout=20'

 

{{ name }}-remove-if-old-{{ no }}:

  cmd.run:

    - name: docker rm {{ containerid }}-{{ name }}-{{ no }}

    - unless: docker inspect --format '{{ .Image }}' {{ containerid }}-{{ name }}-{{ nr }} | grep $(docker images --no-trunc | grep "{{ registryname }}/{{ name }}" | awk '{ print $3 }')

    - require:

      - cmd: {{ name }}-stop-if-old-{{ no }}

 

{{ name }}-container-{{ no }}:

  docker.installed:

    - name: {{ containerid }}-{{ name }}-{{ no }}

    - hostname: {{ containerid }}-{{ name }}-{{ no }}

    - image: {{ registryname }}/{{ name }}:{{ tag }}

    - ports:

        - "8080/tcp"

    - environment:

        - EXECUTER: "node"

        - APP: "index.js"

    - require_in: {{ name }}-{{ no }}

    - require:

      - docker: {{ name }}-image

 

{{ name }}-{{ no }}:

  docker.running:

    - container: {{ containerid }}-{{ name }}-{{ no }}

    - port_bindings:

        "8080/tcp":

            HostIp: "{{ hostip }}"

            HostPort: "{{ hostport }}{{ no }}"

{%- endfor %}

/srv/salt/top.sls

base:

  'minion1':

    - containers.applications.node-demo

 

部署

完整状态Complete state 能在需要的minions地方执行

salt minion1 state.sls containers.applications.node-demo

Salt minion现在运行 node-demo状态文件创建所有容器,然后使用类似haproxy or nginx 等代理将所有运行容器加入到它们配置的hostname. 使用代理网关将无无当机时间,而且每个容器的运行顺序都是stopped=>removed=>created=>started

Docker教程

使用Apache Mesos和Consul实现服务的注册发现