关于docker自动化部署的总结与反思

这次线上部署躺了一些坑,于是记录一下。

内容涉及:

  • python自动化部署(fabric)
  • docker基本命令
  • docker构建镜像
  • docker运行容器
  • golang打包编译
  • rabbitmq&redis安装和配置
  • Makefile命令编写

1. 使用Makefile打包golang项目

.PHONY: build

CUR_ROOT = `pwd`

build:
    @echo "remove old output..."
    @rm -rf bin
    @echo "build..."
    @GOPATH=${CUR_ROOT}:$$GOPATH; \
    GOOS=linux GOARCH=amd64 \
    go build -a -o bin/<my_app>  <muduleA>
    @echo "build...done"

这里采用makefile来打包,目标是运行在linux上,所以需要进行交叉编译,其中@表示控制台不输出,<my_app>表示编译后的可执行二进制文件名称,<muduleA>要编译的模块,该模块下必须包含main.go文件

调用make build即可。

拓展:条件编译

如以下例子:

debug.go:

// build+ debug
package log

func Log(v ...interface{}) {
  print(v)
}

release.go

// build+ !debug
package log

func Log(v ...interface{}) {
  // do nonthing
}

这里两个文件在同一个包下,方法名称却一样,但是不会报错。只因为// build+ tags注释,然后在build是可以加上tag:

go build -tags debug -a -o bin/<my_app>  <muduleA>

这就是条件编译,加上这个之后,编译的将是debug.go,这样可以区分调试环境和正式环境的日志输出了。

2. 使用Makefile打包项目镜像

首先假设项目有用到配置文件config.json,然后是docker相关文件:

.dockerignore,即需要忽略的文件

/src
/doc
/pkg
/sql
*.*

然后就是 Dockerfile:

FROM scratch
MAINTAINER UserName <UserName@xxxxx.org>

COPY bin/linux/<my_app> /

ENTRYPOINT ["/my_app", "--config", "config.json"]

FROM <基础镜像>, 其中scratch是最小的镜像

将二进制文件复制到镜像根目录中,最后将命令打包进镜像中

这里--config是项目中引用的命令,具体使用了flag来设置参数:

  var g_config_file string
  flag.StringVar(&g_config_file, "config",<默认配置文件路径>, "the config file path.")

然后在Makefile中加入构建的命令:

release: build
    docker build . -t <xxx.xxx.com>/<my_app>:$${VER}

使用:

make release -e VER=v0.0.1

这里$${VER}用参数-e在命令行中动态设置,这里指版本号

其次<xxx.xxx.com>是远程仓库的ip,如果采用http,则需要将ip添加到docker的Insecure registris

如果<xxx.xxx.com>未有指向,可以修改本地hosts文件,将其指向具体的ip,以mac为例:

sudo vi /etc/hosts
# 添加 xxx.xxx.xxx xxx.xxx.com 
# 如: 192.168.1.1 www.example.com
# 保存

3. 配置其他依赖镜像

坑:redis和rabbitmq配置的ip需要使用局域网ip,而不能使用127.0.0.1或者localhost

3.1 rabbitmq

运行容器:

docker run -d --hostname my-rabbit --name some-rabbit -p 8080:15672 -p 5672:5672 rabbitmq:3-management

ps: 本地如果没有该镜像,将自动获取最新镜像,如有必要请自行fq

其中-d参数可以让程序在后台运行,同时会返回容器ID,也可以用命令docker inspect -f '{{.ID}}' some-rabbit获取ID

停止容器:

不带sudo版:

 docker stop `docker inspect -f '{{.ID}}' some-rabbit`

带sudo版:

 sudo docker stop `sudo docker inspect -f '{{.ID}}' some-rabbit`

带sudo且带密码版:

这个是为了避免需要等待输入密码,一般用在脚本中

echo '<密码>'|sudo -S  sudo docker stop `echo '<密码>'|sudo -S docker inspect -f '{{.ID}}' some-rabbit`

同理,删除容器:

 docker rm `docker inspect -f '{{.ID}}' some-rabbit`

带sudo以及sudo带密码版本也和上面差不多。

然后就是相关的配置amqp://<用户名>:<密码>@<IP>:<端口号>/

比如url: amqp://guest:guest@localhost:5672/

账号名和密码默认都是guest,另外一定要注意:如果是容器要访问另一个容器的,一定不能用127.0.0.1localhost,比如本例子,可以采用ifconfig查看局域网ip,然后替换掉localhost

3.2 redis

下载镜像

docker pull redis

配置文件:redis.conf

bind 0.0.0.0
port 6379
daemonize no
protected-mode no
requirepass 123456

如上配置表示访问需要密码,且外网可以访问

运行容器:

sudo docker run -v `pwd`/redis.conf:/usr/local/etc/redis/redis.conf -d -p 6379:6379 --name myredis redis redis-server /usr/local/etc/redis/redis.conf

暂停和删除容器:

 docker stop `docker inspect -f '{{.ID}}' myredis`

 docker rm `docker inspect -f '{{.ID}}' myredis`

sudo版本可以参考rabbitmq暂停容器方法

同理这里也需要注意,由于redis运行在容器中,访问的ip需要设置为局域网ip

4. 自动部署

首先说一下部署流程:

  • 编译项目
  • 构建docker镜像
  • 上传镜像
  • 上传并校验配置文件
  • pull镜像
  • 暂停并移除之前的容器
  • 启动新的容器,并测试容器是否启动成功

前两步之前已经说过,至于上传:

可以采用push命令,若ip未正确指向预定的仓库,可能会报上传超时的错误

release: build
    docker build . -t <xxx.xxx.com>/<my_app>:$${VER}
    docker push <xxx.xxx.com>/<my_app>:$${VER}

然后接下来需要介绍一下fabric

Fabric 是一个 Python 的库,同时它也是一个命令行工具。它提供了丰富的同 SSH 交互的接口,可以用来在本地或远程机器上自动化、流水化地执行 Shell 命令。

4.1 基本设置

假设脚本名称为deploy.py,实现设置ip,用户名和密码:

rom fabric.api import *
import sys
import os

###################
# 这里设置 用户名和密码
###################
myuser = ""
mypassword = ""
###################

env.hosts = ['<目标远程IP>']
env.user = myuser   # 多台主机用户名密码相同可以只写一次
env.password = mypassword
env.warn_only = True

其中env.warn_only是为了防止某些情况终止脚本执行,比如grep命令返回为空时

然后就是一些基本变量的设置:

dirname = os.environ["PWD"]
pre_cmd = "echo '%s'|sudo -S " % mypassword
# pre_cmd = "sudo "

target_config_path = os.path.join(dirname, "config.json")
remote_config_path = '/home/%s/config.json' % myuser

version = "v1"

这里os.environ["PWD"]可以获取当前路径

pre_cmd是命令前缀,因为很多命令需要加sudo权限

另外target_path是本地要上传的配置文件路径,需要根据具体情况设置

version为版本号

接下来就是一些具体的方法:

4.2 打包项目并上传到docker:

# 打包
@runs_once            # 该装饰器表示只执行一次,没有的话默认每台主机都执行一次
def task_tar():      # 该场景本地文件打包本身就只需要执行一次
    with lcd(dirname):
        local('make release -e VER=%s' % version)

4.3 上传配置文件

# 上传配置文件
def task_upload():
    print("put file: %s" % target_config_path)
    put(target_config_path, remote_config_path)

put命令即可直接上传文件

4.4 校验配置文件

def check_md5():
    # 计算本地的md5
    local_md5 = local('md5 %s' % target_config_path,
                      capture=True).split(' = ')[1]
    # 计算远程主机md5
    remote_md5 = run('md5sum %s' % remote_config_path).split('  ')[0]
    print(local_md5)
    print(remote_md5)
    if remote_md5 == local_md5:
        print('上传成功')
    else:
        print('上传出错,退出程序')
        sys.exit()

这里值得注意的是,本地是mac操作系统,采用的是md5,而远端是linux,采用的是md5sum

4.5 暂停和移除容器

def kill():
    res = run("%s docker inspect -f '{{.ID}}' <demo>" % pre_cmd)
    if len(res) > 0:
        res = res.split(' ')[-1]
        print("id: %s" % res)
        run("(%s docker stop %s) && sleep 1" % (pre_cmd, res))
        run("(%s docker rm %s) && sleep 1" % (pre_cmd, res))
    print("killed server: <demo>")

这里demo是运行容器的名称,需要自行进行替换

4.6 拉取镜像并运行新的容器

def task_exc():
    kill()

    with cd('/home/%s' % myuser):
        run("%s docker pull xxx.xxx.com/<my_app>:%s" %
            (pre_cmd, version))
        run("(%s docker run -d --restart=always -p <需要映射成的端口>:<服务器端口> --name <demo> -v `pwd`/config.json:/config.json xxx.xxx.com/<my_app>:%s) && sleep 1" % (pre_cmd, version))

    res = run("%s docker inspect -f '{{.ID}}' <demo>" % pre_cmd)
    if len(res) > 0:
        print("publish ok")
    else:
        print("publish fialure")

这里端口号需要自行进行配置,<demo>是容器名称,需要与kill方法中的保持一致,xxx.xxx.com/<my_app>为这次拉取部署的镜像

4.7 组合方法

@task
def start(ver):
    global version
    version = ver

    task_upload()
    check_md5()
    task_tar()
    task_exc()

Makefile中添加:

deploy:
    fab -f deploy.py start:ver=$${VER}

这里用fab命令来运行脚本,指定运行start任务,参数为ver=$${VER}

运行命令:

make deploy -e VER=v0.0.1

这样,一个完整的部署流程就做完了。

好处自不必多说,看起来虽然有些麻烦,但是完成后,再也不用每次登陆到服务器,一个个命令运行程序了。可以说大大解放了生产力。并且,这个脚本可以作为一个模板,基本操作就这些。

如果是在非docker的环境下呢?

5. 非docker环境下的自动部署

首先就是上传文件,这时候就需要压缩和解压目录了

tar -zcvf "<target_path>/<my_app>.tar.gz" "<dist_path>/"

这里的<dist_path>为需要压缩的目录

然后在task_exc中,需要解压:

with cd('<上传目录>'):
        run("rm -r <项目目录>/")
        run("tar -zxvf <my_app>.tar.gz")
        run("mv  dist/ <项目目录>/")

这里dist为解压之后的目录名称,需要根据具体情况替换

然后还有kill的变化:

def kill():
    res = run('ps -ef|grep "<my_app>"|grep -v "grep"')
    if len(res) > 0:
        print("kill server: <my_app>")
        run('ps -ef|grep "<my_app>"|grep -v "grep"|awk \'{print $2}\'|xargs kill -9')
    print("killed server: <my_app>")

以及二进制文件的运行:

with cd('<项目可执行文件路径>'):
        run("(nohup ./<my_app> --config config.json &) && sleep 1")

这里的sleep 1非常重要,如果不加,程序会被kill掉

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • 五、Docker 端口映射 无论如何,这些 ip 是基于本地系统的并且容器的端口非本地主机是访问不到的。此外,除了...
    R_X阅读 1,670评论 0 7
  • 《Docker从入门到实践》阅读笔记 原书地址: https://yeasy.gitbooks.io/docker...
    GuoYuebo阅读 11,268评论 1 39
  • 0x01 核心概念 Docker镜像类似于虚拟机镜像,可以理解为一个面向Docker引擎的只读模板,包含了文件系统...
    闲云逸心阅读 4,601评论 0 9
  • 一 、什么是 Docker Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国...
    Blazzer阅读 3,101评论 0 13
  • 昨天是我简书上更文79天后第一次中断,想想我已经在简书上已经发文100多篇了,这第一次的日更中断还是有些小遗憾,虽...
    伯温运筹阅读 1,318评论 22 84