Registrator中文文档
快速入门
这个简短的教程帮你快速开始使用Registrator。完整参看,查看运行参考
概述
Registrator监控新建的Docker容器,并且检查判定这些容器提供的服务。从我们的目的出发,任何监听在某个端口的程序都是服务。Registrator发现在容器内发现的任务服务,都将被添加到一个服务注册端,比如Consul或etcd。
在这个教程中,我们将使用Registrator和Consul,运行一个Redis容器并自动添加到Consul。
准备
我们需要一个运行Docker的主机,可以是一个本地的boot2doker虚拟机和一个安装了docker client可以连接到虚拟机的shell。
我们在容器里运行一个Server模式的consul实例
$ docker run -d --name=consul --net=host gliderlabs/consul-server -bootstrap
Consul在产品模式下的不是这样运行的,我们这里使用这种方式完成教程。现在我们可以访问通过Docker主机的IP访问Consul的HTTP API。
$ curl $(boot2doker ip):8500/v1/catalog/services
{"consul":[]}
现在我们可以启动Registrator了。
运行Registrator
Registrator运行在每台主机上,我们这里只有一台主机,就运行一次就行。启动Registrator需要配置如何连接到注册机,即这里的Consul。
除了标志选项,唯一需要的参数就是注册机URI。注册机URI编码了注册机类型,如何连接等选项。
$ docker run -d \
--name=registrator
--net=host \
gliderlabs/registrator:latest \
consul://localhost:8500
先说一下Docker运行参数。首先,我们独立的运行容器和命名。我们也采用主机网络模式。这确保Registrator拥有实际主机的主机名和IP,也使Registrator更容易连接到Consul。我们也必须挂载Docker socket。
最后一行是Registrator运行参数,只有注册机URI。我们使用consul//localhost:8500,因为Registrator和Consul 运行在同一个网口。
$ docker logs registrator
我们应该可以看到Registrator运行起来并在“监听Docker事件"。Registrator正常运行了。
运行Redis
现在当你启动的容器如果提供任何服务,他们将被添加到Consul。我们现在运行标准镜像库的Redis:
$ docker run -d -P --name=redis redis
我们使用-P
发布所有端口,除了Registrator我们不经常这样使用。这样不仅发布了容器的所有端口,而且随机分配了一个主机端口。从Registrator和Consul提供服务发现的角度看,端口并不重要。尽管还有一些情况,你仍然想手动指定端口。
我们再来看看Consul的服务端:
$ curl $(boot2docker ip):8500/v1/catalog/services
{"consul":[],"redis":[]}
现在Consul已经有了一个redis服务。我们可以通过Consul的服端API查看更多诸如服务发布端口等信息:
$ curl $(boot2docker ip):8500/v1/catalog/service/redis
[{"Node":"boot2docker","Address":"10.0.2.15","ServiceID":"boot2docker:redis:6379","ServiceName":"redis","ServiceTags":null,"ServiceAddress":"","ServicePort":32768}]
如果我们移除redis容器,我们能够看到服务也从consul移除了。
$ docker rm -f redis
redis
$ curl $(boot2docker ip):8500/v1/catalog/service/redis
[]
就到这了。我知道单独来看这并没什么意义,但是当服务注册到Consul后你可以做很多事情。当然,这超出了Registrator的范围,它做的就是把容器中的服务放进Consul。
下一步
这还有很多方法配置Registrator,并且有很多方法运行容器,自定义服务。想了解这些,去看看运行参考和服务模型
运行参考
Registrator设计是在每个主机上运行一次。你可以在一个集群中运行单个Registrator,但是在每个主机上运行可以使你或得更好的扩展属性和更简单的配置。从一定程度的自动化来说,每个主机都运行比在某个地方运行一次更简单。
运行Registrator
docker run [docker options] gliderlabs/registrator[:tag] [options] <registry uri>
Registrator要求和推荐一些Docker选项,也有它自己的选项集,然后需要个注册机URI。下面是一个运行Registrator的经典方式:
$ docker run -d \
--name=registrator \
--net=host \
--volume=/var/run/docker.sock:/tmp/docker.sock \
gliderlabs/registrator:latest \
consul://localhost:8500
Docker选项
Option | Required | Description |
---|---|---|
--volume=/var/run/docker.sock:/tmp/docker.sock | yes | 允许Registrator访问Docker API |
--net=host | recommended | 帮助Registrator获取主机级的IP和主机名 |
与设置主机网络模式相比,另一个可选的方案是设置容器名字为宿主主机名(-h $HOSTNAME
),并且使用下面Registrator的-ip选项。
Registrator选项
Option | Since | Description |
---|---|---|
-cleanup | v7 | 清理悬挂服务 |
-deregister | v6 | 取消注册退出的服务 "always"或"on-success".缺省值:always |
-internal | 使用暴露端口代替发布端口 | |
-ip | v6 | 强制设置注册服务使用的IP地址 |
-resync | v6 | 服务再同步频率。缺省值:0,never |
-retry-attempts | v7 | 与注册机后台建立连接的最大重试次数 |
-retry-interval | v7 | 重试间隔(以毫秒为单位) |
-tags | v5 | 强制设置所有注册服务的都好分割的tags |
-ttl | 服务TTL。缺省值0,no expiry(注册机后台唯一支持) | |
-ttl-refresh | 服务TTL刷新频率(注册机后台唯一支持) | |
-useIdFromLabel | 使用存储在给定label的IP地址,这个label在容器中设置,用以注册Consul。 |
如果设置了-internal
选项,Registrator会注册docker内部IP和端口,而不是映射到主机的端口。
默认情况下,注册服务时,Registrator会尝试解析当前主机名来设置服务地址。如果你想强制指定服务地址为某个特定地址,你可以指定-ip
参数。
对于支持TTL超期的注册机后端,Registrator支持设置和刷新服务的TTLs,使用-ttl
和-ttl-refresh
。
如果你想无限制的重连尝试,可以使用-retry-attempts -1
。
-resync
选项控制Registrator查询Docker中所有容器并且注册所有服务的频率。这个选项允许Registrator和服务注册机重新找到掉出同步的服务。要谨慎使用这个选项,它会通知已经注册到你服务上的所有观察者,可能会迅速淹没你的系统(比如consul-template就大量使用监测)。
Consul ACL令牌
如果consul配置要求提供ACL令牌,Registrator需要知道,或者你会在consul的docker容器中看到警告。
[WARN] consul.catalog: Register of service 'redis' on 'hostname' denied due to ACLs
ACL令牌通过一个环境变量传入docker:CONSUL_HTTP_TOKEN
。
$ docker run -d \
--name=registrator \
--net=host \
--volume=/var/run/docker.sock:/tmp/docker.sock \
-e CONSUL_HTTP_TOKEN=<your acl token> \
gliderlabs/registrator:latest \
consul://localhost:8500
注册URI
<backend>://<address>[/<path>]
注册机后台使用URI来定义。架构是支持的注册机名字。地址是用来连接注册机的一个主机或者主机和端口。一些注册机支持一个定义的路径,比如作为前缀在服务定义中供基于注册机的key-value使用。
所以支持的后端参考,查看注册机后端
服务模型
Registrator主要关注的那些要被添加到服务发现注册机的服务。对我们而言,所有监听在某个端口的程序都是服务。如果一个容器监听了多个端口,它就又多个服务。
服务,包括来自容器的信息和用户在容器上定义的元数据被创建成一个服务对象。这个服务对象随后被传递给注册机后端,尝试放置到一个特定的注册项。
type Service struc {
ID string //unique service instance ID
Name string //service name
IP string //IP address service is located at
Port int //Port service is listening on
Tags []string //extra tags to classify service
Attrs map[string]string //extra attribute metadata
}
容器覆盖
Name,Tags,Attrs,ID
字段可以被用户自定义的容器元数据覆盖。你可以使用前缀SERVICE_
或者SERVICE_x_
,其中x
是内部暴露端口的环境变量或者标签设置这些值。例如,SERVICE_NAME=customerdb
和SERVICE_80_NAME=api
你在这些环境变量中使用的端口指的是在这个端口上的特定服务。名字中没有使用端口的元数据变量用作所有服务的缺省值,或者便捷的指向暴露的单个服务。
Attrs
字段集合所有使用其他字段名的关键字,例如,SERVICE_REGION=us-east
。
因为元数据被存储为环境变量或者标签,因此容器作者可以在Dockerfile中包含他们自己的元数据定义。操作者仍然能够覆盖这些作者定义的缺省值。
发现服务
缺省情况下,你可以期望Registrator从那些已经显式发布端口(例如使用'-p'或者-P
)的容器中获取服务。这对于那些以主机网络模式运行的容器也是正确的,因此你必须发布端口,即使它没什么做任何网络智慧的事情。
$ docker run --net=host -p 8080:8080 -p 8443:8443 ...
如果使用-internal
选项运行,相反它将寻找暴露的端口。这些可以在Dockerfile中隐式设置或者使用docker run --expose=8080...
显式设定。
你也可以通过设置一个叫SERVICE_IGNORE
的标签或者环境变量告诉Registrator忽略一个容器。
如果你需要在某些容器中忽略几个服务,你可以使用SERVICE_<port>_IGNORE=true
。
服务名称
服务名是你在服务发现查找中使用的。缺省情况下,服务名按下面的格式确定:
<base(container-image)>[-<exposed-port> if >1 ports]
使用容器镜像的基础,如果镜像是gliderlabs/footbar
,服务名就是footbar
。如果镜像是redis
,服务名就是简单的redis
。
而且如果一个容器有多个暴露端口,它将各自追加内部暴露端口以区别。例如,一个镜像nginx
有两个暴露端口,80和443,将产生两个服务,分别命名nginx-80
和nginx-443
。
你可以使用标签或者环境变量,SERVICE_NAME
或者'SERVICE_x_NAME',其中x
是内部暴露端口,覆盖这些缺省名字。注意如果一个容器有多个暴露端口,设置SERVICE_NAME
会导致多个服务命名为SERVICE_NAME-<exposed port>
。
IP和端口
IP和端口组成了服务名解析的地址。这有许多方法Registrator能够依赖你的设置判断IP地址和端口。缺省情况下,端口就是发布的公共端口,IP将是你的主机IP。
由于自动判定正确的IP是困难的,推荐使用-ip
选项显式告诉Registrator使用什么IP。
如果你使用-internal
选项,Regisrator会使用暴露端口和docker分配的内部容器IP。
Tags和Attributes
Tags和attributes是服务额外的元数据字段。并不是所有的后端都支持他们。事实上,目前consul支持tags,并且最近的v1.0.7以KV元数据的形式添加了对attributes的支持,但是没有其他的后端支持attributes。
Attributes也能被后端用来注册特定的特性,不仅仅是元数据。例如,consul使用他们指定specifying HTTP health checks
。
Unique ID
ID是服务示例在集群内的唯一标识。大部分情况下,它是一个实现细节,通常用户使用服务名而不是ID。Registrator使用一个人机友好的字符串,基于下面的格式编码了有用的信息在ID中:
<hostname>:<container-name>:<exposed-port>[:udp if udp]
ID包括了主机名,帮助你识别服务运行的主机。这也是Registrator运行在主机网络模式下或者设置Registrator的主机名为寄宿主机的主机名重要的原因。否则它将是Registrator容器的ID,那没有什么用处。
这个服务的容器名称也包含进来了。它使用容器名称代替容器ID,因为它更人性化,并且用户可配置。
为了识别出容器中这个服务,它使用内部暴露端口。这代表这个服务在容器内在这个端口上监听。我们使用这个是因为它比公共的发布端口更好的表达了这个服务。一个公共的端口可能是一个任意的54292,然而暴露的端口可能是80,表示它是一个HTTP服务。
最后,如果服务定义为UDP,这会被包括到ID中与监听在相同端口的TCP服务区别开。
尽管这可以使用容器的SERVICE_ID
或者SERVICE_x_ID
覆盖,但是不推荐这样做。
示例
缺省的单个服务
$ docker run -d --name redis.0 -p 10000:6379 progrium/redis
Service
结果是:
{
"ID": "hostname:redis.0:6379",
"Name": "redis",
"Port": 10000,
"IP": "192.168.1.102",
"Tags": [],
"Attrs": {}
}
指定元数据的单个服务
$ docker run -d --name redis.0 -p 10000:6379 \
-e "SERVICE_NAME=db" \
-e "SERVICE_TAGS=master,backups" \
-e "SERVICE_REGION=us2" progrium/redis
Service
结果是:
{
"ID": "hostname:redis.0:6379",
"Name": "db",
"Port": 10000,
"IP": "192.168.1.102",
"Tags": ["master", "backups"],
"Attrs": {"region": "us2"}
}
记住并不是所有的Service
对象都会被注册后端使用。例如,目前不支持注册任意attributes。这个字段留作将来使用。
逗号可能可以通过反斜杠转义,像下面的例子:
$ docker run -d --name redis.0 -p 10000:6379 \
-e "SERVICE_NAME=db" \
-e "SERVICE_TAGS=/(;\\,:-_)/" \
-e "SERVICE_REGION=us2" progrium/redis
缺省的多个服务
$ docker run -d --name nginx.0 -p 4443:443 -p 8000:80 progrium/nginx
两个Service
对象的结果:
[
{
"ID": "hostname:nginx.0:443",
"Name": "nginx-443",
"Port": 4443,
"IP": "192.168.1.102",
"Tags": [],
"Attrs": {},
},
{
"ID": "hostname:nginx.0:80",
"Name": "nginx-80",
"Port": 8000,
"IP": "192.168.1.102",
"Tags": [],
"Attrs": {}
}
]
指定元数据的多个服务
$ docker run -d --name nginx.0 -p 4443:443 -p 8000:80 \
-e "SERVICE_443_NAME=https" \
-e "SERVICE_443_ID=https.12345" \
-e "SERVICE_443_SNI=enabled" \
-e "SERVICE_80_NAME=http" \
-e "SERVICE_TAGS=www" progrium/nginx
两个Service
对象的结果:
[
{
"ID": "https.12345",
"Name": "https",
"Port": 4443,
"IP": "192.168.1.102",
"Tags": ["www"],
"Attrs": {"sni": "enabled"},
},
{
"ID": "hostname:nginx.0:80",
"Name": "http",
"Port": 8000,
"IP": "192.168.1.102",
"Tags": ["www"],
"Attrs": {}
}
]
使用labels定义元数据
$ docker run -d --name redis.0 -p 10000:6379 \
-l "SERVICE_NAME=db" \
-l "SERVICE_TAGS=master,backups" \
-l "SERVICE_REGION=us2" dockerfile/redis
Service
结果:
{
"ID": "hostname:redis.0:6379",
"Name": "db",
"Port": 10000,
"IP": "192.168.1.102",
"Tags": ["master", "backups"],
"Attrs": {"region": "us2"}
}
注册后端
Registrator支持很多后端注册机。为了发挥Registrator的用途,你需要运行其中一个。下面是被支持后端使用的注册URI和它们的特性文档。
Consul
consul://<address>:<port>
consul-unix://<filepath>
consul-tls://<address>:<port>
Consul是推荐的注册机,因为它专门为服务发现提供了健康检查服务。
如果没有指定地址和端口,默认将使用127.0.0.1:8500。
consul支持tags,但不是任意服务attributes。
当使用consul-tls
架构,Registrator通过TLS与Consul通信。你必须设置下面的环境变量:*CONSUL_CAERT
:CA 文件位置, *CONSUL_TLSKEY
:Key位置
了解更多关于Consul检查参数信息,查看API documentation。
Consul HTTP Check
这个特性只能在Consul 0.5及更新版本中使用。容器通过环境变量或者标签指定的这些额外的元数据用来在服务中注册一个HTTP健康检查。
SERVICE_80_CHECK_HTTP=/health/endpoint/path
SERVICE_80_CHECK_INTERVAL=15s
SERVICE_80_CHECK_TIMEOUT=1s # optional, Consul default used otherwise
它适用于任何端口的服务,不仅仅是80端口。如果它是唯一的服务,你也可以使用
SERVICE_CHECK_HTTP
Consul HTTPS Check
这个特性只能在Consul 0.5及更新版本中使用。容器通过环境变量或者标签指定的这些额外的元数据用来在服务中注册一个HTTPS健康检查。
SERVICE_443_CHECK_HTTPS=/health/endpoint/path
SERVICE_443_CHECK_INTERVAL=15s
SERVICE_443_CHECK_TIMEOUT=1s # optional, Consul default used otherwise
Consul TCP Check
这个特性只能在Consul 0.6及更新版本中使用。容器通过环境变量或者标签指定的这些额外的元数据用来在服务中注册一个TCP健康检查。
SERVICE_443_CHECK_TCP=true
SERVICE_443_CHECK_INTERVAL=15s
SERVICE_443_CHECK_TIMEOUT=3s # optional, Consul default used otherwise
Consul Script Check
这个特性很难用,因为它让你指定一个脚本检查从Consul运行。如果在容器中运行Consul,你就被限制只能在那个容器中运行。例如,curl必须按照以使用这个特性:
SERVICE_CHECK_SCRIPT=curl --silent --fail example.com
任务no-TTL检查的缺省间隔是10秒,但是你可以使用_CHECK_INTERVAL
设置。检查命令可以使用$SERVICE_IP
和$SERVICE_PORT
占位符插值:
SERVICE_CHECK_SCRIPT=nc $SERVICE_IP $SERVICE_PORT | grep OK
Consul TTL Check
你也可以向consul注册一个TTL Check。记住这意味着Consul将期待一个常规的心跳ping到他的API,用以保持服务标志健康。
SERVICE_CHECK_TTL=30s
Consul Initial Health Check Status
缺省的当一个服务在Consul注册时,状态被设置为"critical"。你可以指定初始的健康检查状态:
SERVICE_CHECK_INITIAL_STATUS=passing
Consul Critical Service Deregistration
Consul可能撤销注册一个服务,如果检查在critical
状态超过设定的时间值。如果启用,这应该比可期待的恢复故障更长。
SERVICE_CHECK_DEREGISTER_AFTER=10m
Consul KV
consulkv://<address>:<port>/<prefix>
consulkv-unix://<filepath>:/<prefix>
这是一个分离后端,使用Consul的key-value存储代替它的本地服务目录。这种表现更像etcd,因为它有相似的语义,但是目前不支持TTLs。
如果没有指定地址和端口,默认是127.0.0.1:8500
。
使用Registry URI前缀,服务定义存储为下面的格式:
<prefix>/<service-name>/<service-id> = <ip>:<port>
Etcd
etcd://<address>:<port>/<prefix>
Etcd工作方式与Consul KV相似,而且支持服务TTLs。它目前也不支持服务attributes和tags。
如果没有指定地址和端口,它将使用默认值127.0.0.1:2379
。
使用Registry URI作为前缀,服务定义被储存为下面的格式:
<prefix>/<service-name>/<service-id> = <ip>:<port>
SkyDNS 2
skydns2://<address>:<port>/<domain>
SkyDNS2 使用etcd,因此这个后端写服务定义的格式兼容SkyDNS2。path不能被省略,必须是一个对SkyDNS有效的DNS域名。
如果没有指定地址和端口,它将是默认值:127.0.0.1:2379
。
使用Registry URI和域名cluster.local
,服务定义存储为下面的格式:
/skydns/local/cluster/<service-name>/<service-id> = {"host":"<ip>","port":<port>}
SkyDNS要求服务ID是一个有效的DNS主机名,因此这个后端要求容器用一个有效的DNS名字覆盖服务ID。例如:
$ docker run -d --name redis-1 -e SERVICE_ID=redis-1 -p 6379:6379 redis
Zookeeper Store
Zookeeper后端让你发布临时的znodes到zookeeper。这个模式在指定zookeeper路径后可以使用。zookeeper后端支持发布一个json格式的znode body,完成定义服务attributes/tags,也包括服务名和容器id。URI示例:
$ registrator zookeeper://zookeeper.host/basepath
$ registrator zookeeper://192.168.1.100:9999/basepath
在zookeeper URI指定的base path中,registrator将为服务创建下面的包括一个JSON入口路径树:
<service-name>/<service-port> = <JSON>
JSON将包括发布容器服务的所有信息。例如下面的容器启动:
docker run -i -p 80 -e 'SERVICE_80_NAME=www' -t ubuntu:14.04 /bin/bash
将产生的zookeeper path和JSON znode body:
/basepath/www/80 = {"Name":"www","IP":"192.168.1.123","PublicPort":49153,"PrivatePort":80,"ContainerID":"9124853ff0d1","Tags":[],"Attrs":{}}
Registrator中文文档的更多相关文章
- Phoenix综述(史上最全Phoenix中文文档)
个人主页:http://www.linbingdong.com 简书地址:http://www.jianshu.com/users/6cb45a00b49c/latest_articles 网上关于P ...
- Chart.js中文文档-雷达图
雷达图或蛛网图(Radar chart) 简介 A radar chart is a way of showing multiple data points and the variation bet ...
- Knockout中文开发指南(完整版API中文文档) 目录索引
a, .tree li > span { padding: 4pt; border-radius: 4px; } .tree li a { color:#46cfb0; text-decorat ...
- ReactNative官方中文文档0.21
整理了一份ReactNative0.21中文文档,提供给需要的reactnative爱好者.ReactNative0.21中文文档.chm 百度盘下载:ReactNative0.21中文文档 来源: ...
- java中文文档官方下载
一直在寻找它,今天无意之间终于发现它了! http://download.oracle.com/technetwork/java/javase/6/docs/zh/api/overview-summa ...
- Spring中文文档
前一段时间翻译了Jetty的一部分文档,感觉对阅读英文没有大的提高(*^-^*),毕竟Jetty的受众面还是比较小的,而且翻译过程中发现Jetty的文档写的不是很好,所以呢翻译的兴趣慢慢就不大了,只能 ...
- jQuery 3.1 API中文文档
jQuery 3.1 API中文文档 一.核心 1.1 核心函数 jQuery([selector,[context]]) 接收一个包含 CSS 选择器的字符串,然后用这个字符串去匹配一组元素. jQ ...
- jQuery EasyUI API 中文文档 - ComboGrid 组合表格
jQuery EasyUI API 中文文档 - ComboGrid 组合表格,需要的朋友可以参考下. 扩展自 $.fn.combo.defaults 和 $.fn.datagrid.defaults ...
- jQuery EasyUI API 中文文档 - ValidateBox验证框
jQuery EasyUI API 中文文档 - ValidateBox验证框,使用jQuery EasyUI的朋友可以参考下. 用 $.fn.validatebox.defaults 重写了 d ...
随机推荐
- MSSQL事务隔离级别详解(SET TRANSACTION ISOLATION LEVEL)
控制到 Transact-SQL 的连接发出的 SQL Server 语句的锁定行为和行版本控制行为. TRANSACT-SQL 语法约定 语法 -- Syntax for SQL Server ...
- [Swift]LeetCode118. 杨辉三角 | Pascal's Triangle
Given a non-negative integer numRows, generate the first numRows of Pascal's triangle. In Pascal's t ...
- [Swift]LeetCode417. 太平洋大西洋水流问题 | Pacific Atlantic Water Flow
Given an m x n matrix of non-negative integers representing the height of each unit cell in a contin ...
- [Swift]LeetCode985. 查询后的偶数和 | Sum of Even Numbers After Queries
We have an array A of integers, and an array queries of queries. For the i-th query val = queries[i] ...
- CentOS随笔——Service与防火墙关闭
Service后台服务管理 基本语法 service 服务名 start 开启服务 service 服务名 stop 关闭服务 service 服务名 restart 重启服务 service 服务名 ...
- JavaScript03-基本概念一
js包括:语法.语句.类型.关键字.保留字.操作符和对象. 语法.关键字.保留字 1.js中一切区分大小写,包括变量.函数名.操作符: 2.标识符规则,第一个字符必须是字母.下划线_.或者美元符号,其 ...
- IntelliJ IDEA 自定义方法注解模板
最近没啥事开始正式用Eclipse 转入 idea工具阵营,毕竟有70%的开发者在使用idea开发,所以它的魅力可想而知.刚上手大概有一天,就知道它为啥取名为 intelli(智能化)了,确实很智能, ...
- Asp.Net Core中利用Seq组件展示结构化日志功能
在一次.Net Core小项目的开发中,掌握的不够深入,对日志记录并没有好好利用,以至于一出现异常问题,都得跑动服务器上查看,那时一度怀疑自己肯定没学好,不然这一块日志不可能需要自己扒服务器日志来查看 ...
- nginx部署~dotnetCore+mvc网站502
这个不是nginx的问题,也不是dotnet core的问题,也不是mvc的问题,更不是防火墙的问题! 原因在于这个SeLinux 把它关了就可以了 setsebool -P httpd_can_ne ...
- EF架构~mysql中时间戳字段被认为是主键自增
回到目录 如果在mysql中添加了自增字段,用来维护行的版本,那么在EF中会有一个问题,会把它当成是数据表主键,当你的真正主键是自曾时,就会把默认值0拼到生成的SQL语句里,导致你的insert出错, ...