在项目中编写 Makefile 实现 docker 编译、打包、发布、部署。

要点

shell

  • 执行:$(shell )
  • 一行一个进程,不同行之间变量不能传递
  • 依次执行多行指令,需要用 ; 结尾且在后面用 \
  • makefile 变量以 $ 开头,shell 变量以 $$ 开头
  • 命令行前缀

    • 无前缀,输出执行的命令以及命令执行的结果, 出错的话停止执行
    • 前缀 @,只输出命令执行的结果, 出错的话停止执行
    • 前缀 -,命令执行有错的话, 忽略错误, 继续执行

      make

  • -n 执行时只显示所要执行的命令,但不会真正的执行这个命令,其中包括了使用的 @ 字符开始的命令,通常用于检查编写的 Makefile 内容。

  • -s 则是禁止所有的执行命令的显示,就好像所有的命令行都使用 @ 开始一样。
  • 传参
    • make 变量=参数值
    • makefile 中用 ?=,在不传参时赋默认值

      部署方式

      make + docker

      实现 docker 的 build、push、run ```makefile IMAGE=$(shell basename ${PWD})

      TAG=$(shell git describe —always —tags)

      TAG=$(shell date ‘+%Y%m%d’) REGISTRY=hub.haifengat.com

      传参 make env=pre/prod/test

      env?=应用服务器orIP(ssh 免密登录)
########################### 环境变量

host=$(hostname) ifeq ($(host), prod)

  1. # 环境变量赋值

else ifeq ($(host), pre) # 开发/测试环境

  1. # 环境变量赋值

else env=””

  1. # 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
  1. docker run -it -d --name ${IMAGE} \

-e TZ=Asia/Shanghai \ -e crms=oracle://user4read:user4read0725@192.168.52.12:1521/CRMS?charset=utf8 \ -v /

: \ -p : \ ${REGISTRY}/${IMAGE}:${TAG}

————————————————————————————————————————-

########### ssh ${target} docker run # 指定服务器部署
  1. ssh ${target} "docker run -it -d --name ${IMAGE} \

-e TZ=Asia/Shanghai \ -v /

: \ -p : \ ${REGISTRY}/${IMAGE}:${TAG}”

————————————————————————————————————————-

clear: docker rm -f ${IMAGE}; docker rmi -f ${REGISTRY}/${IMAGE}:${TAG}

.PHONY: build push publish run clear

.PHONY: exit exit: @echo ${REGISTRY}/${IMAGE}:${TAG}

  1. <a name="Is7dd"></a>
  2. ### make + deploy.sh + docker-compose.yml
  3. > 将上面 run 中的 docker run 改为 sh deploy.sh
  4. ```shell
  5. ## ETF ##############################
  6. if [ $(hostname) == 'pre' ]; then
  7. etf_sftp_cfg='/data/optiver_etf/'
  8. elif [ $(hostname) == 'pro' ]; then
  9. etf_sftp_cfg='/home/settlement/op_etf/'
  10. else
  11. echo 'hostname is not pre or pro !!!'
  12. exit 1
  13. fi
  14. IMAGE=$(shell basename ${PWD})
  15. #TAG=$(shell git describe --always --tags)
  16. TAG=$(shell date '+%Y%m%d')
  17. REGISTRY=hub.haifengat.com
  18. cat > docker-compose.yml << EOF
  19. version: "3.7"
  20. services:
  21. op_etf:
  22. image: ${REGISTRY}/${IMAGE}:${TAG}
  23. container_name: op_etf
  24. restart: always
  25. environment:
  26. TZ: Asia/Shanghai
  27. investor: '010166'
  28. ora_crms: 'oracle://use:use725@192.168.52.12:1521/CRMS'
  29. sftp_cfg: '${etf_sftp_cfg}'
  30. EOF
  31. 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

  1. # FROM hub.haifengat.com/haifengat/oracle_base:19
  2. # 最小化镜像
  3. FROM hub.haifengat.com/library/busybox:glibc
  4. ARG AppName
  5. WORKDIR /app
  6. COPY ./Shanghai /usr/share/zoneinfo/Asia/
  7. RUN ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
  8. ENV TZ=Asia/Shanghai
  9. COPY ./${AppName} ./
  10. # 基础依赖库
  11. # COPY lib64 /lib64
  12. # ENV LD_LIBRARY_PATH /app:\$LD_LIBRARY_PATH
  13. ENTRYPOINT ["./${AppName}"]

Makefile for golang

  1. .DEFAULT_GOAL := help
  2. # 以目录名作为镜像,应用文件名
  3. CURDIRNAME?=$(shell basename ${CURDIR})
  4. # 可用参数定义 CURDATE 的值
  5. CURDATE?=$(shell date '+%Y%m%d')
  6. REGISTRY?=hub.haifengat.com/jrcxb
  7. #===================================== git ================================#
  8. gitpush: # git 代码提交并推送
  9. @if [ ! $M ]; then \
  10. echo "add param: M=<your comment for this commit>"; \
  11. exit 1; \
  12. fi
  13. git commit -a -m "${M}"
  14. git push origin
  15. tag: # 添加标签(当前日期)
  16. # 删除当前日期的 tag
  17. - git tag -d ${CURDATE}
  18. - git push origin :refs/tags/${CURDATE}
  19. git tag -a ${CURDATE} -m "$(shell git log -1 --pretty=%B)" # 最后提交注释作为tag注释
  20. git push origin --tags
  21. #------------------------------------------------------------------------#
  22. #================================= 镜像处理 =============================#
  23. build: # 编译 打包镜像
  24. go build -o ${CURDIRNAME}
  25. @# 替换容器中的 appName 以解决 entrypoint 启动程序不能用变量的问题
  26. sed -i 's#ENTRYPOINT.*#ENTRYPOINT ["./${CURDIRNAME}"]#g' Dockerfile
  27. docker build . -t ${REGISTRY}/${CURDIRNAME}:${CURDATE} --no-cache --build-arg AppName=${CURDIRNAME}
  28. docker: build # 镜像推送
  29. docker push ${REGISTRY}/${CURDIRNAME}:${CURDATE}
  30. publish: docker # 镜像发送 latest
  31. docker tag ${REGISTRY}/${CURDIRNAME}:${CURDATE} ${REGISTRY}/${CURDIRNAME}:latest
  32. docker push ${REGISTRY}/${CURDIRNAME}:latest
  33. #--------------------------------------------------------------------------#
  34. #=================================== 部署 =====================================#
  35. # 应用服务器 pre/prod 免密登录
  36. rm: # 清除原有容器和同版本镜像(报错则忽略),记录部署日志
  37. - ssh ${MAKECMDGOALS} "docker rm -f ${CURDIRNAME}; docker rmi -f ${REGISTRY}/${CURDIRNAME}:${CURDATE};"
  38. echo "${MAKECMDGOALS} $(shell date '+%Y/%m/%d %H:%M:%S') ${REGISTRY}/${CURDIRNAME}:${CURDATE}" >> deploy_log
  39. pre: rm # --restart=always 参数会导致重复报警,且 portainer 不能及时显示停止状态。
  40. ssh ${MAKECMDGOALS} "docker run -it -d --name ${CURDIRNAME} \
  41. -e TZ=Asia/Shanghai \
  42. --net=jcbnet \
  43. -e wxchatURL='http://192.168.52.201:31099/test/txt' \
  44. -p 8888:80 \
  45. -v /data/${CURDIRNAME}:/app/data \
  46. ${REGISTRY}/${CURDIRNAME}:${CURDATE}"
  47. prod: rm
  48. ssh ${MAKECMDGOALS} "docker run -it -d --name ${CURDIRNAME} \
  49. -e TZ=Asia/Shanghai \
  50. --net=jcbnet \
  51. -e wxchatURL='http://192.168.52.231:31099/${CURDIRNAME}/txt' \
  52. -p 8888:80 \
  53. -v /data/${CURDIRNAME}:/app/data \
  54. ${REGISTRY}/${CURDIRNAME}:${CURDATE}"
  55. #--------------------------------------------------------------------------#
  56. .PHONY: gitpush tag
  57. .PHONY: build docker publish
  58. .PHONY: pre prod
  59. .PHONY: help
  60. help:
  61. @echo 'git提交并推送: make gitpush M="提交说明"'
  62. @echo '创建tag(当前日期): make tag'
  63. @echo '镜像生成并推送: make docker'
  64. @echo '镜像发送latest: make publish'
  65. @echo '直接部署预生产: make pre'
  66. @echo '直接部署生产: make prod'
  67. @echo 'make -n 检查语法'
  68. @echo 'make xxxx CURDATE=yyyymmdd 指定日期(版本)'

教程

规则

  • 每条规则中的命令和操作系统Shell的命令行是一致的。
  • make会按顺序一条一条的执行命令,命令如果单起一行,必须以Tab键开头,如果不单起一行,用分号;规则分开。

    内置变量

    ```makefile $@ 目标文件集 $(@D) 目标文件路径的目录部分 $% 仅当目标文件时一个静态库文件时,代表一个静态库的一个成员名 $< 第一个依赖文件名 $? 所有比目标文件新的依赖文件列表。 如果目标是静态库文件,代表库成员。 $^ 所有依赖文件列表 $+ 所有依赖文件列表,保留了依赖文件中重复出现的文件 $ 目标模式中%代表的部分(“茎”),文件名中存在目录时,也包含目录部分 $(F) 目标“茎”中的文件名部分 $(*D) 目标”茎”中的目录部分
  • 用在command前面,忽略命令执行时的错误。如果发生错误,继续执行makefile 随机编号 vpath 选择性搜索,为符合pattern的文件指定搜索路径dir,vpath pattern dir CURDIR make的工作目录 MAKEFLAGS 命令行选项

    1. <a name="iyhUc"></a>
    2. ## 语法
    3. ```css
    4. 空格 在makefile中不要为了美观而进行缩进,或者使用空格间隔变量,空格是makefile语法的一部分
    5. := 定义直接展开式变量
    6. ?= 如果变量未定义则赋予后面的值,否则什么都不作
    7. = 定义递归展开式变量
    8. @ 规则的命令行以@开头,表示不打印出该条命令
    9. unexport 不将变量传递给子makefile
    10. export: 当一个变量使用export进行声明后,变量和它的值将被加入到当前工作的环境变量中,以后make执行的所有规则的命令都可以使用这个变量。
    11. SHELL 特殊变量,默认传递给子makefile,注意该变量没有使用系统环境变量中的定义,GNUmake默认值为/bin/sh
    12. MAKEFLAGS 最上层make的命令行选项会被自动的通过环境变量MAKEFLAGS传递给子make进程。
    13. MAKEFILES make执行时首先将此变量的值作为需要读入的Makefile文件
    14. MAKECMDGOALS 记录命令行参数指定的终极目标列表
    15. VPATH 依赖文件搜索路径,用":"分隔
    16. 多目标规则 一个文件可以作为作为多个规则的目标(多个规则中只能有一个规则定义命令)。以这个文件为目标的规则的所有依赖文件将会被合并成此目标的一个依赖文件列表。
    17. .PHONY:name 将name声明为伪目标,伪目标指的是不真正生成目标文件,只是为了执行后面的指令
    18. .SILENT 创建.SILENT依赖列表中的文件时,不打印重建这些文件时使用的命令,例如 .SILENT : all
    19. $()和${} 取出变量值
    20. $$ 表示一个$符号,因为$在makefile中具有特殊含义,所以要使用$字符时,需要使用$$,类似于C语言中的转义字符
    21. override 不使用命令行中定义的同名变量替代该变量,对使用override定义的变量追加值时,也需要使用override
    22. define 多行定义,如下varname是变量名,value1 value2是分行写的value的组成部分,以endef结束
    23. define varname
    24. value1
    25. value2
    26. endef
    27. ifeq 判断关键字是否相等,四种格式:
    28. ifeq (ARG1, ARG2)
    29. ifeq 'ARG1' 'ARG2
    30. ifeq "ARG1" 'ARG2'
    31. ifeq 'ARG1' "ARG2"
    32. shell command 返回command命令在shell中执行的结果(类似于shell脚本中的`command`),注意大小写的含义不同,大写的SHELL是一个特殊变量
    33. export 将变量添加到当前工作环境,传递给子makefile,不覆盖子makefile中的同名变量(除非是-e选项)。没有使用export的变量(除了一些特殊变量)不传递给子makefile
    34. 单行命令与多行命令的区别
    35. makefile的规则的命令行中,每一行命令在一个独立的子shell进程中被执行。
    36. 上一行中使用的cd命令不会改变下一行命令的工作目录。可以使用\将一行命令多行排版(与C语言中的\符号作用类似)
    37. .d文件 GNU组织建议为每一个“name.c”的文件都生成一个“name.d'的文件,存放.c文件的依赖关系
    38. %.d: %.c
    39. @set -e; rm -f $@; \
    40. gcc -MM $< > $@.$$$$; \
    41. sed 's/\($*\)\.o[ :]*\1.o $@ :/g' < $@.$$$$ > $@; \
    42. rm -f $@.$$$$
    43. FORCE 没有依靠关系也没有命令的规则被认为总是新的,如下面的FORCE
    44. clean:FORCE
    45. rm *.o
    46. FORCE:
    47. 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: ;