制作Go程序的Docker容器(以及容器和主机的网络问题)

今天突然遇到需要将 Go 程序制作成 Docker 的需求,所以进行了一些研究。方法很简单,但是官方文档和教程有些需要注意的地方,所以写本文进行记录。

源程序

首先介绍一下示例程序,示例程序是一个 HTTP 服务器,会显示sin(r)/r的图像,如下:

请添加图片描述

新建一个目录draw-surface,然后在里面新建一个draw-surface.go文件,内容为:

// display Animated Lissajous in a browser and can set arguments in queries.
package mainimport ("errors""fmt""io""log""math""net/http""strconv""sync"
)var mu sync.Mutex
var count intfunc main() {http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe("localhost:8000", nil))
}func handler(w http.ResponseWriter, r *http.Request) {w.Header().Set("Content-Type", "image/svg+xml")func2svg(w, r)
}var width, height int = 500, 400    
var cells int = 200                 
var xyrange int = 50.0              
var xyscale int = width / 2.0 / xyrange   
var zscale float64 = float64(height) * 0.4 
var angle float64 = math.Pi / 9            var sin, cos float64 = math.Sin(angle), math.Cos(angle)func func2svg(out io.Writer, r *http.Request) {fmt.Fprintf(out, "<svg xmlns='http://www.w3.org/2000/svg' "+"style='stroke: grey; fill: white; stroke-width: 0.7' "+"width='%d' height='%d'>", width, height)for i := 0; i < cells; i++ {for j := 0; j < cells; j++ {ax, ay, error1 := corner(i+1, j)bx, by, error2 := corner(i, j)cx, cy, error3 := corner(i, j+1)dx, dy, error4 := corner(i+1, j+1)if error1 == nil || error2 == nil || error3 == nil || error4 == nil {fmt.Fprintf(out, "<polygon points='%g,%g %g,%g %g,%g %g,%g'/>\n",ax, ay, bx, by, cx, cy, dx, dy)}}}fmt.Fprintf(out, "</svg>")
}func corner(i, j int) (float64, float64, error) {x := float64(xyrange) * (float64(i)/float64(cells) - 0.5)y := float64(xyrange) * (float64(j)/float64(cells) - 0.5)z := f(x, y)if math.IsInf(z, 1) {return math.NaN(), math.NaN(), errors.New("Result of f is non-finite")} else {sx := float64(width/2) + (x-y)*cos*float64(xyscale)sy := float64(height/2) + (x+y)*sin*float64(xyscale) - z*zscalereturn sx, sy, nil}
}func f(x, y float64) float64 {r := math.Hypot(x, y)return math.Sin(r) / r
}

然后是用以下命令初始化模块:

$ go mod init

这个程序来自于《The Go Programming language》,本文重点看main函数即可。由于是用来演示构建映像和容器,所以删除了 URL 参数部分。

不在容器里运行(本机)

这个程序可以直接在本机运行,此时main函数内容如下,地址设置为了localhost:8000

func main() {http.HandleFunc("/", handler)log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

可以直接在终端运行:

go run draw-surface.go

然后在浏览器中使用localhost:8000就可以看到前面的示例图的内容。

在容器中运行

构建Docker映像

官方文档很奇怪,Containerize your application 中介绍说用docker init来新建所需的文件,但是 Mac 上的 Docker(20.10.23)没有这个命令:

$ docker init
docker: 'init' is not a docker command.
See 'docker --help'

但是在介绍构建 Go 映像的教程 Build your Go image 里介绍的是手动新建。

所以这里使用手动新建 Docker 映像所需的配置文件Dockerfile

$ touch Dockerfile

然后以此输入下面的内容,解释请看注释:

# syntax=docker/dockerfile:1
# 继承自 Go 1.20 的映像,这样就可以使用Go的编译器等。把容器必做一台设备的话,就是在这个设备上安装了 Go
FROM golang:1.20# 工作目录为/app,这里的/是容器内部的根目录
WORKDIR /app
#将当前目录下的go.mod复制到容器内的工作目录,也就是/app下
COPY go.mod ./
# 使用命令安装所需的模块
RUN go mod download
#将当前目录下所有的go源代码文件复制到容器内的工作目录,也就是/app下
COPY *.go ./# 构建 Go 程序 draw-surface到根目录下
RUN CGO_ENABLED=0 GOOS=linux go build -o /draw-surface# 将该映像当做容器启动之后,执行该程序
CMD [ "/draw-surface" ]

然后需要修改一下 Go 源代码文件draw-surface.go中的一个地方,将地址中的localhost删除,只留下:8000。如下:

func main() {http.HandleFunc("/", handler)log.Fatal(http.ListenAndServe(":8000", nil))
}

为什么要这样修改呢?要解释这个问题有点偏题和涉及后面的内容了,所以放在最后的“题外话”部分。

接下来就可以进行构建映像了,命令如下:

$ docker build --tag draw-surface .
[+] Building 16.6s (15/15) FINISHED                                             => [internal] load build definition from Dockerfile                       0.0s=> => transferring dockerfile: 220B                                       0.0s=> [internal] load .dockerignore                                          0.0s=> => transferring context: 2B                                            0.0s=> resolve image config for docker.io/docker/dockerfile:1                 2.2s=> CACHED docker-image://docker.io/docker/dockerfile:1@sha256:ac85f380a6  0.0s=> [internal] load build definition from Dockerfile                       0.0s=> [internal] load metadata for docker.io/library/golang:1.20             1.6s=> [internal] load .dockerignore                                          0.0s=> [1/6] FROM docker.io/library/golang:1.20@sha256:77e4e426190723821471a  0.0s=> [internal] load build context                                          0.0s=> => transferring context: 3.50kB                                        0.0s=> CACHED [2/6] WORKDIR /app                                              0.0s=> CACHED [3/6] COPY go.mod ./                                            0.0s=> CACHED [4/6] RUN go mod download                                       0.0s=> [5/6] COPY *.go ./                                                     0.0s=> [6/6] RUN CGO_ENABLED=0 GOOS=linux go build -o /draw-surface          12.0s=> exporting to image                                                     0.5s=> => exporting layers                                                    0.5s=> => writing image sha256:d3e0c9b2bc8fb859f189ef7a51f7a478cf61f9d8a3e0c  0.0s=> => naming to docker.io/library/draw-surface                            0.0s

使用映像运行容器

然后就可以使用映像运行一个容器了,使用以下命令运行:

docker run --publish 8000:8000 draw-surface

这里的8000:8000是容器内外端口的映射,和代码中一样即可。如果你想使用 Docker Desktop 运行容器,那么端口映射需要写到 Dockerfile 中,然后才可以在第一次运行该映像的时候设置对应端口。

请添加图片描述

使用的话在浏览器中输入:http://localhost:8000即可看到示例图:
请添加图片描述

除了http://localhost:8000,还可以使用http://HostIp的内容:8000http://0.0.0.0:8000,但是不能使用http://IPAddress:8000来获取服务器返回的图像。关于如何获取容器HostIpIPAddress,在下面的“如何获取 Docker 容器的 IP 地址”有详细说明。

题外话

如何获取 Docker 容器的 IP 地址和主机地址

获取 Docker 容器的 IP 地址很简单。

Docker Desktop

如果使用 Docker Desktop,那么在容器部分查看详细信息,然后最下面就是。如下:

请添加图片描述

请添加图片描述

Docker CLI

如果是在终端中使用 Docker CLI,那么首先使用docker ps找到你想查询的容器的 ID,然后使用以下命令查看这个容器的详细信息:

$ docker inspect 容器ID

这里列出了很多信息,但是本文中我们需要的是HostIp,而不是IPAddress部分。你可以使用http://HostIp:8000http://0.0.0.0:8000http://localhost:8000,唯独不能使用http://IPAddress:8000获取服务器返回的图像(强调,只是本文的情况是这样,其他的项目需要根据情况而定)。

所以如果你想获取HostIp,那么可以将上面的命令修改成:

$ docker inspect 容器ID | grep "HostIp""HostIp": "","HostIp": "0.0.0.0",

如果你想获取IPAddress,那么使用下面的命令比较方便:

$ docker inspect 容器ID | grep "IPAddress""SecondaryIPAddresses": null,"IPAddress": "172.17.0.2","IPAddress": "172.17.0.2",

为什么使用:8000格式作为地址(使用静态IP行不行)

Docker 是一种虚拟技术,致力于用最小系统环境模拟单台设备。如果在本地网络上的一台设备上使用了localhost:8000这样的地址,而这个端口也对外开放。那么在本地网络上的其他设备中,在浏览器中使用http://服务器IP:8000这样的 URL 也无法看到图。只能在代码设置地址的时候,使用:8000说明端口或服务器IP:8000说明网络中的地址,然后访问时使用服务器IP:8000这样的 URL 就可以看到图了。不过一般考虑到移植问题,后者使用的少。

但是 Docker 容器虽然工作起来像这样,但不是完全符合,或者说默认情况下不是这样的。

Docker 容器不能使用服务器IP:8000声明和访问。因为在运行映像的时候,使用的8000:8000表示的是将主机的8000端口和容器的8000端口映射,但是容器并不是在主机的网络上,而是在 Docker 网络上(也就是在主机内部,有一个守护进程为各个容器分配 IP)。也就是说,主机就是一个Host,这些容器把端口映射到Host的端口上了。

这也解释了为什么这里无法使用容器的 IP 地址获取图像,因为主机的网络端口根本找不到他。但却可以使用HostIp的地址来获取图像,因为Host就是主机,是一台机器。

此外,不光主机本地地址http://localhost:8000可以,你还可以通过主机的 IP 来使用,也就是某个网络接口的 IP 地址,比如无线接口的IP:http://169.252.1.4:8000

参考阅读

IP address, Network address, and Host address Explained

Servers - Go

Networking overview - Docker Docs

Dockerfile reference - Docker Docs

How to get a Docker container’s IP address from the host - stackoverflow

希望能帮到有需要的人~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/198898.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FSOD论文阅读 - 基于卷积和注意力机制的小样本目标检测

来源:知网 标题:基于卷积和注意力机制的小样本目标检测 作者:郭永红&#xff0c;牛海涛&#xff0c;史超&#xff0c;郭铖 郭永红&#xff0c;牛海涛&#xff0c;史超&#xff0c;郭铖&#xff0e;基于卷积和注意力机制的小样本目标检测 [J/OL]&#xff0e;兵工学报. https://…

红海营销时代,内容占位的出海品牌更有机会营销占位

#01 品牌出海&#xff1a;内容占位就是品牌营销占位 红海营销时代&#xff0c;内容信息充斥着用户周边。无论线上还是线下&#xff0c;生活工作、休闲娱乐等不同场景内&#xff0c;广告信息均无孔不入。对于用户来说&#xff0c;能记住的品牌或者商品往往寥寥无几。 占位营销…

IntelliJ IDE 插件开发 |(一)快速入门

前言 IntelliJ IDEA 作为 Java 开发的首选 IDE&#xff0c;其强大、方便之处不必多说。不过&#xff0c;由于个人或者团队的个性化需求&#xff0c;我们或多或少会想对其功能进行拓展&#xff0c;这时就需要开发插件&#xff08;在 IntelliJ 平台下的所有 IDE 均可运行&#x…

iOS学习 --- Xcode 15 下载iOS_17.0.1_Simulator失败解决方法

1.去开发者官网下载安装包 https://developer.apple.com/download/all/?qiOS%2017 使用浏览器下载。 2.打开终端通过命令添加到xcode 命令如下&#xff1a; sudo xcode-select -s /Applications/Xcode.app(输入开始密码)xcodebuild -runFirstLaunch (等待一小会)xcrun simctl…

微服务测试怎么做

开发团队越来越多地选择微服务架构而不是单体结构&#xff0c;以提高应用程序的敏捷性、可扩展性和可维护性。随着决定切换到模块化软件架构——其中每个服务都是一个独立的单元&#xff0c;具有自己的逻辑和数据库&#xff0c;通过 API 与其他单元通信——需要新的测试策略和新…

一个用于操作Excel文件的.NET开源库

推荐一个高性能、跨平台的操作Excel文件的.NET开源库。 01 项目简介 ClosedXML是一个.NET第三方开源库&#xff0c;支持读取、操作和写入Excel 2007 (.xlsx&#xff0c; .xlsm)文件&#xff0c;是基于OpenXML封装的&#xff0c;让开发人员无需了解OpenXML API底层API&#xf…

学习网络编程No.10【深入学习HTTPS】

引言&#xff1a; 北京时间&#xff1a;2023/11/14/18:45&#xff0c;因为种种原因&#xff0c;上个月的文章昨天才更新&#xff0c;目前处于刷题前夕&#xff0c;算法课在看了。这次和以前不一样&#xff0c;因为以前对知识框架没有很好的理念&#xff0c;并不清楚相关知识要…

【Axure教程】滑动内容选择器

滑动内容选择器通常是一种用户界面组件&#xff0c;允许用户通过滑动手势在一组内容之间进行选择。这种组件可以在移动应用程序或网页中使用&#xff0c;以提供直观的图片选择体验。 那今天就教大家如何用中继器制作一个滑动内容选择器&#xff0c;我们会以滑动选择电影为案例…

【HarmonyOS开发】设备调试避坑指南

备注&#xff1a;通过开发验证&#xff0c;发现每个设备调试都会存在不小的差别&#xff0c;开发验证需要注意~ 1、预览器调试&#xff08;只能预览具有Entry修饰的文件&#xff09; 修改文件&#xff0c;预览器将自动刷新 注意&#xff1a;当我们只修改了Component 组件的文件…

【从入门到起飞】JavaSE—带你了解Map,HashMap,TreeMap的使用方法

&#x1f38a;专栏【Java基础】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【The truth that you leave】 &#x1f970;欢迎并且感谢大家指出我的问题 文章目录 &#x1f33a;双列集合的特点&#x1f384;Map&#x1f354;Ma…

系列九、JUC强大的辅助类

一、CountDownLatch 1.1、概述 让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒 1.2、功能 CountDownLatch主要有两个方法&#xff0c;当一个或多个线程调用await方法时&#xff0c;这些线程会阻塞&#xff0c;其它线程调用countDown方法会将计数器减1(调用countDown方…

机器学习介绍与分类

随着科学技术的不断发展&#xff0c;机器学习作为人工智能领域的重要分支&#xff0c;正逐渐引起广泛的关注和应用。本文将介绍机器学习的基本概念、原理和分类方法&#xff0c;帮助读者更好地理解和应用机器学习技术。 一、机器学习的基本概念 机器学习是一种通过从数据中学…

如何查看 class 文件的编译器版本

文章目录 原理分析解决方案其它解决方案javap 命令行工具 在平时的 Java 开发中&#xff0c;有时候我们需要知道某个 class 文件是由哪个版本的 Java 编译器编译生成的 原理分析 class 文件&#xff0c;即字节码文件&#xff0c;它有特定的二进制格式&#xff0c;这种格式是由…

【自我管理】To-do list已过时?学写Done List培养事业成功感

自我管理&#xff1a;已完成清单&#xff08;doneList&#xff09;培养事业成功感 待办事项清单常常让人感到压力山大&#xff0c;让人不想面对工作。但是&#xff0c;你知道吗&#xff1f;除了待办清单之外&#xff0c;还有一个叫做「已完成清单」的东西&#xff0c;它可能更符…

广西柳州机械异形零部件三维扫描3D抄数全尺寸测绘建模-CASAIM中科广电

一、背景介绍 复杂机械异形零部件具有不规则的形状和复杂的结构&#xff0c;给生产制造带来了很大的检测难度。为了确保零部件的制造质量和精度&#xff0c;需要对零部件进行全面的尺寸检测和分析。 CASAIM三维扫描仪在机械异形零部件全尺寸检测应用可以实现对机械异形零部件…

git rebase 和 git merge的区别?以及你对它们的理解?

文章目录 前言是什么分析区别后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;git操作相关 &#x1f431;‍&#x1f453;博主在前端领域还有很多知识和技术需要掌握&#xff0c;正在不断努力填补技术短板。(如果出现错误&#xff0c;感谢…

单链表相关面试题--2.反转一个单链表

/* 解题思路&#xff1a; 此题一般常用的方法有两种&#xff0c;三指针翻转法和头插法 1. 三指针翻转法记录连续的三个节点&#xff0c;原地修改节点指向 2. 头插法每一个节点都进行头插 */ // 三个指针翻转的思想完成逆置 struct ListNode* reverseList(struct ListNode* head…

前端为什么要工程化

前端为什么要工程化 文章目录 前端为什么要工程化传统开发的弊端一个常见的案例更多问题 工程化带来的优势开发层面的优势团队协作的优势统一的项目结构统一的代码风格可复用的模块和组件代码健壮性有保障团队开发效率高 求职竞争上的优势 现在前端的工作与以前的前端开发已经完…

SourceTree修改Git密码

SourceTree用的好好的&#xff0c;无奈公司隔段时间强制更改电脑密码。更改完成后SourceTree无法使用&#xff0c;重新输入密码。VS的nuget也是。查资料虽然也能比较快的解决&#xff0c;但是。。。。在此转载记录下。 1. 找到 SourceTree 配置文件所在目录 ‘userhosts’ 目录…