基于 micro 的 go微服务系列之四

以下文章来源于饭谈编程 ,作者ricoder

一、采用gvt管理依赖(目前推荐使用 go module 管理依赖)

同java的maven方案一样,go也具备有管理依赖的方案,如godep、gv、gvt、govendor等,在这里我使用的是gvt,在这里说说我是如何使用gvt实现依赖管理的。由于我自己平常都是使用ubuntu做开发和日常使用,这里就只说如何在ubuntu上操作,windows的额我就不说了~

1.1 安装gvt

首先安装gvt(这里请自行翻墙)

go get -u github.com/FiloSottile/gvt 
1.2 使用gvt

进入项目src下,使用以下命令:

$ gvt fetch github.com/BurntSushi/toml 

之后在src下面就会看到生成文件夹vendor以及vendor下面生成相应依赖和manifest,之后再多次重复gvt fetch xxx命令即可生成完整依赖(xxx修改为相应的依赖),图示如下:

当采用了gvt管理依赖后,启动项目的时候系统会默认从src/vendor下面寻找相应的依赖。

二、采用docker做容器部署项目

为了比较完整的玩这一套微服务项目,我引入了docker做容器,并在docker上跑这个服务,这里说说我是如何采用docker做容器的。

2.1 安装docker

具体如何安装docker可以查看官网(https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/),这里我就不提了,安装成功后,在命令行输入:docker version 有输出版本等信息即意味着安装成功。

2.2 使用Dockerfile构建镜像

我先在项目根目录创建了dockerbase,在里边放置我的Dockefile文件,Dockerfile内容如下,具体的可以查看源码:

FROM ubuntu:14.04
MAINTAINER ricoder "ricoder142@gmail.com"
ADD http://mirrors.163.com/.help/sources.list.trusty /etc/apt/sources.list
COPY conf/redis.conf /etc/redis/6379.conf
COPY conf/consul.json /etc/consul/consul.json
COPY supervisor/*.conf /etc/supervisor/conf.d/
RUN apt-get update && 
    apt-get -y install build-essential && 
    apt-get -y install openssh-server && 
    apt-get -y install libssl-dev && 
    apt-get -y install git && 
    apt-get -y install vim && 
    apt-get -y install wget && 
    apt-get -y install curl && 
    apt-get -y install unzip
RUN apt-get -y install supervisor && 
    apt-get -y install redis-server && 
    apt-get -y install mysql-server && 
    apt-get -y install mysql-client && 
    mkdir -p /data/services/consul-0.9/bin/ && 
    wget https://releases.hashicorp.com/consul/0.9.0/consul_0.9.0_linux_amd64.zip && 
    unzip consul_0.9.0_linux_amd64.zip && 
    mv ./consul /usr/local/bin/ && 
    mkdir /data/consul/ && 
    mkdir -p /data/logs/gologs/ && 
    mysql_install_db && 
    update-rc.d -f mysql defaults && 
    wget https://storage.googleapis.com/golang/go1.9.1.linux-amd64.tar.gz && 
    tar zxf go1.9.1.linux-amd64.tar.gz && 
    mkdir -p /data/goapp && 
    mv go/ /data/services/ && 
    rm -rf consul_0.9.0_linux_amd64.zip go1.9.1.linux-amd64.tar.gz && 
    echo "export GOROOT=/data/services/gonPATH=$PATH:/data/services/go/bin" >> ~/.bashrc
EXPOSE 3306 8500 6379 8082 8083 8084 8085 5324 9999
CMD chown -R mysql:mysql /var/lib/mysql && 
    service mysql start && 
    supervisord -c /etc/supervisor/supervisord.conf -n 

构建的镜像主要实现的功能是:

在ubuntu14.04系统下搭建go生产环境,安装git、vim等工具,安装supervisor管理后台进程,安装redis和mysql,安装consul实现服务发现,暴露部分端口,启动mysql和supervisord等。

Dockerfile涉及的命令有:

(1)FROM(指定基础镜像)

该指令必须指定且需要在Dockerfile其他指令的前面。后续的指令都依赖于该指令指定的image。FROM指令指定的基础image可以是官方远程仓库中的,也可以位于本地仓库。
该指令有两种格式:

        b.FROM <image> 
</image>

指定基础image为该image的最后修改的版本。或者:

        a.FROM <image>:<tag> 
</tag></image>

指定基础镜像为该镜像的一个tag版本。

可以通过我写的Dockerfile看出我指定的基础镜像是14.04 的。

(2)MAINTAINER(用来指定镜像创建者信息)

该指令用于将image的制作者相关的信息写入到image中。当我们对该image执行docker inspect命令时,输出中有相应的字段记录该信息。
指令格式:

        MAINTAINER <name> 
</name>

可以在我写的Dockerfile中看出镜像创建者的信息是:ricoder “ricoder142@gmail.com”

(3)ADD(从src复制文件到容器的dest路径)

该指令将所有拷贝到container中的文件和文件夹权限为0755,uid和gid为0;如果是一个目录,那么会将该目录下的所有文件添加到container中,不包括目录;如果文件是可识别的压缩格式,则docker会帮忙解压缩(注意压缩格式);如果是文件且中不使用斜杠结束,则会将视为文件,的内容会写入;如果是文件且中使用斜杠结束,则会文件拷贝到目录下。
指令格式:

        ADD <src> <dest> 
</dest></src>

是相对被构建的源目录的相对路径,可以是文件或目录的路径,也可以是一个远程的文件url;是container中的绝对路径

可以在Dockerfile中看出我主要是将conf下和supervisor下的文件复制容器内,复制conf的目的是修改配置,而复制supervisor的目的我会在第三章节说到。

(4)COPY
COPY的语法与功能与ADD基本相同,不同的是不支持上面讲到的是远程URL、自动解压这两个特性,所以上面我并没有使用COPY,而是使用了ADD,不过官方建议尽量使用COPY,这是因为虽然COPY只支持本地文件拷贝到container,但它的处理比ADD更加透明,官方建议只在复制tar文件时使用ADD(因为ADD会自动解压文件),如ADD trusty-core-amd64.tar.gz 。

可以在我写的Dockerfile中看出,我是将conf和supervisor文件夹下的文件拷贝到指定的路径,conf下的是对redis和consul的设置,而supervisor的主要实现的是后台进程的管理,等等我会在第三部分做个详细的讲解,此处先略过。

(5)RUN
RUN指令会在当前镜像的顶层执行任何命令,并commit成新的(中间)镜像。
指令格式:

        RUN <commnad>或RUN ["executable", "param1", "param2"] 
</commnad>

第一种格式是shell格式,相当于执行/bin/sh -c ““,例如我Dockerfile中的例子:

        RUN apt-get -y install supervisor 

exec格式,不会触发shell,所以$HOME这样的环境变量无法使用,但它可以在没有bash的镜像中执行,而且可以避免错误的解析命令字符串:

        RUN ["apt-get", "install", "supervisor", "-y"]或RUN ["/bin/bash", "-c", "apt-get install -y supervisor"] 

(6)EXPOSE
EXPOSE指令会告诉容器在运行时要监听的端口,不过这个端口是用于多个容器之间通信用的,外面的host是访问不到的。而要把端口暴露给外面的主机,在启动容器时使用-p
选项,这一点在后面将会有提及。示例:

    EXPOSE 3306 8500 6379 8082 8083 8084 8085 5324 9999 

(7)CMD
一个Dockerfile里只能有一个CMD,如果有多个,只有最后一个生效。CMD指令的主要功能是在build完成后,为了给docker run启动到容器时提供默认命令或参数,这些默认值可以包含可执行的命令,也可以只是参数(此时可执行命令就必须提前在ENTRYPOINT中指定)。

CMD与RUN的区别在于,RUN是在build成镜像时就运行的,而CMD指令实在build完成后运行的,所以RUN先于CMD运行,其次CMD会在每次启动容器的时候运行,而RUN只在创建镜像时执行一次。

可以从我的Dockerfile中看出在这里CMD的作用是启动mysql和supervisord。

2.3 构建镜像

使用命令:

docker build -t gomicro-env . 

可以在命令行中看到输出:

ricoder# docker build -t gomicro-env .Sending build context to Docker daemon  42.5 kBStep 1/10 : FROM ubuntu:14.04 ---> dea1945146b9Step 2/10 : MAINTAINER ricoder "ricoder142@gmail.com" ---> Running in d4da66553673 ---> 2e8b5ccd3c9e ....... 

构建成功后,在命令行输入:

ricoder# docker images 

可以在列表中看到:

gomicro-env           latest             9c9883edc1db        20 hours ago         899 MB 
2.4 启动容器

使用命令:

 docker run --name=$Container -p 18087:8082 -p 18505:8500 -d -v `pwd`:/data/deploy/$ProjectName gomicro-env 

查看容器:

ricoder# docker container ls 

可以在命令行中看到容器的详细信息。

这里大概讲解下docker run这一行话的意思,—name是容器名的参数,-p 对应着的是本地端口映射到容器端口,这里的-p 18087:8082意味着本地的18087映射到8082端口,这样在本地访问18087端口的时候可以访问到容器的8082端口,-d意思是后台运行容器,并返回容器ID,-v参数的给容器添加一个数据卷,即将本地的项目挂载到容器中。

2.5 在容器中创建数据库&跑起服务

我在dockerbase目录下面新加了一个init.sh文件,内容如下:

!/bin/bash
mysql -u root -e "CREATE DATABASE gomicro "
R=/data/deploy/gomicro
cd $Rbash
build_local.sh all 
然后运行如下命令: 
docker exec $Container bash /data/deploy/$ProjectName/dockerbase/init.sh 

该命令的功能是在容器$Container中跑起这个init.sh脚本,实现的功能是创建数据库gomicro和跑起build_local.sh脚本并且携带参数all,接下来看看build_local.sh脚本

#!/bin/sh
if [ $1 == "all" ]; then
    for srv in `ls src`; do
        if [ ${srv:0-4} == "-srv" ]; then
            echo "开始更新$srv"
            GOROOT=/data/services/go GOBIN=/data/goapp/wolf/bin GOPATH=`pwd`:`pwd`/vendor /data/services/go/bin/go install $srv && sudo supervisorctl restart wolf-$srv:*
        fi
    done
else
    for srv in "$@"
    do
        if [ "${srv:0-4}" != "-srv" ]; then
            srv="${srv}-srv"
        fi
        echo "开始更新$srv"
        GOROOT=/data/services/go GOBIN=/data/goapp/wolf/bin GOPATH=`pwd`:`pwd`/vendor /data/services/go/bin/go install $srv && sudo supervisorctl restart wolf-$srv:*
    done
fi 

从脚本中可以看出,当携带的参数是all的时候实现的功能是 install go服务和使用supervisorctl重启指定的后台进程,至于supervisor如何管理后台进程我会在第三章节说到。

三、采用supervisor管理后台进程

3.1 什么是supervisor

Supervisor是一个 Python 开发的 client/server 系统,是一款Linux下的进程管理软件,可以管理和监控类 UNIX 操作系统上面的进程。它可以同时启动,关闭多个进程,使用起来特别的方便,最主要的两个功能是:

  • 将非daemon程序变成deamon方式运行
  • 对程序进行监控,当程序退出时,可以自动拉起程序

supervisor 主要由两部分组成:
supervisord(server 部分):主要负责管理子进程,响应客户端命令以及日志的输出等;

supervisorctl(client 部分):命令行客户端,用户可以通过它与不同的supervisord 进程联系,获取子进程的状态等。

3.2 在容器内安装supervisor

由于我是在docker容器内部署项目,所以supervisor也是在容器内安装和使用,不过本地也是一样的。可以在我写的dockerfile中看到:

    RUN apt-get -y install supervisor &&  

我在容器中安装了supervisor,然后在dockerfile中采用以下命令启动supervisor:

    supervisord -c /etc/supervisor/supervisord.conf -n 

我们可以先使用以下命令登录docker :

    docker exec -it $Container /bin/bash 

登录成功后采用一下命令查看supervisor是否启动成功:

    ps -ef | grep supervisor | grep -v grep 
    root       448     1  0 15:04 ?        00:00:00 /usr/bin/python /usr/bin/supervisord -c /etc/supervisor/supervisord.conf -n 

证明启动成功。

3.3 在容器内使用supervisor管理后台进程

首先,我在Dockerfile中做了如下操作:

    COPY supervisor/*.conf /etc/supervisor/conf.d/ 

将以下的文件全都复制到容器的/etc/supervisor/conf.d/文件夹下面

以api-srv.conf为例,目的是将api-srv微服务进程交给supervisor管理,源码如下:

    [program:class-api-srv]
    command=/data/goapp/gomicro/bin/api-srv
    process_name=class-user-srv
    autorestart=true
    redirect_stderr=true
    stderr_logfile=/data/logs/api-srv.err.log
    stdout_logfile=/data/logs/api-srv.out.log
    user=root 
command         启动命令
process_name     进程的名字,这里和program一致
autorestart=true     程序异常退出后自动重启
stderr_logfile     stderr 日志输出位置
stdout_logfile    stdout 日志输出位置 

重新启动supervisor

    supervisord -c /etc/supervisor/supervisord.conf -n 

使用以下命令查看已经在跑的并且被supervisor管理的进程:

    supervisordctl status 

结果如下:

root@133f583a598b:/# supervisorctl status
api-srv                          RUNNING    pid 544, uptime 0:00:12
consul-server                    RUNNING    pid 543, uptime 0:00:12
redis-6379                       RUNNING    pid 545, uptime 0:00:12 

可以看出目前被supervisor管理的进程有三个。

3.4 使用supervisor添加管理进程

为要管理的进程按照3.3中写到的那样,添加一份conf文件,并放在supervisor目录下面,之后在docker容器内运行命令:

    supervisorctl reload 

即可管理新增进程。

四、采用脚本进行自动化部署

为了开发方便,我采用了shell脚本部署项目、操作docker、操作数据库等,具体可以查看ctrl.sh文件、build_local.sh文件、build_proto.sh文件和init.sh文件。

Last、系列总结

这是go微服务系列的第四篇,接下来我会将该技术真正用到项目开发中,开发电影票在线购票系统(毕设),具体可以关注项目:
https://github.com/wiatingpub/MTBSystem ,不介意的话也可以给个star哈。
tip:该项目的源码(包含数据库的增删查改的demo)可以点击原文链接查看源代码~

10