引言
一个成熟的python项目可能会依赖很多特定的环境,然而项目运行的结果不仅取决于代码,和运行代码的环境也息息相关。这很有可能会造成,开发环境上的运行结果和测试环境、线上环境上的结果都不一致的现象。为了解决这个问题,我们可以将python项目打包成docker镜像,这样即使在不同的机器上运行打包后的项目,我们也能够得到一致的运行结果。
准备工作
安装docker
可以使用如下脚本一键安装
1
| sudo curl -sSL https://get.daocloud.io/docker | sh
|
也可参考 CentOS Docker基础环境安装
准备python项目
在项目根目录下添加以下两个文件
打包镜像
在Dockerfile所在的目录下运行
1
| sudo docker build -t demo:v1 .
|
其中demo为镜像名称,v1为镜像tag用于版本管理
运行容器
其它常用参数,根据需要使用:
- -d: 后台运行容器,并返回容器ID;
- -v /opt/logs:/logs 挂载目录,将容器中的目录映射到宿主机中,格式为宿主机目录:容器中的目录
- -p 80:80 指定端口映射,格式为:主机(宿主)端口:容器端口
- -e username=”ritchie” 设置环境变量;
- –env-file .env 使用.env文件设置环境变量
- –name nginx1.18 为容器指定一个名称;
- –link mysql:mysql 添加链接到另一个容器;
标准化自动打包、自动部署流程
本规范仅专注于如何打包python容器,如果需要结合jenkins自动化部署流程,则需要参照以下步骤
添加docker.sh文件
需要在项目根目录添加docker.sh文件,参照如下:
设置镜像名称BUILD_IMAGE_NAME
不需要设置tag,统一使用构建时间生成,构建成功后保存在.docker_build_version文件中
根据项目设置run()函数容器的映射端口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
| #!/usr/bin/env bash
BUILD_DIRECTORY=$(dirname $(readlink -f "$0"))
BUILD_REGISTRY=$DOCKER_REGISTRY BUILD_REGISTRY_USER=$DOCKER_REGISTRY_USER BUILD_REGISTRY_PASS=$DOCKER_REGISTRY_PASS
BUILD_REGISTRY_PROD=$DOCKER_REGISTRY_PROD BUILD_REGISTRY_PROD_USER=$DOCKER_REGISTRY_PROD_USER BUILD_REGISTRY_PROD_PASS=$DOCKER_REGISTRY_PROD_PASS
BUILD_NAMESPACE=""
BUILD_IMAGE_NAME=aglaia-web-re
if [ "$2" ]; then BUILD_IMAGE_NAME=$2 fi
BUILD_VERSION=""
BUILD_IMAGE=$BUILD_IMAGE_NAME if [ "$BUILD_NAMESPACE" ]; then BUILD_IMAGE=$BUILD_NAMESPACE/$BUILD_IMAGE fi
check_docker_registry() { if [ "$BUILD_REGISTRY" == "" ]; then echo "请配置docker仓库环境变量:DOCKER_REGISTRY" exit 1 fi }
check_docker_registry_user_and_pass() { if [ "$BUILD_REGISTRY_USER" == "" -o "$BUILD_REGISTRY_PASS" == "" ]; then echo "请配置docker仓库认证信息环境变量:DOCKER_REGISTRY_USER、DOCKER_REGISTRY_PASS" exit 1 fi }
check_docker_registry_prod() { if [ "$BUILD_REGISTRY_PROD" == "" ]; then echo "请配置生产docker仓库环境变量:DOCKER_REGISTRY_PROD" exit 1 fi }
check_docker_registry_prod_user_and_pass() { if [ "$BUILD_REGISTRY_PROD_USER" == "" -o "$BUILD_REGISTRY_PROD_PASS" == "" ]; then echo "请配置生产docker仓库认证信息环境变量:DOCKER_REGISTRY_PROD_USER、DOCKER_REGISTRY_PROD_PASS" exit 1 fi }
check_image() { if [ "$BUILD_IMAGE" == "" ]; then echo "请配置镜像名称:BUILD_IMAGE_NAME,或通过参数指定 docker.sh <command> <image>" exit 1 fi }
check_version() { if [ "$BUILD_VERSION" == "" ]; then echo "请先执行build操作" exit 1 fi }
gen_version() { echo $(date "+%Y%m%d%H%M%S") }
get_version() { local FILE=$BUILD_DIRECTORY/.docker_build_version if [ -f "$FILE" ]; then echo $(cat "$FILE") else echo "" fi }
build_when_necessary() { if [ "$(get_version)" == "" ]; then build fi }
build() { echo "构建镜像" check_docker_registry check_image BUILD_VERSION=$(gen_version) check_version docker build --no-cache --force-rm $BUILD_DIRECTORY -t $BUILD_REGISTRY/$BUILD_IMAGE:$BUILD_VERSION if [ "$?" != "0" ]; then echo "构建镜像失败" exit 1 fi echo $BUILD_IMAGE >$BUILD_DIRECTORY/.docker_build_image echo $BUILD_VERSION >$BUILD_DIRECTORY/.docker_build_version echo "构建镜像成功" }
run() { echo "执行镜像" check_docker_registry check_image BUILD_VERSION=$(get_version) check_version docker run --rm -p 80:80 $BUILD_REGISTRY/$BUILD_IMAGE:$BUILD_VERSION if [ "$?" != "0" ]; then echo "执行镜像失败" exit 1 fi echo "执行镜像成功" }
push() { echo "推送镜像" check_docker_registry check_docker_registry_user_and_pass check_image BUILD_VERSION=$(get_version) check_version docker login $BUILD_REGISTRY -u $BUILD_REGISTRY_USER -p $BUILD_REGISTRY_PASS if [ "$?" != "0" ]; then echo "登录docker仓库失败" exit 1 fi docker push $BUILD_REGISTRY/$BUILD_IMAGE:$BUILD_VERSION if [ "$?" != "0" ]; then echo "推送镜像失败" exit 1 fi echo "推送镜像成功" }
tag_latest() { echo "标记为最新版本" check_docker_registry check_image BUILD_VERSION=$(get_version) check_version docker tag $BUILD_REGISTRY/$BUILD_IMAGE:$BUILD_VERSION $BUILD_REGISTRY/$BUILD_IMAGE:latest if [ "$?" != "0" ]; then echo "标记为最新版本失败" exit 1 fi echo "标记为最新版本成功" }
push_latest() { echo "推送最新版本" check_docker_registry check_docker_registry_user_and_pass check_image BUILD_VERSION=$(get_version) check_version docker login $BUILD_REGISTRY -u $BUILD_REGISTRY_USER -p $BUILD_REGISTRY_PASS if [ "$?" != "0" ]; then echo "登录docker仓库失败" exit 1 fi docker push $BUILD_REGISTRY/$BUILD_IMAGE:latest if [ "$?" != "0" ]; then echo "推送最新版本失败" exit 1 fi echo "推送最新版本成功" }
tag_prod() { echo "标记为生产版本" check_docker_registry check_docker_registry_prod check_image BUILD_VERSION=$(get_version) check_version docker tag $BUILD_REGISTRY/$BUILD_IMAGE:$BUILD_VERSION $BUILD_REGISTRY_PROD/$BUILD_IMAGE:$BUILD_VERSION if [ "$?" != "0" ]; then echo "标记为生产版本失败" exit 1 fi echo "标记为生产版本成功" }
push_prod() { echo "推送生产版本" check_docker_registry check_docker_registry_prod check_docker_registry_prod_user_and_pass check_image BUILD_VERSION=$(get_version) check_version docker login $BUILD_REGISTRY_PROD -u $BUILD_REGISTRY_PROD_USER -p $BUILD_REGISTRY_PROD_PASS if [ "$?" != "0" ]; then echo "登录docker生产仓库失败" exit 1 fi docker push $BUILD_REGISTRY_PROD/$BUILD_IMAGE:$BUILD_VERSION if [ "$?" != "0" ]; then echo "推送生产版本失败" exit 1 fi echo "推送生产版本成功" }
tag_prod_latest() { echo "标记为生产最新版本" check_docker_registry check_docker_registry_prod check_image BUILD_VERSION=$(get_version) check_version docker tag $BUILD_REGISTRY/$BUILD_IMAGE:$BUILD_VERSION $BUILD_REGISTRY_PROD/$BUILD_IMAGE:latest if [ "$?" != "0" ]; then echo "标记为生产最新版本失败" exit 1 fi echo "标记为生产最新版本成功" }
push_prod_latest() { echo "推送生产最新版本" check_docker_registry check_docker_registry_prod check_docker_registry_prod_user_and_pass check_image BUILD_VERSION=$(get_version) check_version docker login $BUILD_REGISTRY_PROD -u $BUILD_REGISTRY_PROD_USER -p $BUILD_REGISTRY_PROD_PASS if [ "$?" != "0" ]; then echo "登录docker生产仓库失败" exit 1 fi docker push $BUILD_REGISTRY_PROD/$BUILD_IMAGE:latest if [ "$?" != "0" ]; then echo "推送生产最新版本失败" exit 1 fi echo "推送生产最新版本成功" }
RC=0
case "$1" in build) build ;; run) run ;; push) push ;; tag-latest) tag_latest ;; push-latest) push_latest ;; tag-prod) tag_prod ;; push-prod) push_prod ;; tag-prod-latest) tag_prod_latest ;; push-prod-latest) push_prod_latest ;; pipeline) build_when_necessary push tag_latest push_latest ;; pipeline-prod) build_when_necessary tag_prod push_prod tag_prod_latest push_prod_latest ;; pipeline-all) build_when_necessary push tag_latest push_latest tag_prod push_prod tag_prod_latest push_prod_latest ;; pipeline-rebuild) build push tag_latest push_latest ;; pipeline-prod-rebuild) build tag_prod push_prod tag_prod_latest push_prod_latest ;; pipeline-all-rebuild) build push tag_latest push_latest tag_prod push_prod tag_prod_latest push_prod_latest ;; *) echo $"Usage 1: $0 {build|run|push|tag-latest|push-latest|tag-prod|push-prod|tag-prod-latest|push-prod-latest}" echo $"Usage 2: $0 {pipeline|pipeline-prod|pipeline-all|pipeline-rebuild|pipeline-prod-rebuild|pipeline-all-rebuild}" echo $"Tips 1: 使用前请设置脚本变量:BUILD_NAMESPACE、BUILD_IMAGE_NAME" echo $"Tips 2: 使用前请设置环境变量:DOCKER_REGISTRY、DOCKER_REGISTRY_USER、DOCKER_REGISTRY_PASS、DOCKER_REGISTRY_PROD、DOCKER_REGISTRY_PROD_USER、DOCKER_REGISTRY_PROD_PASS" exit 2 ;; esac
exit $RC
|
赋予docker.sh执行权限
1
| sudo chmod a+x docker.sh
|
设置dockerhub相关环境变量
这些环境变量在docker.sh中会用到,并且所有docker打包发布相关操作都会用到,因此强烈建议直接配置到/etc/profile
1 2 3 4 5 6 7 8 9
| # docker仓库相关信息 export DOCKER_REGISTRY=docker.parkson.net.cn export DOCKER_REGISTRY_USER=<账号> export DOCKER_REGISTRY_PASS=<密码>
# docker生产仓库相关信息 export DOCKER_REGISTRY_PROD=docker-registry.parkson.net.cn export DOCKER_REGISTRY_PROD_USER=<账号> export DOCKER_REGISTRY_PROD_PASS=<密码>
|
docker.sh使用方法
查看使用方法
构建镜像
运行镜像
推送镜像
标记为最新版本
推送最新版本
标记为生产版本
推送生产版本
标记为生产最新版本
1
| ./docker.sh tag-prod-latest
|
推送生产最新版本
1
| ./docker.sh push-prod-latest
|
执行流水线作业
build(如果已经构建不会重新构建)->push->tag-latest->push-latest
执行生产流水线作业
build(如果已经构建不会重新构建)->tag-prod>push-prod->tag-latest->push-latest
1
| ./docker.sh pipeline-prod
|
执行全部流水线作业
build(如果已经构建不会重新构建)->push->tag-latest->push-latest->tag-prod>push-prod->tag-latest->push-latest
1
| ./docker.sh pipeline-all
|
执行流水线作业(强制重新构建镜像)
build(每次都重新构建镜像)->push->tag-latest->push-latest
1
| ./docker.sh pipeline-rebuild
|
执行生产流水线作业(强制重新构建镜像)
build(每次都重新构建镜像)->tag-prod>push-prod->tag-latest->push-latest
1
| ./docker.sh pipeline-prod-rebuild
|
执行全部流水线作业(强制重新构建镜像)
build(每次都重新构建镜像)->push->tag-latest->push-latest->tag-prod>push-prod->tag-latest->push-latest
1
| ./docker.sh pipeline-all-rebuild
|
项目相关环境变量配置
在项目根目录下添加.env文件(用于设置环境变量,更多环境变量请根据项目进行设置)
.env
1 2 3 4
| # dev、test、prod ENV=dev # DBURI HOST=10.88.1.12
|
docker-compose.yml中环境变量的优先级
需要区分两种环境变量,一种指传递到容器内部的环境变量,一种指docker-compose.yml文件中通过${}符号引用用于设置docker-compose的环境变量。
前者的优先级从低到高如下:
- docker-compose.yml中声明env_file
- docker-compose.yml声明environment
后者的优先级从低到高如下:
同一目录下放一个.env文件
在/etc/profile或者在shell中通过export设置环境变量
注意,存在.env文件在前者和后者都使用的情况,此种情况各论各即可
添加docker-compose.yml文件
在项目根目录下添加docker-compose.yml文件
docker-compose.yml
设置镜像名称(格式:项目代号,形如xxx-yyy-zzz,例如aglaia-ui-re)
容器名称与镜像名称保持一致
主机名称与镜像名称保持一致
根据项目设置容器的映射端口
大部分基底容器默认不支持中文,为了保持统一只设置时区为Asia/Shanghai,语言方面以英语作为默认语言
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| version: "3.5" services: <项目代号>: image: ${DOCKER_REGISTRY:-docker.parkson.net.cn}/<项目代号>:${VERSION:-latest} container_name: <项目代号> hostname: <项目代号> ports: - 8080:8080 ulimits: memlock: soft: -1 hard: -1 env_file: - .env environment: - TZ=Asia/Shanghai - LANG=en_US.UTF-8 - LANGUAGE=en_US:en - LC_ALL=en_US.UTF-8 restart: unless-stopped logging: driver: "json-file" options: max-size: "128m" max-file: "20"
|
docker-compose.yaml使用方法
该文件主要用于部署配合Jenkins自动化部署使用
将docker-compose.yml上传到服务器的”/docker/compose/<项目代号>/“目录下
启动容器
前台
启动容器
后台
销毁容器
更新镜像
查看日志
查看最后500行日志
1
| docker-compose logs --tail 500
|
查看最后500行日志(滚动刷新)
1
| docker-compose logs --tail 500 -f
|