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

news2024/11/15 11:15:18

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

源程序

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

请添加图片描述

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

// display Animated Lissajous in a browser and can set arguments in queries.
package main

import (
	"errors"
	"fmt"
	"io"
	"log"
	"math"
	"net/http"
	"strconv"
	"sync"
)

var mu sync.Mutex
var count int

func 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*zscale
		return 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.coloradmin.cn/o/1228964.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++标准模板(STL)- 类型支持 (类型关系,检查一个类型是否派生自另一个类型,std::is_base_of)

类型特性 类型特性定义一个编译时基于模板的结构&#xff0c;以查询或修改类型的属性。 试图特化定义于 <type_traits> 头文件的模板导致未定义行为&#xff0c;除了 std::common_type 可依照其所描述特化。 定义于<type_traits>头文件的模板可以用不完整类型实例…

代码随想录-刷题第二天

977. 有序数组的平方 题目链接&#xff1a;977. 有序数组的平方 思路&#xff1a;双指针思想&#xff0c;数组是有序的且含有负数&#xff0c;其中元素的平方一定是两边最大。定义两个指针&#xff0c;从两端开始向中间靠近&#xff0c;每次比较两个指针的元素平方大小&#…

BLIP-2:冻结现有视觉模型和大语言模型的预训练模型

Li J, Li D, Savarese S, et al. Blip-2: Bootstrapping language-image pre-training with frozen image encoders and large language models[J]. arXiv preprint arXiv:2301.12597, 2023. BLIP-2&#xff0c;是 BLIP 系列的第二篇&#xff0c;同样出自 Salesforce 公司&…

聚观早报 |地平线征程6芯片来了;鸿蒙智行官网上线

【聚观365】11月20日消息 地平线征程6芯片来了 鸿蒙智行官网上线 Redmi K70系列设计细节 vivo X100系列明日开卖 特斯拉将交付10辆Cybertruck电动皮卡 地平线征程6芯片来了 据“地平线HorizonRobotics”公众号消息&#xff0c;在2023广州车展上&#xff0c;地平线宣布征程…

振弦式渗压计的安装方式及注意要点

振弦式渗压计的安装方式及注意要点 振弦式渗压计是一种高精度、高效率的地下水位测量仪器。它可以测量地下水位的高度&#xff0c;计算地下水的压力&#xff0c;从而推算出地下水的流量。对于地下水资源管理和保护、治理工程等方面具有非常重要的意义。在安装振弦式渗压计时&a…

[oeasy]python001_先跑起来_python_三大系统选择_windows_mac_linux

先跑起来 &#x1f94a; Python 什么是 Python&#xff1f; Python [ˈpaɪθɑ:n]是 一门 适合初学者 的编程语言 类库 众多 几行代码 就能 出 很好效果 应用场景丰富 在 各个应用领域 都有 行内人制作的 python 工具类库 非常专业、 好用 特别是 人工智能领域 pytho…

理化生考试系统中智能排考系统的功能

理化生考试系统是基于大数据技术以及智能图像识别技术研发的教育工具&#xff0c;可以帮助学生以及老师快捷准确的进行理化生实验的学习和考试。它主要应用于省、市级学生实验操作考核中考、学业考试的考试平台&#xff0c;通过信息技术应用让实验教学清晰高效&#xff0c;考前…

好莱坞罢工事件!再次警醒人类重视AI监管,人工智能矛盾一触即发!

原创 | 文 BFT机器人 关注国外新闻的应该都知道&#xff0c;最近焦点新闻是好莱坞史上最大规模的一场罢工运动。这场维持118天的罢工运动&#xff0c;终于在11月9号早上12点在好莱坞宣布结束。这场罢工运动虽是演员工会和代表资方的影视制片人联盟的茅盾&#xff0c;但直接引发…

电力感知边缘计算网关产品设计方案-业务流程设计

1.工业数据通信流程 工业数据是由仪器仪表、PLC、DCS等工业生产加工设备提供的,通过以太网连接工业边缘计算网关实现实时数据采集。按照现有的通信组网方案,在理想通信状态下可以保证有效获取工业数据的真实性和有效性。 边缘计算数据通信框架图: 2.边缘计算数据处理方案 …

学生适合用哪种台灯?公认好用的学生护眼台灯分享

作为一名家长&#xff0c;平常最关心的事情应该就是孩子健康问题。不过通过调查数据我们可以发现&#xff0c;我国的青少年儿童近视率是非常高的&#xff0c;达到了52.7%。也就是说平均每十个孩子当中就有5个是存在视力问题的&#xff01;看着这些触目惊心的数字&#xff0c;回…

系列一、JUC概述

一、概述 juc是java.util.concurrent在并发编程中使用的工具类。 二、进程 & 线程 2.1、进程概述 进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元&#xff0c;在传统的操作系统中&#xff0c;进程既是基本的分配单元&a…

AnyLogic 8.8.4:遗传优化和步行电梯 AnyLogic 8.8.5

AnyLogic 8.8.4&#xff1a;遗传优化和步行电梯 2023年7月19日 | 阿纳斯塔西娅日利亚耶娃 新发布行人建模 AnyLogic 8.8.4 现已可供下载。新版本有两个显着特点&#xff1a;内置基因优化和行人图书馆电梯。有关 AnyLogic 8.8.4 中发布的所有改进和更新的更多详细信息&#xf…

利用iptable实现ssh端口复用后门

目录 第一种方式&#xff1a;利用ICMP 远程遥控iptables进行端口复用 创建端口复用链 创建端口复用规则 开启开关 关闭开关 let’s do it 第二种方式&#xff1a;利用tcp数据包中的关键字 端口复用链 端口复用规则 开启开关 关闭开关 let‘s do it 第三种方式&am…

unordered_map,unordered_set模拟实现

目录 一 . 底层结构--哈希 1.直接定址法 2. 除留余数法 哈希桶 3. 一些定义 二 . 模拟实现哈希表 1.哈希表框架 ​编辑 2.插入 3.查找 4 . 删除 5.解决使用问题 6.完整代码 三 .实现unordered_map, unordered_set 1. 初步实现unordered_map, unordered_set 2.…

Leetcode—53.最大子数组和【中等】

2023每日刷题&#xff08;三十四&#xff09; Leetcode—53.最大子数组和 前缀和算法思想 参考灵茶山艾府 实现代码 #define MAX(a, b) ((a > b) ? (a) : (b)) #define MIN(a, b) ((a < b) ? (a) : (b)) int maxSubArray(int* nums, int numsSize) {int ans INT_…

ubuntu20.04在docker下运行ros-noetic进行开发

经常折腾虚拟机各双系统 &#xff0c; 想着不如把docker利用起来&#xff0c;下面算是一个初学者使用docker运行ros的记录&#xff1a; 1. 安装 使用官方安装脚本自动安装 curl -fsSL https://test.docker.com -o test-docker.shsudo sh test-docker.sh验证是否安装成功 doc…

信驰达科技加入智慧车联产业生态联盟ICCE,共创智慧车联未来

图1 信驰达加入智慧车联产业生态联盟 信驰达拥有60余项专利认证及软件著作权&#xff0c;以及BQB、SRRC、FCC、IC、CE、RoHS、REACH、KCC等数百个权威产品认证&#xff0c;公司是车联网联盟(CCC)和智慧车联产业生态联盟&#xff08;ICCE&#xff09;会员&#xff0c;已通过ISO…

[Kettle] 单变量统计

单变量统计是对数据进行单个变量的数据统计&#xff0c;以字段为单变量&#xff0c;可以分别对多个字段进行数据统计&#xff0c;统计类型有N(统计数量)、最小值、最大值、平均值、样本标准差、中位数和任意百分位数等 数据源 2018年上学期期末考试成绩(Kettle数据集9).xlsht…

基于Pyflwdir实现流域的提取(参照官网例子)

本文参照官网例子实现流域的提取,官方GitHub地址如下pyflwdir&#xff1a; 该工具包目前仅支持D8和LDD两种算法&#xff0c;在效率上具有较好的应用性&#xff0c;我用省级的DEM&#xff08;30米&#xff09;数据作为测试&#xff0c;输出效率可以满足一般作业需要。 环境env…

【LeetCode】每日一题 2023_11_20 最大子数组和(dp)

文章目录 刷题前唠嗑题目&#xff1a;最大子数组和题目描述代码与解题思路 刷题前唠嗑 LeetCode? 启动&#xff01;&#xff01;&#xff01; 今天是一道 LeetCode 的经典题目&#xff0c;如果是 LeetCode 老手&#xff0c;估计都刷过&#xff0c;话是这么说&#xff0c;但咱…