Nomad系列-Nomad网络模式
系列文章
- Nomad 系列文章
概述
Nomad 的网络和 Docker 的也有很大不同, 和 K8s 的有很大不同. 另外, Nomad 不同版本(Nomad 1.3 版本前后)或是否集成 Consul 及 CNI 等不同组件也会导致网络模式各不相同. 本文详细梳理一下 Nomad 的主要几种网络模式
在Nomad 1.3发布之前,它自身并不支持发现集群中运行的其他应用程序。在集群中调度任务时,这是一个非常基本的要求。Nomad依赖于Consul来发现其他“服务”,并为注册和获取服务记录提供一流的支持,这使得事情变得更容易。Consul通过各种机制提供记录,例如REST API,DNS和Consul模板,这些模板在可以注入到应用程序中的Go模板中呈现服务的确切IP/端口。
学习 Nomad 的一个难点在于, Nomad 往往和 Consul 一起运行, 那么对于这种情况来说,一个主要的学习曲线是,我们必须首先了解Consul是如何工作的,部署一个Consul集群, 同时要融会贯通 2 个软件就很难了。Nomad 1.3 解决了这个问题的一部分(即不需要运行Consul就可以进行基本的服务发现),非常适合刚刚开始使用基于Nomad的网络。
场景一: 在主机上公开应用
从最简单的用例开始:你有一个 redis 容器,你想把它暴露给主机。 相当于我们想要做的docker run
是 :
docker run --rm -p=6379 redis
此命令公开主机上的动态端口。要查看端口号到底是什么,您可以执行 docker ps
并在 PORTS 下找到类似于 0.0.0.0:49153->6379/tcp
的输出。
$ redis-cli -p 49153
127.0.0.1:49153> ping
PONG
那么, 在 Nomad 中相同的操作如何实现?
job "redis" {type = "service"group "redis" {network {mode = "host"port "redis" {to = 6379}}task "redis" {driver = "docker"config {image = "redis"ports = ["redis"]}}}
}
在几行配置中,我们有一个正在运行的Docker容器,它公开了一个动态端口 30627:
我们可以通过主机上的 redis-cli
连接到它:
$ redis-cli -p 30627
127.0.0.1:30627> ping
PONG
🐾Warning:
在
task.config
部分中有ports
很重要。Nomad将此信息传递给主机上运行的 docker 守护进程。因此,除非您指定在容器中通告哪些端口,否则它不会知道是否要公开6379。
暴露静态端口
一种不太常见的情况是将应用程序绑定到主机上的静态端口, 只需在 port
块中添加一个 static
行:
network {port "redis" {static = 6379}}
当我们再次部署相同的文件时,我们可以看到端口分配已经从动态端口更改为我们分配的静态端口。但是注意需要确保没有其他应用程序侦听同一接口和端口,否则必然会导致冲突。
静态端口典型的使用场景就是: Ingress. 比如 Traefik 可以使用静态端口监听 80 和 443.
场景二: 与同一 Group 内的 Redis 通信
对于这个场景,我们假设有一个应用程序需要与Redis通信。在这个场景中,Redis用途是临时缓存,所以可以将它们部署在同一个 Group 中。
一个 Group 可以包含多个 Task。这里需要知道的重要一点是,同一 Group 将始终具有自己的共享网络命名空间(类似K8s中Pod中的多个Container具有共享网络命名空间)。这意味着,如果您在组中有2个 Task,则它们都可以访问相同的网络命名空间。这允许两个 Task 在同一网络接口上相互通信。
job "hello" {type = "service"group "app" {network {mode = "host"port "app" {static = 8080}port "redis" {static = 6379}}task "redis" {driver = "docker"config {network_mode = "host"image = "redis"ports = ["redis"]}}task "app" {driver = "docker"env {DEMO_REDIS_ADDR = "${NOMAD_ADDR_redis}"}config {network_mode = "host"image = "mrkaran/hello-app:1.0.0"ports = ["app"]}}}
}
详细说明如下:
- 您可以看到我们在同一 Group 下定义了 task
app
和taskredis
。这意味着Nomad将在同一客户端上共同定位这两个Task(因为它们不仅倾向于共享相同的网络命名空间,而且还共享公共分配目录-这使得跨任务共享文件变得非常容易)。 - 我们使用
NOMAD_ADDR_redis
来获取 redis task 的IP:Port
组合。这在运行时由Nomad注入。您可以在这里找到运行时环境变量的列表。 - 这是快速测试/开发设置的理想选择,因为您不希望服务发现等问题,并且希望以最小的代价连接到您的应用程序。
如果您要从基于 docker-compose 的环境迁移,以上配置非常适合(但是实现还是不同, Nomad利用了主机网络),您可以将此模板用于您的服务。这种方法的最大限制是它使用主机网络。
场景三: 跨不同的 Group 进行通信
如上所述, 如果您有相关的 Task(如init
task,您希望在 task 开始前获取文件),同一个 Group 很有用(类似K8s Pod 的 init container)。但是使用 group 的缺点是您不能独立地扩展 task。在上面的例子中,我们将Redis和App放在同一个 Group 中,但这意味着如果你增加同一个 Group 的 count
来扩展 app,你最终也会扩展Redis容器。这是不可取的,因为Redis可能不需要与应用程序成比例地扩展。
创建多个 Group 的方法是将任务拆分到各自的组中:
job "hello" {type = "service"group "app" {count = 1network {mode = "host"port "app" {static = 8080}}task "app" {driver = "docker"env {DEMO_REDIS_ADDR = "localhost:6379"}config {image = "mrkaran/hello-app:1.0.0"ports = ["app"]}}}group "redis" {count = 1network {mode = "host"port "redis" {static = 6379}}task "redis" {driver = "docker"config {image = "redis"ports = ["redis"]}}}
}
提交此 Job 后,您将获得2个分配ID(每个 Group 会创建一个 alloc
)。这里的关键点是这两个 Group 都有自己的网络命名空间。因此,我们实际上没有任何方法可以访问其他应用程序(我们不能向上面这样依赖主机网络,因为无法保证这两个 Group 都部署在同一个节点上)。
现在由于组是分开的, app
容器不知道 redis
(反之亦然):
env | grep NOMAD
NOMAD_REGION=global
NOMAD_CPU_LIMIT=4700
NOMAD_IP_app=127.0.0.1
NOMAD_JOB_ID=hello
NOMAD_TASK_NAME=app
NOMAD_SECRETS_DIR=/secrets
NOMAD_CPU_CORES=1
NOMAD_NAMESPACE=default
NOMAD_ALLOC_INDEX=0
NOMAD_ALLOC_DIR=/alloc
NOMAD_JOB_NAME=hello
NOMAD_HOST_IP_app=127.0.0.1
NOMAD_SHORT_ALLOC_ID=a9da72dc
NOMAD_DC=dc1
NOMAD_ALLOC_NAME=hello.app[0]
NOMAD_PORT_app=8080
NOMAD_GROUP_NAME=app
NOMAD_PARENT_CGROUP=nomad.slice
NOMAD_TASK_DIR=/local
NOMAD_HOST_PORT_app=8080
NOMAD_MEMORY_LIMIT=512
NOMAD_ADDR_app=127.0.0.1:8080
NOMAD_ALLOC_PORT_app=8080
NOMAD_ALLOC_ID=a9da72dc-94fc-6315-bb37-63cbeef153b9
NOMAD_HOST_ADDR_app=127.0.0.1:8080
服务发现
app
Group 需要在连接到 redis
之前发现它。有多种方法可以做到这一点,但我们将介绍两种更常见的标准方法。
使用 Nomad Native Service Discovery
这是在Nomad 1.3中推出的功能。在这次发布之前,Nomad 不得不依靠 Consul 来完成这一任务。但是有了Nomad中内置的原生服务发现,事情就简单多了。让我们对作业文件进行以下更改。在每个 Group 中,我们将添加一个 service
定义:
group "app" {count = 1network {mode = "host"port "app" {to = 8080}}service {name = "app"provider = "nomad"port = "app"}// task is the same}group "redis" {count = 1network {mode = "host"port "redis" {to = 6379}}service {name = "redis"provider = "nomad"port = "redis"}// task is the same}
如上,我们添加了一个新的 service
块,并删除了 static
端口。当我们使用服务发现时,不需要绑定到静态端口。
提交作业后,我们可以使用 nomad service list
命令确保服务已注册到Nomad。
nomad service list
Service Name Tags
app []
redis []
要了解特定服务的详细信息,我们可以使用 nomad service info
:
$ nomad service info app
Job ID Address Tags Node ID Alloc ID
hello 127.0.0.1:29948 [] d92224a5 5f2ac51f
$ nomad service info redis
Job ID Address Tags Node ID Alloc ID
hello 127.0.0.1:22300 [] d92224a5 8078c9a6
如上, 我们可以看到每个服务中的动态端口分配。要在我们的应用程序中使用此配置,我们将其模板化:
task "app" {driver = "docker"template {data = <<EOH
{{ range nomadService "redis" }}
DEMO_REDIS_ADDR={{ .Address }}:{{ .Port }}
{{ end }}
EOHdestination = "secrets/config.env"env = true}config {image = "mrkaran/hello-app:1.0.0"ports = ["app"]}}
我们添加了 template
节,它将在容器中插入环境变量。我们遍历 nomadService
并获取 redis
服务的地址和端口。这使得其他节点上的任务可以方便地发现彼此。
使用 Consul 服务发现
只需调整 service
块中的 provider
,我们就可以使用Consul代理进行服务发现。
service {name = "app"provider = "consul"port = "app"}task "app" {driver = "docker"template {data = <<EOH
{{ range service "redis" }}
DEMO_REDIS_ADDR={{ .Address }}:{{ .Port }}
{{ end }}
EOH
🐾Warning:
注意
range nomadService
也改为了range service
前提是要确保正在运行Consul并已将Nomad连接到它。具体请参阅该文档。
其余的事情几乎保持不变。只用两行代码就可以在Nomad/Consul之间切换来发现服务。
另外, 使用Consul会有更多的优势:
- 可以使用DNS查询服务的地址:
doggo redis.service.consul @tcp://127.0.0.1:8600
NAME TYPE CLASS TTL ADDRESS NAMESERVER
redis.service.consul. A IN 0s 172.20.10.3 127.0.0.1:8600
- 可由Nomad以外的应用程序访问。如果 consul 被Nomad集群外的其他应用程序使用,它们仍然可以获得对应的地址(使用DNS或REST API)
当然,Nomad Native Service Discovery 非常适合本地/边缘环境设置,甚至是生产中的较小用例,因为它不再需要 Consul!
场景四: 限制对某些 Namespace 的访问
在上述所有场景中,我们发现服务会暴露给本地Nomad客户端。如果您在集群上运行多个 Namespace,您可能希望根本不公开它们。此外,您可能希望表达应用程序可以访问特定服务的细粒度控制。所有这些都可以通过服务网格实现。Nomad提供了一种通过Consul Connect建立“服务网格”的方法。Consul Connect可以进行mTLS和服务授权。在引擎盖下,它是一个与您的应用程序一起运行的Envoy代理(或sidecar)。 Consul 代理为您配置Envoy配置,因此这一切都非常无缝。
要做到这一点,我们首先需要的是 bridge
网络模式。此网络模式实际上是一个CNI插件,需要在 /opt/cni/bin
中单独安装。按照这里提到的步骤:
network {mode = "bridge"port "redis" {to = 6379}}
Redis 中的服务被 Consul Connect Ingress 所调用:
service {name = "redis"provider = "consul"port = "6379"connect {sidecar_service {}}}
这是一个空块,因为我们不需要在这里定义任何上游。其余值将为默认值。
接下来,我们为 app 创建一个服务,这是一个Consul Connect Egress:
service {name = "app"provider = "consul"port = "app"connect {sidecar_service {proxy {upstreams {destination_name = "redis"local_bind_port = 6379}}}}}
这里我们为 redis 定义一个上游。在这里,当 app 想要与redis通信时,它会与 localhost:6379
对话,这是Envoy sidecar正在监听的本地端口。我们可以使用 netstat
来验证:
$ netstat -tulpvn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.2:19001 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:23237 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:6379 0.0.0.0:* LISTEN -
tcp6 0 0 :::8080 :::* LISTEN 1/./hello.bin
流量从这个端口发送到它通告的端口上的另一个Envoy代理(并且Consul自动配置)。该Envoy代理进一步将流量发送到端口6379上的 redis 容器。代理流量通过mTLS进行安全加密并授权(通过Consul Intentions -本文不做介绍)。
场景五: 向最终用户公开服务
在第一个场景中,我们讨论了如何使用静态端口。事实证明,如果你想定义一个Traffic Ingress服务,它非常有用。与K8s不同的是,Nomad没有任何Ingress Controller,所以最好的方法是将这些Web代理作为 system job 部署在每个节点上(这意味着它可以确保在每个客户端节点上运行),并将它们绑定到静态端口(比如443/80)。然后,配置 LB 并将所有Nomad节点注册为 Target IP,其端口将是您定义的静态端口。这些Ingress代理(比如Traefik/Nginx)可以通过上面提到的任何模式与您的应用程序通信。
📝Notes:
在上一篇文章中, 我们并没有配置 LB 后面对接所有 Traefik. 相反, 我们直接访问某一个特定节点的 Traefik 的 80/443 端口.
通常,您希望为入口代理使用“基于主机”的路由模式来做出路由决策。
例如,如果您有一个指向ALB的 a.example.org
DNS记录。现在,当请求到达ALB时,它会转发到任何一个Traefik/NGINX。为了使 NGINX 正确地将流量路由到a
service,您可以使用“Host”报头。
总结
这些是我所知道的一些常见的网络模式。由于其中一些概念并不是非常简单,我希望解释有助于带来一些清晰。
关于这个主题还有很多,比如 Consul Gateway 和多种CNI,它们可以调整集群中的网络的底层细节,但这些都是一些非常高级的主题,超出了本文的范围。后续有机会可以再做展开.
📚️参考文档
- Understanding Nomad Networking Patterns - YouTube
- Understanding Networking in Nomad | Karan Sharma (mrkaran.dev)
三人行, 必有我师; 知识共享, 天下为公. 本文由东风微鸣技术博客 EWhisper.cn 编写.
相关文章:

Nomad系列-Nomad网络模式
系列文章 Nomad 系列文章 概述 Nomad 的网络和 Docker 的也有很大不同, 和 K8s 的有很大不同. 另外, Nomad 不同版本(Nomad 1.3 版本前后)或是否集成 Consul 及 CNI 等不同组件也会导致网络模式各不相同. 本文详细梳理一下 Nomad 的主要几种网络模式 在Nomad 1.3发布之前&a…...

OpenCV项目开发实战--实现面部情绪识别对情绪进行识别和分类及详细讲解及完整代码实现
文末提供免费的完整代码下载链接 面部情绪识别(FER)是指根据面部表情对人类情绪进行识别和分类的过程。通过分析面部特征和模式,机器可以对一个人的情绪状态做出有根据的猜测。面部识别的这个子领域是高度跨学科的,借鉴了计算机视觉、机器学习和心理学的见解。 在这篇研究…...

Validate表单组件的封装
之前一直是直接去使用别人现成的组件库,也没有具体去了解人家的组件是怎么封装的,造轮子才会更好地提高自己,所以尝试开始从封装Form表单组件开始 一:组件需求分析 本次封装组件,主要是摸索封装组件的流程,…...

企业架构LNMP学习笔记32
企业架构LB-服务器的负载均衡之LVS实现: 学习目标和内容 1)能够了解LVS的工作方式; 2)能够安装和配置LVS负载均衡; 3)能够了解LVS-NAT的配置方式; 4)能够了解LVS-DR的配置方式&…...

基于Jetty9的Geoserver配置https证书
1.环境准备 由于Geoserver自带的jetty版本不具备https模块,所以需要下载完整版本jetty。这里需要先查看本地geoserver对应的jetty版本,进入geoserver安装目录,执行如下命令。 java -jar start.jar --version Jetty Server Classpath: -----…...

企业互联网暴露面未知资产梳理
一、互联网暴露面梳理的重要性 当前,互联网新技术的产生推动着各种网络应用的蓬勃发展,网络安全威胁逐渐蔓延到各种新兴场景中,揭示着网络安全威胁不断加速泛化。当前网络存在着许多资产,这些资产关系到企业内部的安全情况&#…...

【动态规划刷题 12】等差数列划分 最长湍流子数组
139. 单词拆分 链接: 139. 单词拆分 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。 注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。 示例 1: 输入: …...

react-redux 的使用
react-redux React Redux 是 Redux 的官方 React UI 绑定库。它使得你的 React 组件能够从 Redux store 中读取到数据,并且你可以通过dispatch actions去更新 store 中的 state 安装 npm install --save react-reduxProvider React Redux 包含一个 <Provider…...

77 # koa 中间件的应用
调用 next() 表示执行下一个中间件 const Koa require("koa");const app new Koa();app.use(async (ctx, next) > {console.log(1);next();console.log(2); });app.use(async (ctx, next) > {console.log(3);next();console.log(4); });app.use(async (ctx,…...

【css】z-index与层叠上下文
z-index属性用来设置元素的堆叠顺序,使用z-index有一个大的前提:z-index所作用元素的样式列表中必须有position属性并且属性值为absolute、relative或fixed中的一个,否则z-index无效。 层叠上下文 MDN讲解 我们给元素设置的z-index都是有一…...

系统架构设计师(第二版)学习笔记----多媒体技术
【原文链接】系统架构设计师(第二版)学习笔记----多媒体技术 文章目录 一、多媒体概述1.1 媒体的分类1.2 多媒体的特征1.3 多媒体系统的基本组成 二、多媒体系统的关键技术2.1 多媒体系统的关键技术2.2 视频技术的内容2.3 音频技术的内容2.4 数据压缩算法…...

【面试经典150 | 数组】合并两个有序数组
文章目录 写在前面Tag题目来源题目解读解题思路方法一:合并排序方法二:双指针方法三:原地操作-从前往后方法四:原地操作-从后往前 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章…...

系统架构设计专业技能 ·操作系统
现在的一切都是为将来的梦想编织翅膀,让梦想在现实中展翅高飞。 Now everything is for the future of dream weaving wings, let the dream fly in reality. 点击进入系列文章目录 系统架构设计高级技能 操作系统 一、操作系统概述二、进程管理2.1 进程概念2.2 进…...

CSP 202209-1 如此编码
答题 题目就是字多 #include<iostream>using namespace std;int main() {int n,m;cin>>n>>m;int a[n],c[n1];c[0]1;for(int i0;i<n;i){cin>>a[i];c[i1]c[i]*a[i];}for(int i0;i<n;i){cout<<(m%c[i1]-m%c[i])/c[i]<< ;} }...

windows安装向量数据库milvus
本文介绍windows下安装milvus的方法。 一.Docker安装 1.1docker下载 首先到Docker官网上下载docker:Docker中文网 官网 1.2.安装前前期准备 先使用管理员权限打开windows powershell 然后在powershell里面输入下面那命令,启用“适用于 Linux 的 Windows 子系统”…...

Qt中,QScript对JavaScript的内置接口支持情况
支持 JSON.parse()/stringify() Object.keys() 不支持 console.info()/debug()/warn()/error() window setTimeout() clearTimeout() setInterval() clearInterval() 后续添加更多接口支持情况~...

C语言基础-typedef的用法
文章目录 前言基础用法高阶用法typedef作用于数组typedef作用于函数指针 总结 前言 熟悉C语言的同学,应该都见过typedef,但可能对typedef的用法并不是真的了解。本文介绍几种typedef的用法,相信会有所帮助 基础用法 一般typedef用来声明一个…...

Linux中安装MySQL5.7.42
1. 首先,下载mysql5.7.42的安装包(下方是下载地址),选择红色框框的下载(注意的是,这个链接只提供5.7的版本下载,可能还会更新,不一定打开就是5.7.42的版本,后续可能会有4…...

网络基础--1.网络纵横
网络的发展历程 计算机由原来的只能单一处理信息(单用户批处理)逐步发展为多用户批处理,可以实现一台计算机连接多个终端同时使用一台计算机(分时系统),但是多个终端之间不能相互通信,再发展成为…...

Django TypeError: Abstract models cannot be instantiated.错误解决方案
问题 [2023-09-05 10:23:41][dvadmin.utils.exception.CustomExceptionHandler():64] [ERROR] Traceback (most recent call last): File “D:\InstallSpace\Anaconda3\envs\py39\lib\site-packages\rest_framework\views.py”, line 506, in dispatch response handler(requ…...

vscode使用delve调试golang程序
环境配置 delve仓库,含有教程:https://github.com/go-delve/delve golang的debugging教程:https://github.com/golang/vscode-go/wiki/debugging > go version go version go1.20 windows/amd64> go install github.com/go-delve/de…...

如何从任何苹果、Windows或安卓设备访问iCloud照片
本文介绍了如何在各种设备上访问iCloud照片库,包括iPhone和iPad、Mac、Windows PC和Android设备。说明适用于iOS 13及以上版本、iPadOS 13及以上、macOS Big Sur(10.16)和Catalina(10.15)、Windows 10或11以及Android 10。 从iPhone、iPod Touch和iPad访问iCloud照片 照…...

关于“找不到mfc140u.dll,无法继续执行代码”问题的分析处理方法
我想和大家分享一个在编程过程中经常会遇到的问题——找不到mfc140u.dll,无法继续执行代码。找不到 mfc140u.dll,这个问题可能会让我们感到困扰。mfc140u.dll 是 Microsoft Foundation Classes(MFC)库的一部分,它是一个 Windows 系…...

用 TripletLoss 优化bert ranking
下面是 用 TripletLoss 优化bert ranking 的demo import torch from torch.utils.data import DataLoader, Dataset from transformers import BertModel, BertTokenizer from sklearn.metrics.pairwise import pairwise_distancesclass TripletRankingDataset(Dataset):def __…...

Tomcat安装及使用
这里写目录标题 Tomcat一.java基础1.java历史2.java组成3.实现动态网页功能serveltjsp 4.jdkJDK 和 JRE 关系安装openjdk安装oracle官方JDK 二.tomcat基础功能1.Tomcat介绍2.安装tomcat二进制安装Tomcat 3.配置文件介绍及核心组件配置文件组件 4.状态页5.常见的配置详解6.tomca…...

法国新法案强迫 Firefox 等浏览器审查网站
导读Mozilla 基金会已发起了一份请愿书,旨在阻止法国政府强迫 Mozilla Firefox 等浏览器审查网站。 据悉,法国政府正在制定一项旨在打击网络欺诈的 SREN 法案 (“Projet de loi Visant scuriser et reguler lespace numrique”),包含大约 2…...

开源电商项目 Mall:构建高效电商系统的终极选择
文章目录 Mall 项目概览前台商城系统后台管理系统系统架构图业务架构图 模块介绍后台管理系统 mall-admin商品管理:功能结构图-商品订单管理:功能结构图-订单促销管理:功能结构图-促销内容管理:功能结构图-内容用户管理࿱…...

QT(9.1)对话框与事件处理
作业: 1. 完善登录框 点击登录按钮后,判断账号(admin)和密码(123456)是否一致,如果匹配失败,则弹出错误对话框,文本内容“账号密码不匹配,是否重新登录”&…...

C++项目实战——基于多设计模式下的同步异步日志系统-③-前置知识补充-设计模式
文章目录 专栏导读六大原则单例模式饿汉模式懒汉模式 工厂模式简单工厂模式工厂方法模式抽象工厂模式 建造者模式代理模式 专栏导读 🌸作者简介:花想云 ,在读本科生一枚,C/C领域新星创作者,新星计划导师,阿…...

C++ 新旧版本两种读写锁
一、简介 读写锁(Read-Write Lock)是一种并发控制机制,用于多线程环境中实现对共享资源的高效读写操作。读写锁允许多个线程同时读取共享资源,但在有写操作时,需要互斥地独占对共享资源的访问,以确保数据的…...