Skip to content

Files

Latest commit

8d041bb · Oct 24, 2021

History

History
593 lines (454 loc) · 19.5 KB

File metadata and controls

593 lines (454 loc) · 19.5 KB

九、与 Go 和 Docker 协作

在本章中,我们将介绍以下配方:

  • 打造你的第一个 Docker 形象
  • 运行您的 first Go Docker 容器
  • 将 Docker 映像推送到 Docker 注册表
  • 创建第一个用户定义的网桥网络
  • 在用户定义的网桥网络上运行 MySQL Docker 映像
  • 构建 Go web 应用 Docker 映像
  • 在用户定义的网桥网络上运行与 MySQL Docker 容器链接的 web 应用 Docker 容器

介绍

随着组织向 DevOps 的发展,Docker 也开始流行起来。Docker 允许将一个应用及其所有依赖项打包到一个标准化的软件开发单元中。如果该单元在您的本地计算机上运行,我们可以保证它将以完全相同的方式运行,从 QA 到登台,再到生产环境。有了本章介绍的概念知识,我们将能够轻松地编写 Docker 映像和部署 Docker 容器。

在本章中,我们将学习如何创建 Docker 映像和 Docker 容器以部署简单的 Go web 应用,接下来我们将了解如何将容器保存到映像并将其推送到 Docker 注册表,以及 Docker 网络的一些基本概念。

由于我们将与 Docker 合作,我假设它已安装并运行在您的本地计算机上。

打造你的第一个 Docker 形象

Docker 映像是我们应用的文件系统和配置,进一步用于创建 Docker 容器。有两种方法可以创建 Docker 映像,可以从头开始创建,也可以从父映像创建。在这个配方中,我们将学习如何从父映像创建 Docker 映像。这意味着创建的图像基本上是指父图像的内容以及Dockerfile修改父图像中的后续声明。

准备好了…

通过执行以下命令验证DockerDocker Machine是否已安装:

$ docker --version
Docker version 18.03.0-ce, build 0520e24  $ docker-machine --version
docker-machine version 0.14.0, build 89b8332

怎么做…

  1. 创建http-server.go,在这里我们将创建一个简单的 HTTP 服务器,它将呈现 Hello World!从命令行浏览http://docker-machine-ip:8080或执行curl -X GET http://docker-machine-ip:8080,如下所示:
package main
import 
(
  "fmt"
  "log"
  "net/http"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
)
func helloWorld(w http.ResponseWriter, r *http.Request) 
{
  fmt.Fprintf(w, "Hello World!")
}
func main() 
{
  http.HandleFunc("/", helloWorld)
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}
  1. 创建一个DockerFile,它是一个文本文件,包含构建图像所需的所有命令。我们将使用golang:1.9.2作为基础,或父映像,我们已使用Dockerfile中的FROM指令指定,如下所示:
FROM golang:1.9.2
 ENV SRC_DIR=/go/src/github.com/arpitaggarwal/
 ENV GOBIN=/go/bin

 WORKDIR $GOBIN

 # Add the source code:
 ADD . $SRC_DIR

 RUN cd /go/src/;

 RUN go install github.com/arpitaggarwal/;
 ENTRYPOINT ["./arpitaggarwal"]

 EXPOSE 8080

一切就绪后,目录结构应如下所示:

  1. 使用-t标志,从Dockerfile执行docker build命令生成一个镜像名为golang-image的 Docker 镜像,如下所示:
$ docker build --no-cache=true -t golang-image .

成功执行上述命令后,它将呈现以下输出:

如果要在公司代理后构建映像,则可能需要提供代理设置。您可以使用Dockerfile中的ENV语句添加环境变量,我们通常称之为运行时自定义,如下所示:

FROM golang:1.9.2
....
ENV http_proxy "http://proxy.corp.com:80"
ENV https_proxy "http://proxy.corp.com:80"
...

我们还可以使用--build-arg <varname>=<value>标志将构建时的代理设置传递给构建器,该标志称为构建时自定义,如下所示:

$ docker build --no-cache=true --build-arg http_proxy="http://proxy.corp.com:80" -t golang-image.

它是如何工作的…

通过执行以下命令验证是否已成功创建 Docker 映像:

$ docker images

这将列出所有顶级图像、它们的存储库、标记及其大小,如以下屏幕截图所示:

让我们了解一下我们创建的Dockerfile

  • FROM golang:1.9.2FROM指令指定了基础图像,对我们来说是golang:1.9.2

  • ENV SRC_DIR=/go/src/github.com/arpitaggarwal/:在这里,我们使用ENV语句将 Go 源代码目录设置为环境变量

  • ENV GOBIN=/go/bin:在这里,我们使用ENV语句设置GOBIN或目录,以生成可执行二进制文件作为环境变量

  • WORKDIR $GOBINWORKDIR指令为任何RUNCMDENTRYPOINTCOPYADD语句设置工作目录,这是我们图像的/go/bin

  • ADD . $SRC_DIR:这里,我们使用ADD语句将http-server.go从主机当前目录复制到golang-image/go/src/github.com/arpitaggarwal/目录

  • RUN cd /go/src/:在这里,我们使用RUN语句将golang-image中的当前目录更改为/go/src/

  • RUN go install github.com/arpitaggarwal/:这里我们编译/go/src/github.com/arpitaggarwal/http-server.go并在/go/bin目录下生成其可执行二进制文件

  • ENTRYPOINT ["./arpitaggarwal"]:这里,我们指定运行容器时生成的可执行二进制文件作为可执行文件运行

  • EXPOSE 8080EXPOSE指令通知 Docker,我们将从映像创建的容器将在运行时侦听网络端口8080

运行您的 first Go Docker 容器

Docker 容器包括应用及其所有依赖项。它与其他容器共享内核,并在主机操作系统的用户空间中作为独立进程运行。要运行实际的应用,我们必须从一个图像创建并运行容器,我们将在本配方中介绍。

怎么做…

执行docker run命令,从golang-image创建并运行 Docker 容器,使用-name标志将容器名称指定为golang-container,如下所示:

$ docker run -d -p 8080:8080 --name golang-container -it golang-image
 9eb53d8d41a237ac216c9bb0f76b4b47d2747fab690569ef6ff4b216e6aab486

docker run命令中指定的-d标志以守护程序模式启动容器,最后的哈希字符串表示golang-container的 ID。

它是如何工作的…

通过执行以下命令,验证 Docker 容器是否已创建并正在成功运行:

$ docker ps

成功执行上述命令后,它将为我们提供正在运行的 Docker 容器详细信息,如以下屏幕截图所示:

要列出所有 Docker 容器,无论它们是否正在运行,我们必须传递一个附加标志-a,即docker ps -a

浏览http://localhost:8080/或从命令行执行GET调用,如下所示:

$ curl -X GET http://localhost:8080/
 Hello World!

这将给我们带来你好世界!作为响应,这意味着 HTTP 服务器正在端口8080的 Docker 容器中侦听。

将 Docker 映像推送到 Docker 注册表

创建 Docker 映像后,存储或保存映像始终是最佳做法,以便下次必须从自定义映像启动容器时,不必担心或记住先前创建映像时执行的步骤。

您可以将映像保存在本地计算机或人工制品中,也可以保存到任何公共或私有 Docker 注册表,如 Docker Hub、Quay、Google 容器注册表、AWS 容器注册表等。在本食谱中,我们将学习如何将我们在之前的食谱中创建的图像保存或推送到 Docker Hub。

查看打造你的第一个 Docker 形象配方*。*

怎么做…

  1. 在 Docker Hub(https://hub.docker.com/上)创建您的帐户。

  2. 通过执行docker login命令,从命令行登录 Docker Hub,如下所示:

$ docker login --username arpitaggarwal --password XXXXX
 Login Succeeded
  1. 标记golang-image
$ docker tag golang-image arpitaggarwal/golang-image
  1. 通过执行docker images命令验证图像是否已成功标记:
$ docker images

执行上述命令将列出所有 Docker 图像,如以下屏幕截图所示:

  1. 通过执行docker push命令,将标记的图像推送到 Docker Hub,如下所示:
$ docker push arpitaggarwal/golang-image
 The push refers to a repository [docker.io/arpitaggarwal
 /golang-image]
 4db0afeaa6dd: Pushed
 4e648ebe6cf2: Pushed
 6bfc813a3812: Mounted from library/golang
 e1e44e9665b9: Mounted from library/golang
 1654abf914f4: Mounted from library/golang
 2a55a2194a6c: Mounted from library/golang
 52c175f1a4b1: Mounted from library/golang
 faccc7315fd9: Pushed
 e38b8aef9521: Mounted from library/golang
 a75caa09eb1f: Mounted from library/golang
 latest: digest: sha256:ca8f0a1530d3add72ad4e328e51235ef70c5fb8f38bde906a378d74d2b75c8a8 size: 2422

它是如何工作的…

要验证图像是否已成功推送到 Docker Hub,请浏览https://hub.docker.com/,使用您的凭据登录,登录后,您将看到标记的图像,如以下屏幕截图所示:

如果您对 Docker 容器执行了任何更改,并且希望保留这些更改以及图像的一部分,那么首先您必须在标记并将其推送到 Docker Hub 之前,使用docker commit命令将更改提交到新图像或同一图像,如下所示: $ docker commit <container-id> golang-image-new $ docker tag golang-image-new arpitaggarwal/golang-image $ docker push arpitaggarwal/golang-image

创建第一个用户定义的网桥网络

每当我们想通过容器名称将一个 Docker 容器连接到另一个 Docker 容器时,首先我们必须创建一个用户定义的网络。这是因为 Docker 不支持默认网桥网络上的自动服务发现。在本食谱中,我们将学习如何创建自己的网桥网络。

怎么做…

执行docker network命令,创建一个名为my-bridge-network的网桥网络,如下所示:

$ docker network create my-bridge-network
 325bca66cc2ccb98fb6044b1da90ed4b6b0f29b54c4588840e259fb7b6505331

它是如何工作的…

通过执行以下命令验证my-bridge-network是否已成功创建:

$ docker network ls
 NETWORK ID NAME DRIVER
 20dc090404cb bridge bridge
 9fa39d9bb674 host host
 325bca66cc2c my-bridge-network bridge
 f36203e11372 none null

要查看有关my-bridge-network的详细信息,请运行docker network inspect命令,后跟网络名称,如下所示:

$ docker network inspect my-bridge-network
 [
 {
 "Name": "my-bridge-network",
 "Id": "325bca66cc2ccb98fb6044b1da90ed4b6b0
     f29b54c4588840e259fb7b6505331",
 "Scope": "local",
 "Driver": "bridge",
 "EnableIPv6": false,
 "IPAM": 
     {
 "Driver": "default",
 "Options": {},
 "Config": 
       [
 {
 "Subnet": "172.18.0.0/16",
 "Gateway": "172.18.0.1"
 }
 ]
 },
 "Internal": false,
 "Containers": {},
 "Options": {},
 "Labels": {}
 }
 ]

在用户定义的网桥网络上运行 MySQL Docker 映像

每当我们运行 Docker 映像来创建和启动容器时,它都会使用默认的网桥网络,Docker 会在安装过程中创建该网络。要在特定网络上运行映像,该网络可以是用户定义的,也可以是 Docker 自动创建的其他两个网络之一(主机或无),我们必须提供附加的--net标志,其值作为docker run命令的网络名称。

在此配方中,我们将在上一配方中创建的用户定义网桥网络上运行一个 MySQL 映像,并将--net标志值传递为my-bridge-network

怎么做…

执行docker run命令,从mysql:latest映像创建并运行 MySQL Docker 容器,使用--name标志将容器名称指定为mysql-container,如下所示:

$ docker run --net=my-bridge-network -p 3306:3306 --name mysql-container -e MYSQL_ROOT_PASSWORD=my-pass -d mysql:latest
 c3ca3e6f253efa40b1e691023155ab3f37eb07b767b1744266ac4ae85fca1722

docker run命令中指定的--net标志将mysql-container连接到my-bridge-networkdocker run命令中指定的-p标志将容器的3306端口发布到主机3306端口。docker run命令中指定的-e标志将MYSQL_ROOT_PASSWORD值设置为my-pass,这是mysql:latest图像的环境变量。docker run命令中指定的-d标志以守护程序模式启动容器,最后的哈希字符串表示mysql-container的 ID。

它是如何工作的…

通过执行以下命令,验证 Docker 容器是否已创建并正在成功运行:

$ docker ps
 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
 f2ec80f82056 mysql:latest "docker-entrypoint.sh" 8 seconds ago Up 6 seconds 0.0.0.0:3306->3306/tcp mysql-container

再次检查my-bridge-network将向我们展示Containers部分的mysql-container细节,如下所示:

$ docker network inspect my-bridge-network
[
 {
 "Name": "my-bridge-network",
 "Id": "325bca66cc2ccb98fb6044b1da90ed
    4b6b0f29b54c4588840e259fb7b6505331",
 "Scope": "local",
 "Driver": "bridge",
 "EnableIPv6": false,
 "IPAM": 
    {
 "Driver": "default",
 "Options": {},
 "Config": 
      [
 {
 "Subnet": "172.18.0.0/16",
 "Gateway": "172.18.0.1"
 }
 ]
 },
 "Internal": false,
 "Containers": 
    {
 "f2ec80f820566707ba7b18ce12ca7a65
      c87fa120fd4221e11967131656f68e59": 
      {
 "Name": "mysql-container",
 "EndpointID": "58092b80bd34135d94154e4d8a8f5806bad
        601257cfbe28e53b5d7161da3b350",
 "MacAddress": "02:42:ac:12:00:02",
 "IPv4Address": "172.18.0.2/16",
 "IPv6Address": ""
 }
 },
 "Options": {},
 "Labels": {}
 }
]

构建 Go web 应用 Docker 映像

在此配方中,我们将构建一个 Docker 映像,该映像连接到在单独的 Docker 容器中运行的 MySQL 数据库实例。

怎么做…

  1. 创建http-server.go,在这里我们将创建一个简单的 HTTP 服务器和一个处理程序,它将为我们提供当前数据库的详细信息,如机器 IP、主机名、端口和所选数据库,如下所示:
package main
import 
(
  "bytes"
  "database/sql"
  "fmt"
  "log"
  "net/http"
  "github.com/go-sql-driver/mysql"
  "github.com/gorilla/mux"
)
var db *sql.DB
var connectionError error
const 
(
  CONN_PORT = "8080"
  DRIVER_NAME = "mysql"
  DATA_SOURCE_NAME = "root:my-pass@tcp(mysql-container:3306)/mysql"
)
func init() 
{
  db, connectionError = sql.Open(DRIVER_NAME, DATA_SOURCE_NAME)
  if connectionError != nil 
  {
    log.Fatal("error connecting to database : ", connectionError)
  }
}
func getDBInfo(w http.ResponseWriter, r *http.Request) 
{
  rows, err := db.Query("SELECT SUBSTRING_INDEX(USER(), 
  '@', -1) AS ip, @@hostname as hostname, @@port as port,
  DATABASE() as current_database;")
  if err != nil 
  {
    log.Print("error executing database query : ", err)
    return
  }
  var buffer bytes.Buffer
  for rows.Next() 
  {
    var ip string
    var hostname string
    var port string
    var current_database string
    err = rows.Scan(&ip, &hostname, &port, &current_database)
    buffer.WriteString("IP :: " + ip + " | HostName :: " + 
    hostname + " | Port :: " + port + " | Current 
    Database :: " + current_database)
  }
  fmt.Fprintf(w, buffer.String())
}
func main() 
{
  router := mux.NewRouter()
  router.HandleFunc("/", getDBInfo).Methods("GET")
  defer db.Close()
  err := http.ListenAndServe(":"+CONN_PORT, router)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}
  1. 创建一个DockerFile,它是一个文本文件,包含构建图像所需的所有命令,如下所示:
FROM golang:1.9.2

 ENV SRC_DIR=/go/src/github.com/arpitaggarwal/
 ENV GOBIN=/go/bin

 WORKDIR $GOBIN

 ADD . $SRC_DIR

 RUN cd /go/src/;
 RUN go get github.com/go-sql-driver/mysql;
 RUN go get github.com/gorilla/mux;

 RUN go install github.com/arpitaggarwal/;
 ENTRYPOINT ["./arpitaggarwal"]

 EXPOSE 8080

一切就绪后,目录结构应如下所示:

  1. 使用-t标志,从Dockerfile执行docker build命令生成一个镜像名为web-application-image的 Docker 镜像,如下所示:
$ docker build --no-cache=true -t web-application-image .

成功执行上述命令后,它将呈现以下输出:

它是如何工作的…

通过执行以下命令验证是否已成功创建 Docker 映像:

$ docker images

这将列出所有顶级图像、它们的存储库、标记及其大小,如以下屏幕截图所示:

我们在这个配方中创建的Dockerfile与我们在之前的一个配方中创建的Dockerfile完全相同,除了在构建映像时安装 Go MySQL 驱动程序和 Gorilla Mux URL 路由器的两个附加命令,如下所示:

...
RUN go get github.com/go-sql-driver/mysql;
RUN go get github.com/gorilla/mux;
...

请参见打造你的第一个 Docker 形象配方。

在用户定义的网桥网络上运行与 MySQL Docker 容器链接的 web 应用 Docker 容器

在这个配方中,我们将学习如何运行 Go web 应用 Docker 映像来创建一个容器,该容器将与在单独的 Docker 容器中运行的 MYSQL 数据库实例通信。

正如我们所知,Docker 不支持默认网桥网络上的自动服务发现,我们将使用在之前的一个方法中创建的用户定义网络来运行 Go web 应用 Docker 映像。

怎么做…

执行docker run命令,从web-application-image创建一个 web 应用 Docker 容器,使用--name标志将容器名称指定为web-application-container,如下所示:

$ docker run --net=my-bridge-network -p 8090:8080 --name web-application-container -d web-application-image
 ef9c73396e9f9e04c94b7327e8f02cf57ce5f0cd674791e2805c86c70e5b9564

docker run命令中指定的--net标志将mysql-container连接到my-bridge-networkdocker run命令中指定的-p标志将容器的8080端口发布到主机8080端口。docker run命令中指定的-d标志以守护程序模式启动容器,最后的哈希字符串表示web-application-container的 ID。

它是如何工作的…

通过执行以下命令,验证 Docker 容器是否已创建并正在成功运行:

$ docker ps

这将呈现以下输出:

浏览http://localhost:8090/as 将向我们提供机器 IP、主机名、端口和当前数据库详细信息,作为响应:

此外,再次检查my-bridge-network将在Containers部分向我们展示mysql-containerweb-application-container的详细信息,如下所示:

$ docker network inspect my-bridge-network
[
 {
 "Name": "my-bridge-network",
 "Id": "325bca66cc2ccb98fb6044b1da90ed4b6b0
    f29b54c4588840e259fb7b6505331",
 "Scope": "local",
 "Driver": "bridge",
 "EnableIPv6": false,
 "IPAM": 
    {
 "Driver": "default",
 "Options": {},
 "Config": 
      [
 {
 "Subnet": "172.18.0.0/16",
 "Gateway": "172.18.0.1"
 }
 ]
 },
 "Internal": false,
 "Containers": 
    {
 "08ce8f20c3205fa3e421083fa1077b
      673cdd10fd5be34f5ef431fead06219019": 
      {
 "Name": "web-application-container",
 "EndpointID": "d22f7076cf037ef0f0057ffb9fec
        0a07e07b44b442182544731db1ad10db87e4",
 "MacAddress": "02:42:ac:12:00:03",
 "IPv4Address": "172.18.0.3/16",
 "IPv6Address": ""
 },
 "f2ec80f820566707ba7b18ce12ca7a65
      c87fa120fd4221e11967131656f68e59": 
      {
 "Name": "mysql-container",
 "EndpointID": "58092b80bd34135d94154e4d8
        a8f5806bad601257cfbe28e53b5d7161da3b350",
 "MacAddress": "02:42:ac:12:00:02",
 "IPv4Address": "172.18.0.2/16",
 "IPv6Address": ""
 }
 },
 "Options": {},
 "Labels": {}
 }
]