系列文章
- 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 = 1
network {
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 = 1
network {
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 = 1
network {
mode = "host"
port "app" {
to = 8080
}
}
service {
name = "app"
provider = "nomad"
port = "app"
}
// task is the same
}
group "redis" {
count = 1
network {
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 }}
EOH
destination = "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 编写.
发表回复