无垠之码

深度剖析代码之道


consul爱的初体验

Consul是HashiCorp研发的一款服务发现与服务治理工具,常用于分布式系统、微服务和混合云环境。服务发现,指服务启动后向Consul注册自己,其他服务通过DNS或HTTP API查询可用实例报告服务位置。服务治理,在分布式微服务架构中,对大量独立服务进行统一管理、控制和优化的一整套机制与方法。

  • 服务注册与发现
  • 配置与策略管理,内置的KV数据库可以作为运行时的轻量级配置中心功能
  • 健康检查,支持TCP|HTTP|Script|TTL心跳的健康检查,监控服务状态
  • 服务通信与路由,基于标签的选路机制(相同服务的不同版本),基于名称的选路机制(web-v${x})
  • 容错与稳定性治理,服务崩溃策略(超时,重试,熔断,限流,降级)
  • 可观测性,提供服务的指标(Metrics),日志(Logs),链路追踪(Tracing)
  • 安全与访问控制,支持服务身份验证,零信任通信

consul运行模式: [应用服务] → [Consul客户端agent] → [Consul服务集群]

1.安装与配置

与其他hashicorp产品一样,consul的安装十分简单,官方网站基本针对每种操作系统都有详细的介绍。 以ubuntu系统举例,导入hashicorp的公钥,增加官方源后,软件管理工具即可快捷安装

wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install consul

consul运行于两种模式的一种,客户端与服务器

服务器配置

# consul.hcl
datacenter = "Datacenter-0"
data_dir = "/opt/consul"        # 数据存放位置,低版本该配置不生效,默认存放在/var/lib/consul中
client_addr = "0.0.0.0"
ui_config{
  enabled = true                # 内置ui界面
}
ports {
  grpc_tls = 8502
}
server = true
bind_addr = "192.168.5.225"    # consul服务器集群通信地址,一般多3-5server端同时运行,提供高可用服务
bootstrap_expect=1
connect {
  enabled = true
  ca_provider = "consul"
}
telemetry {
  prometheus_retention_time = "72h"
  disable_hostname = true
}
acl = {
  enabled = true               # 开启访问控制,控制运行连接的consul客户端
  default_policy = "deny"      # 默认禁止
  down_policy = "extend-cache" # 客户端与服务器连接断开,允许读取本地缓存
  tokens = {
    default = "d0c24029-fcf5-e83d-7876-acc18ac247f0"  # 允许查询
  }
}

consul acl bootstrap # 创建超级权限token
export CONSUL_HTTP_TOKEN=b9e0ee65-70d7-6586-677a-f545dcb5d2b0 (简化操作,下面操作遇到权限问题,都使用根权限)

# bootstrap-token,(root-token)无限权限
# agent-token,供agent的server或client在集群里注册自身、注册服务、更新sidecar配置时使用
# policy-token,即普通token,限定权限,例如某个namespace、KV、服务或connect路由的读写权限

# 在创建具体token时,需要提前创建token的策略,即在定义token的使用范围
# agent-policy.hcl, 该策略允许所有节点可被注册和访问,允许所有服务可读
node_prefix "" {
   policy = "write"
}
service_prefix "" {
   policy = "write"
}

consul acl policy create  -name "agent-token-policy" -description "Agent Token Policy" -rules @agent-policy.hcl
consul acl token create -description "Agent Token" -policy-name "agent-token-policy"

客户端配置

# client.hcl
datacenter = "Datacenter-0"
bind_addr = "192.168.5.170"
client_addr = "0.0.0.0"
ports {
  grpc_tls = 8502                     # 启用Connect服务网格前,务必在Agent配置中开启8502gRPC 端口
}
server = false
retry_join = ["192.168.5.225"]        # server端的地址
connect {
  enabled = true
}
telemetry {
  prometheus_retention_time = "72h"
  disable_hostname = true
}
acl = {
  enabled = true
  default_policy = "deny"
  down_policy = "extend-cache"
  tokens = {
    agent = "595feab1-3613-58F2-D1DA-cc96ea8c7c6e"
    default = "d0c24029-fcf5-e83d-7876-acc18ac247f0"
  }
}

2.基本命令

  1. 服务注册与发现

consul支持,配置文件注册(传统运维场景),HTTP API动态注册(应用自动程序)

# /etc/consul.d/gitlab.json
{
  "service": {
    "name": "gitlab",
    "address": "10.10.0.21",
    "port": 443,
    "token": "fcbe30bd-35e1-b21d-0632-cd0d51d4d98b"    "tags": ["v2", "canary"],
    "check": {
      "http": "https://gitlab.diyao.me/-/health",
      "interval": "10s",
      "timeout": "5s",
      "tls_skip_verify": false
    },
    "weights": {
      "passing": 9,     # 服务正常,权重9
      "warning": 1      # 过载情况,权重1
    }
  }
}

consul services register ./gitlab.json
consul reload

# 配置文件gitlab.conf
curl --request PUT --data ${gitlab.conf} http://127.0.0.1:8500/v1/agent/service/register
dig @127.0.0.1 -p 8600 gitlab.service.consul SRV  # 查询dns的srv记录

; <<>> DiG 9.18.30-0ubuntu0.20.04.2-Ubuntu <<>> @127.0.0.1 -p 8600 gitlab.service.consul SRV
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2205
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 3
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;gitlab.service.consul.         IN      SRV

;; ANSWER SECTION:
gitlab.service.consul.  0       IN      SRV     1 1 443 0a0a0015.addr.datacenter-0.consul.

;; ADDITIONAL SECTION:
0a0a0015.addr.datacenter-0.consul. 0 IN A       10.10.0.21
peter-ThinkPad-Edge-E530.node.datacenter-0.consul. 0 IN TXT "consul-network-segment="

;; Query time: 3 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1) (UDP)
;; WHEN: Mon Feb 09 10:10:00 CST 2026
;; MSG SIZE  rcvd: 185

curl -s http://127.0.0.1:8500/v1/health/service/gitlab?passing=true | jq .
[
  {
    "Node": {
      "ID": "3e4b7df7-397b-106d-c7b8-a768b15258f7",
      "Node": "peter-ThinkPad-Edge-E530",
      "Address": "192.168.5.170",
      "Datacenter": "datacenter-0",
      "TaggedAddresses": {
        "lan": "192.168.5.170",
        "wan": "192.168.5.170"
      },
      "Meta": {
        "consul-network-segment": ""
      },
      "CreateIndex": 3393,
      "ModifyIndex": 7033
    },
    "Service": {
      "ID": "gitlab",
      "Service": "gitlab",
      "Tags": [],
      "Address": "10.10.0.21",
      "Meta": null,
      "Port": 443,
      "Weights": {
        "Passing": 9,
        "Warning": 1
      },
      "EnableTagOverride": false,
      "ProxyDestination": "",
      "Proxy": {},
      "Connect": {},
      "CreateIndex": 21502,
      "ModifyIndex": 21874
    },
    "Checks": [
      {
        "Node": "peter-ThinkPad-Edge-E530",
        "CheckID": "serfHealth",
        "Name": "Serf Health Status",
        "Status": "passing",
        "Notes": "",
        "Output": "Agent alive and reachable",
        "ServiceID": "",
        "ServiceName": "",
        "ServiceTags": [],
        "Definition": {},
        "CreateIndex": 3396,
        "ModifyIndex": 19076
      },
      {
        "Node": "peter-ThinkPad-Edge-E530",
        "CheckID": "service:gitlab",
        "Name": "Service 'gitlab' check",
        "Status": "passing",
        "Notes": "",
        "Output": "HTTP GET https://gitlab.diyao.me/-/health: 200 OK Output: GitLab OK",
        "ServiceID": "gitlab",
        "ServiceName": "gitlab",
        "ServiceTags": [],
        "Definition": {},
        "CreateIndex": 21676,
        "ModifyIndex": 21877
      }
    ]
  }
]
  1. 动态配置中心

consul内置一个分布式的键值对Key/Value存储系统,非常适合存放数据库连接串、功能开关等

consul kv put config/gitlab/max_upload_size "100MB"
consul kv get config/gitlab/max_upload_size

consul-database

  • 应用无感知, consul提供的template守护进程,监视远端配置变化(KV存储),远程值变更,自动下载并重写本地config.conf,然后执行service reload重启进程
# gitlab.conf.ctmpl
upstream gitlab_servers {
    {{ range service "gitlab" }}
    server {{ .Address }}:{{ .Port }}; # 自动循环填入所有健康的 gitlab 地址
    {{ else }}
    server 127.0.0.1:65535;            # 如果没服务,防止报错的备用地址
    {{ end }}
}

server {
    listen 80;
    client_max_body_size {{ key "config/gitlab/max_upload_size" }};
    location / {
        proxy_pass http://gitlab_servers;
    }
}

# consult-template的运行配置
consul-template -config "/etc/consul-template/config.hcl"

consul {
  address = "127.0.0.1:8500"
}

template {
  source      = "/etc/consul-template/templates/gitlab.conf.ctmpl"
  destination = "/etc/nginx/conf.d/gitlab.conf"
  command     = "systemctl reload nginx"
}
  • 应用原生支持,应用启动时,直接通过网络请求Consul API
config = consul.get_kv("config/gitlab/database")
db.connect(config.url)
  1. 服务通信与路由

Consul不仅能告诉你服务在哪,还能通过标签Tags实现精细化的流量控制。例如,在注册GitLab服务时增加了v2标签,其他服务便可通过v2.gitlab.service.consul 精准访问该版本

利用预定义查询,编写一套路由剧本,可以在服务发现层直接完成智能故障切换与降级

{
  "Name": "gitlab-smart-router",
  "Service": {
    "Service": "gitlab",
    "Tags": ["v2"],
    "OnlyPassing": true,
    "Failover": {
      "NearestN": 0,             # 禁止跨数据中心寻址,如果设置成2表示,延时最小的2个数据中心中所有可用地址
      "Selectors": [
        { "Tag": "v1" },         # 优先返回v1标签服务
        { "Untagged": true }     # 兜底选择
      ]
    }
  }
}

curl -q http://127.0.0.1:8500/v1/query/gitlab-smart-router/execute | jq .
dig @127.0.0.1 -p 8600 gitlab-smart-router.query.consul                                  # 注意域名是query.consul

consul的强大之处在于它让流量调度变得可编程。通过服务注册时的Weights权重参数,可以轻松实现金丝雀发布。例如,为v2版本设置较低权重,即可实现小比例的风险试错与性能验证

进一步地,利用Tags标签+自定义路由功能,consul能化身为精准的流量控制器。通过给实例打上地域或身份标签如region-bj或user-vip,可以实现更高级的灰度发布,定向引导VIP用户或特定地域流量访问指定版本的服务,从而在业务层面实现精准控流

upstream gitlab_prod {
  {{ range service "gitlab" }}
  server {{ .Address }}:{{ .Port }};
  {{ end }}
}

upstream gitlab_gray {
  {{ range service "vip-only.gitlab" }}
  server {{ .Address }}:{{ .Port }};
  {{ end }}
}

# 通过http首部,X-User-Role字段,设置backend变量
map $http_x_user_role $backend {
    default gitlab_prod;      
    VIP     gitlab_gray; 
}

server {
    listen 80;
    location / {
        proxy_pass http://$backend;
    }
}

3.高级用法

connect是consul提供的服务网格能力扩展(也称作service mesh),用于实现服务之间的安全通信mTLS(双向认证),并在服务发现之上引入以sidecar代理为核心的服务级路由与流量控制机制。 前文尚未涉及的服务容错与稳定性治理、安全与访问控制等高级特性,均构建于connect所提供的服务网格能力之上。 通过统一接管服务间通信链路,consul得以在不侵入业务代码的前提下,实现对服务行为的集中治理。

下面以一个简单的echo服务为例,演示Consul Connect的基本使用方式及其在服务间通信中的作用。

# /etc/consul.d/echo.json
{
  "service": {
    "name": "echo",
    "port": 58888,
    "check": {
      "tcp": "127.0.0.1:58888",
      "interval": "10s",
      "timeout": "1s"
    },
    "connect": {
      "sidecar_service": {
      }
    }
  }
}

socat TCP-LISTEN:58888,reuseaddr,fork SYSTEM:'cat'
  • consul内置的L4层代理

    通过在服务端创建echo服务的sidecar,其对外监听一个由自动分配的动态端口(21000+),对内其连接echo服务指定的本地58888端口,并自动获取TLS证书双向认证,接管mTLS加密流量
    在客户端一侧启动一个sidecar服务,在本地伪造一个目标服务,声明当前代理的身份web应用的同时,指定上游服务与本地端口的映射关系echo:9000

    本地客户端(telnet localhost:9000) -> consule-client-sidecar(认证加密) -> consule-server-sidecar(认证解密) -> echo服务(58888端口)

# 服务端
consul connect proxy -sidecar-for echo  # 创建echo-sidecar-proxy服务

# 客户端
consul connect proxy -service web -upstream echo:9000  # web标明身份,告诉consul当前是web流量在访问echo服务
  • 基于envoy的应用层代理

    注意在使用envoy作为流量代理插件时,需要在配置文件中设置verify_incoming和verify_outgoing选项关闭

# 服务端
# 客户端

consul connect envoy -sidecar-for echo
consul connect proxy -service web -upstream echo:9000  # 客户侧继续使用内置代理插件 

# 客户端侧注册web服务,使用envoy作为外部代理工具
# web.hcl
service {
  name = "web"
  connect {
    sidecar_service {
      proxy {
        upstreams {
          destination_name = "echo"
          local_bind_port = 9000
        }
      }
    }
  }
}
consul services register web.hcl
consul connect envoy --sidecar-for web                
  1. 安全与访问控制

consul通过访问控制意图Intentions来限制服务间访问,实现零信任安全通信。服务身份验证,只有拥有合法服务身份的应用才能访问目标服务。 服务间通信,consul通过对服务调用方和服务提供方认证(mTLS),以及服务数据的全流量加密,保证数据安全

# 访问控制
consul intention create -allow web echo
consul intention create -deny "*" echo

# 命令行实际创建service-intentions类型的配置条目
# 现代化的管理方式是使用consul config write ./echo-intentions.hcl
# echo-intentions.hcl

{
    "kind": "service-intentions",
    "name": "echo",
    "sources": [
        {
            "name": "web",
            "action": "allow"
        }
    ]
}

# 查询
consul intention check web echo
  1. 容错与稳定性治理

consul本身提供的健康检查、服务发现、预编译查询,可作为容错决策基础。 同时consul原生支持的特性,consul-template会自动感知服务,在发现服务不可用时,自动更新nginx配置,在nginx配置文件中删除不可用节点,也可以视作服务的简单熔断(被动剔除)。以及前面提到的预编译查询语句,也可以达到基础的服务降级效果。

但一般情况下,大型应用场景,consul使用envoy或其他第三方代理服务提供的高级熔断、重试、限流、超时等功能,更好的达到主动式服务稳定性治理目的。

# proxy-defaults.hcl

# 定义全局代理配置默认值
# 由于echo服务使用的tcp协议,当前配置要求envoy将代理流量当做http协议解析
# envoy会构造http-400(bad request)响应
kind = "proxy-defaults"       
name = "global"

config {
  protocol = "http"  
  envoy_prometheus_bind_addr = "0.0.0.0:9102"         
}

accesslogs {
  enabled = true
}

consul config write ./proxy-defaults.hcl

# echo-service.hcl

# 定义特定服务在网格中的全局行为, 比如下面定义echo服务,定义服务协议为tcp
kind = "service-defaults"
name = "echo"
protocol = "tcp"

consul config write ./echo-service.hcl

# 定义web服务的相关限制,比如这里限制web服务到echo服务的最大连接数量2
kind = "service-defaults"
name = "web"
protocol = "tcp"
meta = {
  purpose = "test" 
}
upstreamconfig = {
  overrides = {
    name = "echo"
    limits = {
      maxconnections = 2
      maxpendingrequests = 2  # tcp协议无效
    }
  }
}

下面介绍一下使用consul的配置项进行灰度测试的样例,例子中我们希望80%的访问继续使用稳定版本v1,20%的版本使用开发版v2

创建服务

流量方向:

  • 客户端浏览器访问192.168.5.225:10000
  • 主机192.168.5.225(consul-server机器),consul-envoy-sidecar(client:10000)
  • 主机192.168.5.46(consul-client机器),consul-envoy-sidecar(端口内部协商)
  • hugo进程主机(192.168.5.46:1313)
# hugo.hcl
service = {
  name = "hugo"
  port = 1313
  token = "38123619-dc6f-7ce6-3857-43c829f40d61"
  check = {
    http = "http://127.0.0.1:1313"
    interval = "10s"
    timeout = "1s"
  }
  meta = { 
    version = "v1"
  }
  connect { sidecar_service {} }
}

consul services register hugo.hcl
consul connect envoy -bootstrap -admin-bind=127.0.0.1:9010 -sidecar-for hugo > bootstrap.json
envoy -c bootstrap.json

# 客户端侧注册hugo-proxy服务,使用envoy作为外部代理工具
# hugo-proxy.hcl
service {
  name = "hugo-proxy"
  connect {
    sidecar_service {
      proxy {
        upstreams {
          destination_name = "hugo"
          local_bind_port = 10000
          local_bind_address = "0.0.0.0"
        }
      }
    }
  }
}
consul connect envoy --sidecar-for hugo-proxy -admin-bind=127.0.0.1:9010
curl http://192.168.5.225:10000

服务分组

针对注册的hugo服务,默认将其划入v1组,服务元信息标明版本信息的,划入对应的组别

# hugo-service-resolver.hcl

kind = "service-resolver" 
name = "hugo" 
default_subset = "v1" 
Subsets = {
  v1 = {
    Filter = "Service.Meta.version == v1"   # service和meta必须大写
  }
  v2 = {
    Filter = "Service.Meta.version == v2"
  }
}

划分流量

发往hugo服务的流量,按照权重随机分配,v1组别处理80%,v2组别处理20%

# hugo-service-splitter.hcl

kind = "service-splitter"
name = "hugo"

splits = [
  {
    weight = 80
    service_subset = "v1"
  },
  {
    weight = 20
    service_subset = "v2"
  }
]

利用consul connect的高级功能,可以轻松的实现服务区分,比如下面的配置项,实现vip用户的优先处理 在请求体首部设置X-User-Role:vip或者请求参数中包含x-user-role=vip的请求会路由至服务的vip子集处理,默认使用hugo服务的稳定版本

# hugo-service-router.hcl

kind = "service-router"
name = "hugo"
routes = [
  {
    match {
      http {
        header = [
          {
            name  = "X-User-Role"
            exact = "vip"
          },
        ]
      }
    }
    destination {
      service       = "hugo"
      servicesubset = "vip"
    }
  },
  {
    match {
      http {
        queryparam = [
          {
            name  = "x-user-role"
            exact = "vip"
          },
        ]
      }
    }
    destination {
      service       = "hugo"
      servicesubset = "vip"
    }
  },
  {
	  destination {
		  service = "hugo"
      retrynnconnectfailure = true
      retryon = ["reset""5xx", "gateway-error"]
	  }
  }
]
  1. 服务可观测性

指标

  • 数据面,指标需要通过proxy-defaults的全局配置项,envoy_prometheus_bind_addr参数,发送至监控系统
  • 管理面,通过向consul进程发送user1信号,同时监控journalctl -u consul -f命令输出,可以观测consul agent运行的相关指标数据
Feb 12 05:43:04 resources consul[151659]: [2026-02-12 05:42:10 +0000 UTC][G] 'consul.runtime.total_gc_pause_ns': 220032864.000
Feb 12 05:43:04 resources consul[151659]: [2026-02-12 05:42:10 +0000 UTC][G] 'consul.session_ttl.active': 0.000
Feb 12 05:43:04 resources consul[151659]: [2026-02-12 05:42:10 +0000 UTC][G] 'consul.state.config_entries.datacenter-0.terminating-gateway': 0.000
Feb 12 05:43:04 resources consul[151659]: [2026-02-12 05:42:10 +0000 UTC][G] 'consul.state.config_entries.datacenter-0.exported-services': 0.000
Feb 12 05:43:04 resources consul[151659]: [2026-02-12 05:42:10 +0000 UTC][G] 'consul.state.config_entries.datacenter-0.service-intentions': 2.000
Feb 12 05:43:04 resources consul[151659]: [2026-02-12 05:42:10 +0000 UTC][G] 'consul.runtime.alloc_bytes': 47405864.000
# 省略
......

日志

  • journalctl -u consul -f命令输出consul的程序运行日志
  • 在proxy-defaults的全局配置项中配置accesslogs参数,记录envoy处理的每一次请求或连接,包括来源、目标、状态、延迟等信息(服务网格里的流量日志,类似apache中access.log)

链路追踪

consul的服务网格中,链路跟踪记录一次请求跨多个服务的调用路径,分析系统行为和依赖关系,帮助你分析延迟、错误和性能瓶颈。envoy作为sidecar代理,可以自动收集每个请求的trace数据,将这些trace发送到外部追踪系统,如Jaeger,Zipkin,Lightstep。

4.参考文献

  1. https://developer.hashicorp.com/consul/install#linux
  2. https://docs.gitlab.com/administration/monitoring/health_check/
  3. https://leezhenghui.github.io/microservices/2018/11/25/build-a-scalable-system-practice-on-service-mesh-consul-with-envoy.html