利用docker来部署go应用程序

你说的曾经没有我的故事 提交于 2020-02-27 11:30:00

利用docker实现go程序的快速部署

最近很长一段时间一直通过各种渠道去了解国内外有关devops方面的实践,感受很多的知识点都特别的分散,所以想通过系统的整理来巩固相应的知识体系。接下来会撰写一系列有关容器化的文章。
1、 利用docker部署一个简单的go程序,并且利用阿里云的平台,进行镜像的生成
2、 利用docker-compose部署一个复杂的go程序,同时部署包含多个不同子容器的集成
3、 利用gitlab和Harbor来做ci/cd

容器是独立的软件包,能够将应用程序以及所有的依赖项、工具、库、配置文件 以及运行该应用程序所需要的所有的其他内容捆绑在一起。
通过docker可以实现将您 的应用环境从运行的物理操作系统上面抽象一层虚拟操作环境。从而保证你的程序从开发到测试以及现场的部署,保证了环境 的一致性问题。

创建一个简单的Go项目

在开始演习之前,我们通过下面命令来创建一个新的目录,接下来我们所有的文件都将存放在这个目录当中。

mkdir go-docker
在创建完成目录之后,我们需要用go的原生命令来初始化相应的go模块。go mod是go11新增加的功能,在这之前出现很好几种模块化的解决方案,在go11之后官方终于给出了自己的解决方案.
cd go-docker
go mod init github.com/wenchangshou2/go-docker
我们现在创建一个简单的hello world的服务,通过下面的命令来创建一个新的文件 
touch hello_server.go

以下是hello_server.go的内容

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/gorilla/mux"
    "gopkg.in/natefinch/lumberjack.v2"
)

func handler(w http.ResponseWriter, r *http.Request) {
    query := r.URL.Query()
    name := query.Get("name")
    if name == "" {
        name = "访客"
    }
    log.Printf("接收一个新的请求 %s\n", name)
    w.Write([]byte(fmt.Sprintf("Hello, %s\n", name)))
}

func main() {
    // 创建服务和root事件
    r := mux.NewRouter()

    r.HandleFunc("/", handler)

    srv := &http.Server{
        Handler:      r,
        Addr:         ":8080",
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    // 配置日志
    LOG_FILE_LOCATION := os.Getenv("LOG_FILE_LOCATION")
    if LOG_FILE_LOCATION != "" {
        log.SetOutput(&lumberjack.Logger{
            Filename:   LOG_FILE_LOCATION,
            MaxSize:    500, // 总大小
            MaxBackups: 3,
            MaxAge:     28,   //最长保存时间
            Compress:   true, //  压缩数据,默认关闭
        })
    }

    // 启动服务
    go func() {
        log.Println("启动服务")
        if err := srv.ListenAndServe(); err != nil {
            log.Fatal(err)
        }
    }()

    // 正常关闭
    waitForShutdown(srv)
}

func waitForShutdown(srv *http.Server) {
    interruptChan := make(chan os.Signal, 1)
    signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)

    // 阻塞,直到接收到信号
    <-interruptChan

    ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
    defer cancel()
    srv.Shutdown(ctx)

    log.Println("结束退出")
    os.Exit(0)
}

通过下面的命令来构建一个应用程序

wcs@iMac  ~/demo/go-docker  go build  ✔  >4950  23:22:27
go: finding github.com/gorilla/mux v1.7.4
go: downloading github.com/gorilla/mux v1.7.4
go: extracting github.com/gorilla/mux v1.7.4
go: downloading gopkg.in/natefinch/lumberjack.v2 v2.0.0
go: extracting gopkg.in/natefinch/lumberjack.v2 v2.0.0
通过go build,会自动下载需要的包,并且生成相应的可执行程序,通过下面的命令运行
wcs@iMac  ~/demo/go-docker  ./go-docker  ✔  
2020/02/26 23:24:05 启动服务

现在我们尝试通过curl 来测试功能是否可用

wcs@iMac  ~  curl localhost:8080  ✔  4958 
Hello, 访客
wcs@iMac  ~  curl http://localhost:8080\?name\=Rajeev
Hello, Rajeev

通过DockerFile文件来定义docker 镜像文件

现在我们需要在上面的根目录下面新建一个文件,将文件的名称命名为Dockerfile.

# 构建该项目的基础镜像
FROM golang:latest

# 添加作者信息
LABEL maintainer="wcs <wenchangshou@gmail.com>"

# 设置当前的工作目录
WORKDIR /app

# 复制 go mod和sum 文件 
COPY go.mod go.sum ./

# 下载所有的依赖
RUN go mod download
# 复制当前目录源文件到工作目录下面

COPY . .
# 编译GO程序
RUN go build -o main .

# 通过EXPOSE对外暴露服务的端口号

EXPOSE 8080
# 通过下面的命令来运行可执行文件 
CMD ["./main"]

编译和运行docker镜像

上面我们已经定义了DockerFile,现在我们需要通过命令将DockerFile生成构建和运行相应的映像

编译镜像

docker build -t go-docker .

你懂的国内应该各种原因,访问会特别的慢,你可以利用aliyun和daocloud来进行回事。
通过下面的命令来查看镜像是否生成成功.

docker image ls

REPOSITORY TAG IMAGE ID CREATED SIZE
go-docker lates 7654e880c09b 2 minutes ago 826MB
golang latest c3474fb0f20e 15 hours ago 809MB

运行docker镜像

现在我们终端可以运行之前生成的镜像了

docker run -d -p 8888:8080 go-docker

3c80b9cb6b21a4fd50ea8b157ace4f3a9757e92b0e3cf9192639d30299d03d73

查看运行的容器

你可以通过下面的命令来查看正在运行的容器
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3c80b9cb6b21 go-docker "./main" 45 seconds ago Up 45 seconds 0.0.0.0:8888->8080/tcp quirky_shamir

测试容器功能

现在我们可以通过curl来测试应用是否正常

curl http://localhost:8888\?name\=wcs
Hello, wcs

停止容器

现在运行完成之后我们就可以停止相应的容器。在每次创建容器的时候都会产生一个唯一的id,那个id用来标识相应的容器,之后可以通过该id进行相应的操作

docker container stop 3c80b9cb6b21

挂载本地目录到docker镜像中

现在我们开始一个新的实例,在dockerfile里面提供了一个volume的字段,通过该字段可以定义容器内部的共享的目录。定义完成之后,你可以在创建容器的时候指定volume目录与本地目录的绑定。
在以下Dockerfile中,我们声明一个volume路径/app/logs。容器会将日志文件写入/app/logs/app.log。运行docker映像时,我们可以将宿主的目录挂载到该卷上。完成此操作后,我们将能够从宿主的已挂载目录访问所有日志文件。

# 构建该项目的基础镜像
FROM golang:latest

# 添加作者信息
LABEL maintainer="wcs <wenchangshou@gmail.com>"

# 设置当前的工作目录
WORKDIR /app

# 编译参数
ARG LOG_DIR=/app/logs
# 创建log目录
RUN mkdir -p ${LOG_DIR}

#环境变量
ENV LOG_FILE_LOCATION=${LOG_DIR}/app.log

# 复制 go mod和sum 文件 
COPY go.mod go.sum ./

# 下载所有的依赖
RUN go mod download
# 复制当前目录源文件到工作目录下面

COPY . .
# 编译GO程序
RUN go build -o main .

# 通过EXPOSE对外暴露服务的端口号

EXPOSE 8080
# 声明Volumes
VOLUME [${LOG_DIR}]
# 通过下面的命令来运行可执行文件 
CMD ["./main"]

我们重新编译一个新的镜像

docker build -t go-docker-voluume -f Dockerfile.volume

现在我们创建宿主将要绑定的目录

mkdir -p ~/logs/go-docker
docker run -d -p 8889:8080 -v ~/logs/go-docker:/app/logs go-docker-volume 
394433aa0804dd24443d65090006e8becbe2aa26a883efe4324e79d4e085e261

现在我们发现docker已经将日志写入到宿主对象的目录当中

tail -f ~/logs/go-docker/app.log

2020/02/26 16:01:38 启动服务

优化镜像

通过上面的示例,我们生成了二个镜像,但是你们仔细查看,会发现即使这么小功能的一个程序,也会占用很大的存储空间。
docker image ls  ✔  4992  00:04:52
REPOSITORY TAG IMAGE ID CREATED SIZE
go-docker-volume latest 0b534f6bceaf 4 minutes ago 826MB
go-docker latest 7654e880c09b 19 minutes ago 826MB
golang latest c3474fb0f20e 16 hours ago 809MB
仅golang:latest镜像就占用了809MB,,而我们生成程序也占用了826M的空间。
上面之所以这样大,是因为golang:latest里面包含了整个go的运行环境以及相应的编译环境,而我们的应用程序仅在编译时需要这些,在编译完成之后仅运行就可以。根据这些需求我们可以将原有的镜像拆分成多步,第一步是编译,之后我们可以通过Alpine linux来进行最小化运行。
Dockerfile.multistage


FROM golang:latest as builder

LABEL maintainer="wcs<wenchangshou@gmail.com>"

WORKDIR /app

COPY go.mod go.sum ./

RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .



# 开始一个新的步骤
FROM alpine:latest

RUN apk --no-cache add ca-certificates

WORKDIR /root/
# 复制上一阶段的预构建二进制文件
COPY --from=builder /app/main .

EXPOSE 8080

CMD ["./main"] 

现在我们生成新的镜像

docker build -t go-docker-optimized -f Dockerfile.multistage .

现在我们再看压缩后的镜像

docker image ls

REPOSITORY TAG IMAGE ID CREATED SIZE
go-docker-optimized latest ed8e431271dc 23 seconds ago 14.1MB
go-docker-volume latest 0b534f6bceaf 11 minutes ago 826MB
go-docker latest 7654e880c09b 27 minutes ago 826MB
golang latest c3474fb0f20e 16 hours ago 809MB
alpine latest e7d92cdc71fe 5 weeks ago 5.59MB
现在你惊讶的发现压缩后的镜像仅有14.1M

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!