这次线上部署躺了一些坑,于是记录一下。
内容涉及:
- 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"
@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
go build -tags debug -a -o bin/<my_app> <muduleA>
debug.go
2. 使用Makefile打包项目镜像
config.json
.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
将二进制文件复制到镜像根目录中,最后将命令打包进镜像中
--configflag
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
Insecure registris
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
-ddocker inspect -f '{{.ID}}' some-rabbit
停止容器:
不带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://<用户名>:<密码>@:<端口号>/
amqp://guest:guest@localhost:5672/
127.0.0.1localhostifconfiglocalhost
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
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_onlygrep
然后就是一些基本变量的设置:
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
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()
md5md5sum
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")
killxxx.xxx.com/
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}
fabstartver=$${VER}
运行命令:
make deploy -e VER=v0.0.1
这样,一个完整的部署流程就做完了。
好处自不必多说,看起来虽然有些麻烦,但是完成后,再也不用每次登陆到服务器,一个个命令运行程序了。可以说大大解放了生产力。并且,这个脚本可以作为一个模板,基本操作就这些。
如果是在非docker的环境下呢?
5. 非docker环境下的自动部署
首先就是上传文件,这时候就需要压缩和解压目录了
tar -zcvf "<target_path>/<my_app>.tar.gz" "<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