在项目中编写 Makefile 实现 docker 编译、打包、发布、部署。
要点
shell
- 执行:$(shell
) - 一行一个进程,不同行之间变量不能传递
- 依次执行多行指令,需要用 ; 结尾且在后面用 \
- makefile 变量以 $ 开头,shell 变量以 $$ 开头
命令行前缀
-n 执行时只显示所要执行的命令,但不会真正的执行这个命令,其中包括了使用的 @ 字符开始的命令,通常用于检查编写的 Makefile 内容。
- -s 则是禁止所有的执行命令的显示,就好像所有的命令行都使用 @ 开始一样。
- 传参
########################### 环境变量
host=$(hostname) ifeq ($(host), prod)
# 环境变量赋值
else ifeq ($(host), pre) # 开发/测试环境
# 环境变量赋值
else env=””
# exit 1 # 不能直接退出
endif
—————————————————————————————————————
all: # 环境不正确:退出 if [ ${env} == “” ]; then \ exit 1; \ endif
build: docker build . -t ${REGISTRY}/${IMAGE}:${TAG}
push: build docker push ${REGISTRY}/${IMAGE}:${TAG}
publish: push docker tag ${REGISTRY}/${IMAGE}:${TAG} ${REGISTRY}/${IMAGE}:latest docker push ${REGISTRY}/${IMAGE}:latest
run: clear
########################## docker run
docker run -it -d --name ${IMAGE} \
-e TZ=Asia/Shanghai \ -e crms=oracle://user4read:user4read0725@192.168.52.12:1521/CRMS?charset=utf8 \ -v /
————————————————————————————————————————-
########### ssh ${target} docker run # 指定服务器部署
ssh ${target} "docker run -it -d --name ${IMAGE} \
-e TZ=Asia/Shanghai \ -v /
————————————————————————————————————————-
clear: docker rm -f ${IMAGE}; docker rmi -f ${REGISTRY}/${IMAGE}:${TAG}
.PHONY: build push publish run clear
.PHONY: exit exit: @echo ${REGISTRY}/${IMAGE}:${TAG}
<a name="Is7dd"></a>
### make + deploy.sh + docker-compose.yml
> 将上面 run 中的 docker run 改为 sh deploy.sh
```shell
## ETF ##############################
if [ $(hostname) == 'pre' ]; then
etf_sftp_cfg='/data/optiver_etf/'
elif [ $(hostname) == 'pro' ]; then
etf_sftp_cfg='/home/settlement/op_etf/'
else
echo 'hostname is not pre or pro !!!'
exit 1
fi
IMAGE=$(shell basename ${PWD})
#TAG=$(shell git describe --always --tags)
TAG=$(shell date '+%Y%m%d')
REGISTRY=hub.haifengat.com
cat > docker-compose.yml << EOF
version: "3.7"
services:
op_etf:
image: ${REGISTRY}/${IMAGE}:${TAG}
container_name: op_etf
restart: always
environment:
TZ: Asia/Shanghai
investor: '010166'
ora_crms: 'oracle://use:use725@192.168.52.12:1521/CRMS'
sftp_cfg: '${etf_sftp_cfg}'
EOF
docker-compose up -d
比较
docker 方式
- 只需要一个文件,方便维护
- docker 部署需要与 portainer 配合进行管理
- 每次部署均需删除上次的镜像,以获取最新版本
- 远程部署需将 Makefile 传到应用服务器,以执行脚本
docker-compose 方式
- 需要 Makefile 和 deploy.sh
- 部署时传递文件 deploy.sh 并执行
- 不需要清除之前的部署
- 每个应用需要有自己的目录以保存 docker-compose.yml
相同点
- 都要传递文件到应用服务器
- 脚本要在应用服务器上执行
以docker方式部署,将Makefile文件复制到/tmp目录下执行即可,配合jenkins + portainer管理实现自动化部署。
示例
Dockerfile for golang
# FROM hub.haifengat.com/haifengat/oracle_base:19
# 最小化镜像
FROM hub.haifengat.com/library/busybox:glibc
ARG AppName
WORKDIR /app
COPY ./Shanghai /usr/share/zoneinfo/Asia/
RUN ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
ENV TZ=Asia/Shanghai
COPY ./${AppName} ./
# 基础依赖库
# COPY lib64 /lib64
# ENV LD_LIBRARY_PATH /app:\$LD_LIBRARY_PATH
ENTRYPOINT ["./${AppName}"]
Makefile for golang
.DEFAULT_GOAL := help
# 以目录名作为镜像,应用文件名
CURDIRNAME?=$(shell basename ${CURDIR})
# 可用参数定义 CURDATE 的值
CURDATE?=$(shell date '+%Y%m%d')
REGISTRY?=hub.haifengat.com/jrcxb
#===================================== git ================================#
gitpush: # git 代码提交并推送
@if [ ! $M ]; then \
echo "add param: M=<your comment for this commit>"; \
exit 1; \
fi
git commit -a -m "${M}"
git push origin
tag: # 添加标签(当前日期)
# 删除当前日期的 tag
- git tag -d ${CURDATE}
- git push origin :refs/tags/${CURDATE}
git tag -a ${CURDATE} -m "$(shell git log -1 --pretty=%B)" # 最后提交注释作为tag注释
git push origin --tags
#------------------------------------------------------------------------#
#================================= 镜像处理 =============================#
build: # 编译 打包镜像
go build -o ${CURDIRNAME}
@# 替换容器中的 appName 以解决 entrypoint 启动程序不能用变量的问题
sed -i 's#ENTRYPOINT.*#ENTRYPOINT ["./${CURDIRNAME}"]#g' Dockerfile
docker build . -t ${REGISTRY}/${CURDIRNAME}:${CURDATE} --no-cache --build-arg AppName=${CURDIRNAME}
docker: build # 镜像推送
docker push ${REGISTRY}/${CURDIRNAME}:${CURDATE}
publish: docker # 镜像发送 latest
docker tag ${REGISTRY}/${CURDIRNAME}:${CURDATE} ${REGISTRY}/${CURDIRNAME}:latest
docker push ${REGISTRY}/${CURDIRNAME}:latest
#--------------------------------------------------------------------------#
#=================================== 部署 =====================================#
# 应用服务器 pre/prod 免密登录
rm: # 清除原有容器和同版本镜像(报错则忽略),记录部署日志
- ssh ${MAKECMDGOALS} "docker rm -f ${CURDIRNAME}; docker rmi -f ${REGISTRY}/${CURDIRNAME}:${CURDATE};"
echo "${MAKECMDGOALS} $(shell date '+%Y/%m/%d %H:%M:%S') ${REGISTRY}/${CURDIRNAME}:${CURDATE}" >> deploy_log
pre: rm # --restart=always 参数会导致重复报警,且 portainer 不能及时显示停止状态。
ssh ${MAKECMDGOALS} "docker run -it -d --name ${CURDIRNAME} \
-e TZ=Asia/Shanghai \
--net=jcbnet \
-e wxchatURL='http://192.168.52.201:31099/test/txt' \
-p 8888:80 \
-v /data/${CURDIRNAME}:/app/data \
${REGISTRY}/${CURDIRNAME}:${CURDATE}"
prod: rm
ssh ${MAKECMDGOALS} "docker run -it -d --name ${CURDIRNAME} \
-e TZ=Asia/Shanghai \
--net=jcbnet \
-e wxchatURL='http://192.168.52.231:31099/${CURDIRNAME}/txt' \
-p 8888:80 \
-v /data/${CURDIRNAME}:/app/data \
${REGISTRY}/${CURDIRNAME}:${CURDATE}"
#--------------------------------------------------------------------------#
.PHONY: gitpush tag
.PHONY: build docker publish
.PHONY: pre prod
.PHONY: help
help:
@echo 'git提交并推送: make gitpush M="提交说明"'
@echo '创建tag(当前日期): make tag'
@echo '镜像生成并推送: make docker'
@echo '镜像发送latest: make publish'
@echo '直接部署预生产: make pre'
@echo '直接部署生产: make prod'
@echo 'make -n 检查语法'
@echo 'make xxxx CURDATE=yyyymmdd 指定日期(版本)'
教程
规则
- 每条规则中的命令和操作系统Shell的命令行是一致的。
- make会按顺序一条一条的执行命令,命令如果单起一行,必须以Tab键开头,如果不单起一行,用分号;规则分开。
内置变量
```makefile $@ 目标文件集 $(@D) 目标文件路径的目录部分 $% 仅当目标文件时一个静态库文件时,代表一个静态库的一个成员名 $< 第一个依赖文件名 $? 所有比目标文件新的依赖文件列表。 如果目标是静态库文件,代表库成员。 $^ 所有依赖文件列表 $+ 所有依赖文件列表,保留了依赖文件中重复出现的文件 $ 目标模式中%代表的部分(“茎”),文件名中存在目录时,也包含目录部分 $(F) 目标“茎”中的文件名部分 $(*D) 目标”茎”中的目录部分 用在command前面,忽略命令执行时的错误。如果发生错误,继续执行makefile 随机编号 vpath 选择性搜索,为符合pattern的文件指定搜索路径dir,vpath pattern dir CURDIR make的工作目录 MAKEFLAGS 命令行选项
<a name="iyhUc"></a>
## 语法
```css
空格 在makefile中不要为了美观而进行缩进,或者使用空格间隔变量,空格是makefile语法的一部分
:= 定义直接展开式变量
?= 如果变量未定义则赋予后面的值,否则什么都不作
= 定义递归展开式变量
@ 规则的命令行以@开头,表示不打印出该条命令
unexport 不将变量传递给子makefile
export: 当一个变量使用export进行声明后,变量和它的值将被加入到当前工作的环境变量中,以后make执行的所有规则的命令都可以使用这个变量。
SHELL 特殊变量,默认传递给子makefile,注意该变量没有使用系统环境变量中的定义,GNUmake默认值为/bin/sh
MAKEFLAGS 最上层make的命令行选项会被自动的通过环境变量MAKEFLAGS传递给子make进程。
MAKEFILES make执行时首先将此变量的值作为需要读入的Makefile文件
MAKECMDGOALS 记录命令行参数指定的终极目标列表
VPATH 依赖文件搜索路径,用":"分隔
多目标规则 一个文件可以作为作为多个规则的目标(多个规则中只能有一个规则定义命令)。以这个文件为目标的规则的所有依赖文件将会被合并成此目标的一个依赖文件列表。
.PHONY:name 将name声明为伪目标,伪目标指的是不真正生成目标文件,只是为了执行后面的指令
.SILENT 创建.SILENT依赖列表中的文件时,不打印重建这些文件时使用的命令,例如 .SILENT : all
$()和${} 取出变量值
$$ 表示一个$符号,因为$在makefile中具有特殊含义,所以要使用$字符时,需要使用$$,类似于C语言中的转义字符
override 不使用命令行中定义的同名变量替代该变量,对使用override定义的变量追加值时,也需要使用override
define 多行定义,如下varname是变量名,value1 value2是分行写的value的组成部分,以endef结束
define varname
value1
value2
endef
ifeq 判断关键字是否相等,四种格式:
ifeq (ARG1, ARG2)
ifeq 'ARG1' 'ARG2
ifeq "ARG1" 'ARG2'
ifeq 'ARG1' "ARG2"
shell command 返回command命令在shell中执行的结果(类似于shell脚本中的`command`),注意大小写的含义不同,大写的SHELL是一个特殊变量
export 将变量添加到当前工作环境,传递给子makefile,不覆盖子makefile中的同名变量(除非是-e选项)。没有使用export的变量(除了一些特殊变量)不传递给子makefile
单行命令与多行命令的区别
makefile的规则的命令行中,每一行命令在一个独立的子shell进程中被执行。
上一行中使用的cd命令不会改变下一行命令的工作目录。可以使用\将一行命令多行排版(与C语言中的\符号作用类似)
.d文件 GNU组织建议为每一个“name.c”的文件都生成一个“name.d'的文件,存放.c文件的依赖关系
%.d: %.c
@set -e; rm -f $@; \
gcc -MM $< > $@.$$$$; \
sed 's/\($*\)\.o[ :]*\1.o $@ :/g' < $@.$$$$ > $@; \
rm -f $@.$$$$
FORCE 没有依靠关系也没有命令的规则被认为总是新的,如下面的FORCE
clean:FORCE
rm *.o
FORCE:
include make暂停读取当前的Makefile,转去读取include指定的一个或者多个文件,完成以后继续当前Makefile的读取
内置函数
origin $(origin VARIABLE) 查询变量VARIABLE的出处 返回值: 1 undefined 2 default 3 environment 4 environment override 5 file 6 command line 7 override 8 automatic if $(if CONDITION,THEN-PART[,ELSE-PART]) filter-out $(filter-out PATTERN...,TEXT) 保留TEXT中不符合PATTERN的内容 filter $(filter PATTERN...,TEXT) 取出TEXT中符合PATTERN的内容 dir 取目录函数 notdir 取文件名函数 subst $(subst from,to,text) 把text中的from替换成to call $(call VARIABLE,PARAM,PARAM), 参数将会依次赋值给临时变量$(1) $(2)等 firstword firstword NAMES 取首单词函数 wildcard $(wildcard PATTERN) 获取当前目录下符合模式PATTERN的文件名 patsubst $(patsubst PATTERN,REPLACEMENT, TEXT) 把TEXT中以空格分隔的、符合模式PATTERN单词替换为REPLACEMENT error 产生致命错误, $(error Text) $(VAR:x=y) 把var中符合%x的替换成%y strip 去掉行首和行尾的空字符和行中重复的空字符 findstring $(findstring FIND,IN) 在IN中存在FIND返回FIND,否则返回空 foreach $(foreach var,list.text) 展开var和list,不展开text,依次把list中空格分割的值赋给var,然后执行text。类似于for循环。 eval $(eval XX) eval将XX展开一次,Makefile会将XX再次展开一次。 sort $(sort XX XX XX) 排序和去掉重复的内容 words $(words, XX XX) 统计单词个数
控制结构
```makefile ifeq(XXX,XXX) command else command endif
注意command前面不要有TAB键!
<a name="nJBME"></a>
## 不依赖任何文件
```makefile
$(CRUDIR)/Makefile Makefile: ;