一、前言
为了使用Jenkins实现混合部署,经调研采用当前热门且社区强大的ansible工具,以下是环境搭建过程。下面是本人在windows10 1803操作系统上的实验记录,操作系统不同,实验步骤可能略有区别,各位小伙伴要特别留意啊。
二、实验步骤
1.环境搭建
重要的事情说三遍!!!网络类型必须为专用网,且关闭防火墙。
鉴于ansible服务端(一般部署在linux发行版操作系统上)与windows操作系统进行数据传输,使用到winrm服务。因为win10中已经内置了winrm,因此只需要开启winrm服务并完成相关配置,步骤如下:
图 winrm配置示意图
传送门在此^-^
winrm service
winrm enumerate winrm/config/listener
winrm quickconfig
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
2.增加配置
CI/CD流水线作业依赖于ansible-playbook脚本,需要增加ansible主机映射,主要包括加入新的IP地址,用户凭证和传输方式等配置。Ansible该文件,默认位于控制机/etc/ansible/hosts路径下,根据该文件中其它范式配置,加入需要做CI的主机名称和变量的相关配置,如下:
#受控机主机ip
[host1]
192.168.70.51
#受控机主机相关配置
[host1:vars]
#用户名密码
ansible_ssh_user='administrator'
ansible_ssh_pass='Yanfa_1304'
#默认通信端口
ansible_ssh_port=5985
ansible_connection='winrm'
ansible_winrm_transport='ntlm'
#不使用身份有效性验证,仅使用用户名和密码
ansible_winrm_server_cert_validation=ignore
图 配置ansible受控机信息
3.编写ansible脚本
可以出于安全性考虑(只是建议啊),将ansible脚本内容对jenkins配置管理人员黑盒。本实验调用分为三层,第一层为调用层,第二层中间层,第三层为业务层,仅供参考。
首先,调用层脚本放置于jenkins配置中,其中使用到了jenkins管理员定义的全局变量,如下:
/home/jenkins/ci/${ANSIBLE_HOST_NAME_VXSIP}/are-agent/exec-are-agent.sh ${ANSIBLE_HOST_NAME_ARE} ${JOB_NAME}
图 调用层脚本
然后,中间层可以配置一些公共的非业务逻辑,如下:
#!/bin/sh
echo "当前项目路径:/home/jenkins/.jenkins/workspace/$2"
echo "当前ansible主机:$1"
ansible-playbook -e "host_name=$1 service_name=project1 project_name=$2" /home/jenkins/ci/$1/common-server.yml
图 中间层脚本
最后,是业务层,是ansible-playbook脚本,主要是CI/CD相关业务逻辑。
---
- hosts: "{{ host_name }}" #目标机器的主机名
gather_facts: no #不收集facts
# 变量引用
vars_files:
- ./common_vars.yml
vars:
- exec_path: "{{ remote_server_exec_path }}/{{ service_name }}"
- src_path: "{{ compile_file_path }}/{{ project_name }}/target"
- src_conf_path: "{{ compile_file_path }}/{{ project_name }}/deploy/windows-undocker"
tasks:
#获取当前时间
- name: get current timestamp
win_shell: "Get-Date -Format 'yyyyMMddHHmmssfff'"
register: current_date
#停止和卸载远程服务(如果存在*.exe并且存在该windows服务)
- name: stop and uninstall remote {{ service_name }} server
win_shell: 'if (( test-path {{ exec_path }}/{{ service_name }}.exe ) -and (Get-Service {{ service_name }} -ErrorAction SilentlyContinue)) { (sc.exe query {{ service_name }} |findstr /i "state" |findstr /i "running") -and (net stop {{ service_name }}); {{ exec_path }}/{{ service_name }}.exe uninstall; }'
#ignore_errors: True
#判断是否存在dependency,存在则操作
- name: justify if exists {{ src_path }}/dependency folder
shell: 'ls {{ src_path }}/dependency'
register: dependency_folder_exists_flag
delegate_to: localhost
ignore_errors: True
#- debug: var=dependency_folder_exists_flag
#拷贝target/dependency中的依赖到临时交换目录下面
- name: copy dependency folder from path {{ src_path }}/dependency/ to exec path {{ remote_server_exec_path }}/dependency_{{ current_date.stdout_lines[0] }}
win_copy: "src='{{ src_path }}/dependency/' dest='{{ remote_server_exec_path }}/dependency_{{ current_date.stdout_lines[0] }}/'"
ignore_errors: True
#succeeded 和 failed
#忽略错误因为dependency可能不存在
when: dependency_folder_exists_flag.failed == false
#如果控制机有dependency目录,而且受控机没有dependency目录,此时需要在受控机新建dependency目录
- name: create dependency folder in path {{ remote_server_exec_path }}/ if it is not exists
win_shell: 'if (( test-path {{ remote_server_exec_path }}/dependency ) -ne "True" ) {new-item -path "{{ remote_server_exec_path }}/" -name "dependency" -type directory }'
ignore_errors: True
#succeeded 和 failed
#忽略错误因为dependency可能不存在
when: dependency_folder_exists_flag.failed == false
#中转文件夹到dependency的文件合并
- name: combine dependency folder from path {{ remote_server_exec_path }}/dependency_{{ current_date.stdout_lines[0] }}/ to path {{ remote_server_exec_path }}/dependency/
#win_shell: "if (Test-Path {{ remote_server_exec_path }}/dependency_{{ current_date.stdout_lines[0] }}) {copy-item -Force '{{ remote_server_exec_path }}/dependency_{{ current_date.stdout_lines[0] }}/*' '{{ remote_server_exec_path }}/dependency/'}"
win_shell: 'if (Test-Path {{ remote_server_exec_path }}/dependency_{{ current_date.stdout_lines[0] }}) { $source_path="{{ remote_server_exec_path }}/dependency_{{ current_date.stdout_lines[0] }}/";$dest_path="{{ remote_server_exec_path }}/dependency/";$filelist=Get-ChildItem $source_path;$filelist | foreach { Copy-Item -Path $source_path$_ -Destination $dest_path -Force };}'
# win_shell: 'if (Test-Path {{ remote_server_exec_path }}/dependency_{{ current_date.stdout_lines[0] }}) { $source_path={{ remote_server_exec_path }}/dependency_{{ current_date.stdout_lines[0] }};$dest_path={{ remote_server_exec_path }}/dependency/;$filelist=Get-ChildItem $source_path;$filelist | foreach { try{Copy-Item -Path $source_path$_ -Destination $dest_path -Force }catch{}};}'
#当复制时,遇到被占用的jar包将会报错,但是该错误并不会影响后续其它文件的传输(本质为相同文件的略过)
ignore_errors: True
#register: combine_result
#- debug: var=combine_result
#删除用于中转的dependency临时文件夹
- name: delete the temp exchange folder with {{ remote_server_exec_path }}/dependency_{{ current_date.stdout_lines[0] }}
win_shell: "if (Test-Path {{ remote_server_exec_path }}/dependency_{{ current_date.stdout_lines[0] }}) {Remove-Item '{{ remote_server_exec_path }}/dependency_{{ current_date.stdout_lines[0] }}' -recurse }"
#清空项目文件夹
- name: clear {{ exec_path }} folder exclude {{ service_name }}.jar and {{ service_name }}_backup_*.jar
win_shell: "if (Test-Path {{ exec_path }}) { Remove-Item '{{ exec_path }}/*' -Exclude ('{{ service_name }}.jar','{{ service_name }}_backup_*.jar') -recurse -ErrorAction SilentlyContinue; if (!$?) {break;}} "
ignore_errors: True
#检查是否存在当前服务执行文件,如果存在将会备份,如果不存在则会跳过备份task
- name: backup flie {{ service_name }}.jar if it exists
win_shell: "if (Test-Path {{ exec_path }}/{{ service_name }}.jar) {ren {{ exec_path }}/{{ service_name }}.jar {{ exec_path }}/{{ service_name }}_backup_{{ current_date.stdout_lines[0] }}.jar}"
#判断是否存在bootstrap.yml,存在则操作
- name: justify if exists file {{ src_path }}/bootstrap.yml
shell: 'ls {{ src_conf_path }}/bootstrap.yml'
register: bootstrap_yml_exists_flag
delegate_to: localhost
ignore_errors: True
# - debug: var=bootstrap_yml_exists_flag
#获取远程主机的ip地址
- name: get remote host ipv4 address if it is a dual network it may failed
win_shell: '(ipconfig|select-string "IPv4"|out-string).Split(":")[-1].trim("")'
register: remote_host_ip_address
ignore_errors: True
when: bootstrap_yml_exists_flag.failed == false
#更改bootstrap.yml中的127.0.0.1为远程主机的网卡地址
#- debug: 'msg="replace 127.0.0.1 to remote network address {{ remote_host_ip_address.stdout_lines[0] }} with flie {{ exec_path }}/bootstrap.yml"'
- name: replace 127.0.0.1 to remote network address {{ remote_host_ip_address.stdout_lines[0] }} with flie {{ src_conf_path }}/bootstrap.yml
replace:
path: '{{ src_conf_path }}/bootstrap.yml'
regexp: '127.0.0.1'
replace: '{{ remote_host_ip_address.stdout_lines[0] }}'
delegate_to: localhost
ignore_errors: True
when: bootstrap_yml_exists_flag.failed == false
#拷贝项目目录下的所有配置文件到远程目录下
- name: copy configuration flies from path {{ src_conf_path }} to exec path {{ exec_path }}/
win_copy: "src={{ src_conf_path }}/ dest={{ exec_path }}/"
#拷贝可执行文件到远程目录下
- name: copy {{ service_name }}.jar flie from path {{ src_path }}/{{ service_name }}-{{ version }}.jar to exec path {{ exec_path }}/
win_copy: "src={{ src_path }}/{{ service_name }}-{{ version }}.jar dest={{ exec_path }}/"
#去掉执行文件当前服务版本号
- name: remove server exec flie version {{ exec_path }}/{{ service_name }}-{{ version }}.jar to {{ exec_path }}/{{ service_name }}.jar
win_shell: "ren {{ exec_path }}/{{ service_name }}-{{ version }}.jar {{ exec_path }}/{{ service_name }}.jar"
#安装和启动服务
- name: install and startup remote {{ service_name }} server
win_shell: 'if (( test-path {{ exec_path }}/{{ service_name }}.exe ) -eq "True") { Get-Service {{ service_name }} -ErrorAction SilentlyContinue; if ( !$? ) { {{ exec_path }}/{{ service_name }}.exe install;} else {(sc.exe query {{ service_name }} |findstr /i "state" |findstr /i "running") -and (net stop {{ service_name }}); ({{ exec_path }}/{{ service_name }}.exe uninstall) -and ({{ exec_path }}/{{ service_name }}.exe install);} net start {{ service_name }}; }'
三、实验验证
环境和脚本搭建了这么久,到底行不行,验证一下。执行jenkins job,一方面,我们可以观察构建日志,如图,即可证明脚本执行成功。另一方面,我们可以去受控机查看脚本执行结果。
以上即是本人在jenkins中集成ansible的方案和实操步骤,使用不当还请各位朋友指正!