關於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 <[email protected]>

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掉

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章