docker安装:https://blog.csdn.net/jy02268879/article/details/111185992
jenkins安装:https://blog.csdn.net/jy02268879/article/details/89819598
ansible安装:https://blog.csdn.net/jy02268879/article/details/111478340
gitlab安装:https://blog.csdn.net/jy02268879/article/details/111266982
本篇讲docker compose和docker swarm部分,还有jenkins+ansible+gitlab+docker+compose+swarm 自动发布的例子
不包含在任何 Swarm 中的 Docker 节点,称为运行于单引擎(Single-Engine)模式。一旦被加入 Swarm 集群,则切换为 Swarm 模式,如下图所示。
在单引擎模式下的 Docker 主机上运行 docker swarm init
会将其切换到 Swarm 模式,并创建一个新的 Swarm,将自身设置为 Swarm 的第一个管理节点。
在选定docker swarm的leader机器既这里是( node1)172.20.10.8上初始化
$ docker swarm init \
--advertise-addr 172.20.10.8:2377 \
--listen-addr 172.20.10.8:2377
将这条命令拆开分析如下。
docker swarm init会通知 Docker 来初始化一个新的 Swarm,并将自身设置为第一个管理节点。同时也会使该节点开启 Swarm 模式。
--advertise-addr 指定其他节点用来连接到当前管理节点的 IP 和端口。这一属性是可选的,当节点上有多个 IP 时,可以用于指定使用哪个IP。此外,还可以用于指定一个节点上没有的 IP,比如一个负载均衡的 IP。
--listen-addr 指定用于承载 Swarm 流量的 IP 和端口。其设置通常与 --advertise-addr 相匹配,但是当节点上有多个 IP 的时候,可用于指定具体某个 IP。并且,如果 --advertise-addr 设置了一个远程 IP 地址(如负载均衡的IP地址),该属性也是需要设置的。建议执行命令时总是使用这两个属性来指定具体 IP 和端口。
Swarm 模式下的操作默认运行于 2337 端口。虽然它是可配置的,但 2377/tcp 是用于客户端与 Swarm 进行安全(HTTPS)通信的约定俗成的端口配置。
执行结果
注意:这里把这些内容记录下来
这个执行结果的内容是告诉你,如果你后面想加入工作节点到这个swarm,请使用这句话
docker swarm join --token SWMTKN-1-4oxx6w39qtnq7o3tk3w71rxusnc5xx9lwlokjk8lw0yd0k6zvb-8uhtcgjhwba91wfmc09x13p0a 172.20.10.8:2377
这个执行结果的内容是告诉你,如果你后面想加入管理节点到这个swarm,请使用这句话查看加入管理节点的命令
docker swarm join-token manager
注意:工作节点和管理节点的接入命令中使用的接入 Token(SWMTKN...)是不同的。因此,一个节点是作为工作节点还是管理节点接入,完全依赖于使用了哪个 Token。
接入 Token 应该被妥善保管,因为这是将一个节点加入 Swarm 的唯一所需!
列出 Swarm 中的节点。
docker node ls
这能看到该节点的状态,hostname,是否激活,是否为管理机,docker版本,node ID
接下来将 node2、node3作为工作节点接入,自动将它们切换为Swarm模式
在node2、node3中分别执行
docker swarm join --token SWMTKN-1-0iltgvpbvmf8kvfnzckyrmspyo569g9royk05jpd8hvlzh9nme-a338edtsq3vthu8l6us79kmuw 172.20.10.8:2377
如果管理机docker swarm init的时候指定了advertise-addr喝listen-addr,记得在node2和node3加入的时候确保使用 --advertise-addr 与 --listen-addr 属性来指定各自的 IP 地址。既是node3用node3的IP,node2用node2的IP
结果
在这个过程中,每个节点的 Docker 引擎都被切换到 Swarm 模式下。并且,Swarm 已经自动启用了 TLS 以策安全。
每次将节点加入 Swarm 都指定 --advertise-addr 与 --listen-addr 属性是痛苦的。然而,一旦 Swarm 中的网络配置出现问题将会更加痛苦。况且,手动将节点加入 Swarm 也不是一种日常操作,所以在执行该命令时额外指定这两个属性是值得的。
此时在管理机node1上执行
docker node ls
docker swarm的集群中的管理机器还能做成多个用以实现高可用HA,共识算法是raft,同一时间只有一个管理机是leader,其他的管理机器是follower,如果docker客户端命令发送到了follower上,follower会转发到leader上,只会有Leader对swarm执行命令。部署奇数个管理节点有利于减少脑裂(Split-Brain)情况的出现机会。
详情看:http://c.biancheng.net/view/3178.html
比如service1这个应用,需要启动3个副本,那直接用docker service create设置副本数--replicas=3 模式为Replication Mode,这种模式会部署期望数量的服务副本,并尽可能均匀地将各个副本分布在整个集群中。
而此处,我们是需要每个服务在指定的机器上只启动一个,比如在node1 上启动一个service1,在node2上启动一个service1.并不仅仅只是期望副本数量而已。
全局模式(global),在这种模式下,每个节点上仅运行一个副本。也可以通过给 docker service create
命令传递 --mode global 参数来部署一个全局服务。
我采取的方式是加标签:
docker node update --label-add
注意这里的label要跟ansible的/etc/anisble/hosts里面一样
先看一下我的ansible是怎么加的
然后得到每个node的ID
docker node ls
然后添加标签
在swarm控制机172.20.10.8上执行,给172.20.10.8(node1)打上service1的标签,给172.20.10.9(node2)打上service2的标签,给172.20.10.10(node3)打上service3的标签
docker node update --label-add service1=true kjkv0vkbsygyfwxji46m14izj
docker node update --label-add service2=true lk8j9ob4y345u34msh32faufz
docker node update --label-add service3=true jgvug4jn676k2fpfvgy4rgeui
查看标签
docker node inspect node1
Docker有以下网络类型:
bridge:多由独立container之间的通信
host: 直接使用宿主机的网络,端口也使用宿主机的
overlay:当有多个docker主机时,跨主机的container通信
macvlan:每个container都有一个虚拟的MAC地址
none: 禁用网络
默认网络
Docker在默认情况下,分别会建立一个bridge、一个host和一个none的网络:
可以看到,driver类型为bridge的网络的名字也为bridge。在默认情况下,container都是使用的这个bridge的网络,此时container是可以访问外网和其他container的(需要通过IP地址)。
默认的名为bridge的网络是有很多限制的,为此,我们可以自行创建bridge类型的网络。默认的bridge网络与自建bridge网络有以下区别:
端口不会自行发布,必须使用-p参数才能为外界访问,而使用自建的bridge网络时,container的端口可直接被相同网络下的其他container访问。
container之间的如果需要通过名字访问,需要--link参数,而如果使用自建的bridge网络,container之间可以通过名字互访。
我们这里要在docker swarm控制机上执行
docker network create -d overlay --attachable sid
Service 方式
docker service create \
--name service1 \
--constraint 'node.labels.service1 == true' \
service1
Stack 方式
这里是使用的这种方式
这个文件主要是jenkins上面执行,对service1 service2 service3制作镜像并且推送到docker registry自己部署私有镜像中心里面去
--- #固定格式
- hosts: swarmserver #定义需要执行主机
gather_facts: no #不收集facts
tasks: #定义一个任务的开始
- name: build components docker images #定义任务的名称
shell: "mvn docker:build docker:push -Ddocker.registry.address={{registry_address}} -Dgit.branch={{build_version}}"
args:
chdir: "/app/jenkins/workspace/test-docker-ansible-docker-swarm/{{item}}"
with_items:
- service1
- service2
- service3
这个文件是ansible控制机上面会执行,主要是通过ansible控制/etc/ansible/hosts中配置的service1的机器从docker registry私有的镜像中心里面拉取service1应用的镜像。service2 service3类似。。
控制每台机器创建相应的日志路径
控制swarmdeployserver机器(就是swarm的leader那台机器)使用stack方式根据compose.yml文件在docker swarm集群中发布应用
--- #固定格式
- hosts: service1,service2,service3 #定义需要执行主机
gather_facts: no #不收集facts
tasks: #定义一个任务的开始
# 拉镜像
- name: pull docker images
shell: "docker pull {{registry_address}}/sid/{{item}}:{{build_version}}"
with_items:
- service1
- service2
- service3
- name: Create DockerHost EnvFile
shell: |
echo "" > /etc/dockerhost.env
echo "export dockerhost=\"`ifconfig ens33 | grep 'inet' | head -n 1 | awk '{print $2}'`\"" >> /etc/dockerhost.env
echo "export hostOriginName=\"`cat /etc/hostname`\"" >> /etc/dockerhost.env
- hosts: service1
gather_facts: no
tasks:
- name: create log directory
file:
dest: "{{item}}"
mode: 666
state: directory
with_items:
- /log/service1/log
- hosts: service2
gather_facts: no
tasks:
- name: create log directory
file:
dest: "{{item}}"
mode: 666
state: directory
with_items:
- /log/service2/log
- hosts: service3
gather_facts: no
tasks:
- name: create log directory
file:
dest: "{{item}}"
mode: 666
state: directory
with_items:
- /log/service3/log
# 删除sid_components栈
- hosts: swarmserver
gather_facts: no
tasks:
- name: Debug
debug:
msg: components deploy work directory {{work_dir}}
- name: Clean Components Deloy Workspace
file:
dest: "{{work_dir}}/components"
state: absent
- name: Create Deploy Workspace
file:
dest: "{{work_dir}}/components"
mode: 0755
state: directory
- name: Swarm Remove Components Services
shell: docker stack rm sid_components
# 在leader节点上使用swarm发布java组件
- hosts: swarmserver
gather_facts: no
roles:
- role: swarm
# 删除无用的镜像
- hosts: service1,service2,service3
gather_facts: no
tasks:
- name: Delete Useless Docker Images
shell: "docker ps -a |grep Exited |grep sid_components |awk '{print $1}'|xargs -r docker rm \
&& \
docker images |grep none |grep {{registry_address}} |awk '{print $3}'|xargs -r docker rmi "
ignore_errors: yes
---
##swarm
- name: Copy Swarm Compose.yml
template:
src: compose.yml
dest: "{{work_dir}}/components/compose.yml"
- name: Swarm Deploy
shell: docker stack deploy -c compose.yml sid_components
args:
chdir: "{{work_dir}}/components"
version: "3.3"
services:
service1:
image: "{{registry_address}}/sid/service1:{{build_version}}"
networks:
- sid
ports:
- 8095:8095
volumes:
- /etc/localtime:/etc/localtime
- /log/service1/log:/app/log
- /etc/dockerhost.env:/etc/dockerhost.env
logging:
driver: "json-file"
options:
max-size: "50m"
environment:
- "TZ=Asia/Shanghai"
- "jasypt.encryptor.password={{encryptor_password}}"
deploy:
mode: global
placement:
constraints: [node.labels.service1 == true]
restart_policy:
condition: on-failure
service2:
image: "{{registry_address}}/sid/service2:{{build_version}}"
networks:
- sid
ports:
- 8096:8096
volumes:
- /etc/localtime:/etc/localtime
- /log/service2/log:/app/log
- /etc/dockerhost.env:/etc/dockerhost.env
logging:
driver: "json-file"
options:
max-size: "50m"
environment:
- "TZ=Asia/Shanghai"
- "jasypt.encryptor.password={{encryptor_password}}"
deploy:
mode: global
placement:
constraints: [node.labels.service2 == true]
restart_policy:
condition: on-failure
service3:
image: "{{registry_address}}/sid/service3:{{build_version}}"
networks:
- sid
ports:
- 8097:8097
volumes:
- /etc/localtime:/etc/localtime
- /log/service3/log:/app/log
- /etc/dockerhost.env:/etc/dockerhost.env
logging:
driver: "json-file"
options:
max-size: "50m"
environment:
- "TZ=Asia/Shanghai"
- "jasypt.encryptor.password={{encryptor_password}}"
deploy:
mode: global
placement:
constraints: [node.labels.service3 == true]
restart_policy:
condition: on-failure
networks:
sid:
external:
name: sid
---
deploy_dir: /app/sid_deploy/deploy
work_dir: /app/sid_deploy/workspace
confs_dir: /app/sid_deploy/confs
#config_username:
#config_password:
---
- hosts: swarmserver
gather_facts: no
vars:
deploy_dir: /app/sid_deploy/deploy
confs_dir: /app/sid_deploy/confs
do_deploy_dir: "{{deploy_dir}}/{{deploy_name}}/{{build_version}}"
tasks:
# ready for deploying
# 创建发布目录
- name: remove deploy directory
file:
dest: "{{do_deploy_dir}}"
state: absent
- name: create deloy file directory
file:
dest: "{{do_deploy_dir}}/files"
mode: 0755
state: directory
- name: create deloy vars directory
file:
dest: "{{do_deploy_dir}}/vars"
mode: 0755
state: directory
# 推送发布脚本
- name: copy ansile scripts
copy:
src: ops/ansible
dest: "{{do_deploy_dir}}"
force: yes
# 推送执行shell脚本
- name: copy shell scripts
copy:
src: ops/bin
dest: "{{do_deploy_dir}}"
force: yes
# 推送执行confs文件,不强制覆盖
- name: copy conf files
copy:
src: ops/confs/
dest: "{{confs_dir}}"
force: no
# 获取配置文件加密秘钥
- name: Read Conf Encrypt Password
shell: |
PASSWORD=`cat {{confs_dir}}/encrypt/password`
echo $PASSWORD
register: encryptorPassword
# 生成变量信息
- name: save the env variables
copy:
content: |
---
registry_address: {{registry_address}}
build_version: {{build_version}}
encryptor_password: {{encryptorPassword.stdout or 'sid'}}
dest: "{{do_deploy_dir}}/vars/vars_deploy.yml"
# do deploy
- name: deploy with ansible-playbook
shell: 'ansible-playbook -e "@{{do_deploy_dir}}/vars/vars_deploy.yml" deploy_components.yml'
args:
chdir: "{{do_deploy_dir}}/ansible"
# when: do_deploy == "yes"
#!/bin/bash
#进入脚本所在目录
workdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $workdir
bin_dir=$(pwd)
vars_dir=$bin_dir/../vars
ansible_dir=$bin_dir/../ansible
ansible-playbook -e "@$vars_dir/vars_deploy.yml" $ansible_dir/deploy_components.yml
#!/bin/bash
JOB_NAME=$0
INVENTORY_FILE=$1
DOCKER_SWARM_LEADER_HOST=$2
GIT_BRANCH=$3
DOCKER_REGISTRY_ADDRESS=$4
##-- 初始化工作空间
WORKSPACE="/app/jenkins/workspace/test-docker-ansible-docker-swarm"
INVENTORY_FILE=inventory_${JOB_NAME##*/}
echo "" > $INVENTORY_FILE
echo "[swarmserver]" > $INVENTORY_FILE
echo "$DOCKER_SWARM_LEADER_HOST" >> $INVENTORY_FILE
echo "WORKSPACE: $WORKSPACE"
##-- 从分支名获取版本
BUILD_BRANCH=`echo ${GIT_BRANCH} | cut -d'/' -f 2-`
echo "BUILD_BRANCH: $BUILD_BRANCH"
##-- Change to workspace
cd $WORKSPACE
#else
##全量构建
echo "全量构建"
cp ops/ansible/deploy_build.yml deploy_build.yml
#ansible-playbook -i $INVENTORY_FILE -e "registry_address=$DOCKER_REGISTRY_ADDRESS \
# build_version=$BUILD_BRANCH \
# build_workspace=$WORKSPACE" deploy_build.yml
ansible-playbook -e "registry_address=$DOCKER_REGISTRY_ADDRESS \
build_version=$BUILD_BRANCH \
build_workspace=$WORKSPACE" deploy_build.yml
#fi
##-- 发布
cp ops/ansible/deploy_initialize.yml deploy_initialize.yml
#ansible-playbook -i $INVENTORY_FILE -e "registry_address=$DOCKER_REGISTRY_ADDRESS \
# build_version=$BUILD_BRANCH \
# deploy_name=sid_components" deploy_components.yml
ansible-playbook -e "registry_address=$DOCKER_REGISTRY_ADDRESS \
build_version=$BUILD_BRANCH \
deploy_name=sid_components" deploy_initialize.yml
#-- Clean
rm -rf deploy_components.yml
rm -rf deploy_build.yml
rm -rf $INVENTORY_FILE
#!/bin/bash
#打印maven版本
mvn -v
#打印java版本
java -version
父pom.xml
4.0.0
com.sid
test-docker
pom
1.0-SNAPSHOT
service1
service2
service3
org.springframework.boot
spring-boot-starter-parent
2.0.5.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-web
org.codehaus.janino
janino
3.0.6
service1的pom.xml
test-docker
com.sid
1.0-SNAPSHOT
4.0.0
service1
org.springframework.boot
spring-boot-starter-web
org.codehaus.janino
janino
3.0.6
org.springframework.boot
spring-boot-maven-plugin
com.spotify
docker-maven-plugin
1.0.0
${docker.registry.address}/sid/service1
${git.branch}
${basedir}/docker
/
${project.build.directory}
${project.build.finalName}.jar
Dockerfile
FROM centos:7
RUN yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel vorbis-tools
RUN mkdir -p /app/log
COPY service1-1.0-SNAPSHOT.jar /service1-1.0-SNAPSHOT.jar
EXPOSE 8005/tcp
CMD ["/bin/bash", "-c", "cat /etc/dockerhost.env >> /etc/profile && source /etc/profile && java -jar /service1-1.0-SNAPSHOT.jar"]
TestController.java
package com.sid.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(value = "/test")
public class TestController {
private final static Logger logger = LoggerFactory.getLogger(TestController.class);
@Value("${hostOriginName}")
String hostname;
@RequestMapping(value = "/select")
@ResponseBody
public String select(){
logger.info("hostname : {}",hostname);
return "service1 select";
}
}
App1.java
package com.sid;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App1 {
public static void main(String[] args) {
SpringApplication.run(App1.class, args);
}
}
application.yml
server:
port: 8095
servlet:
context-path: /service1
spring:
mvc:
view:
prefix: /
suffix: .html
# log config
logging:
config: classpath:logback-spring.xml
删除标签
docker node update --label-rm env node1
运行compose.yml文件
$ docker stack deploy -c docker-compose.yml sid_components
删栈
$ docker stack rm sid_components
某台机器彻底退出swarm集群
docker swarm leave --force