-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 209 KB
/
content.json
1
{"meta":{"title":"sunshanpeng","subtitle":null,"description":"java devops kubernetes","author":"sunshanpeng","url":"http://sunshanpeng.com","root":"/"},"pages":[{"title":"","date":"2019-08-16T17:44:23.000Z","updated":"2019-08-16T18:15:30.107Z","comments":true,"path":"about/index.html","permalink":"http://sunshanpeng.com/about/index.html","excerpt":"","text":""},{"title":"All tags","date":"2018-12-22T04:39:04.000Z","updated":"2019-08-16T18:10:13.719Z","comments":true,"path":"tags/index.html","permalink":"http://sunshanpeng.com/tags/index.html","excerpt":"","text":""},{"title":"","date":"2019-08-16T17:42:27.000Z","updated":"2019-08-16T18:10:55.480Z","comments":true,"path":"categories/index.html","permalink":"http://sunshanpeng.com/categories/index.html","excerpt":"","text":""}],"posts":[{"title":"istio官方示例bookinfo","slug":"istio官方示例bookinfo","date":"2020-02-27T13:26:37.000Z","updated":"2020-03-01T13:39:27.662Z","comments":true,"path":"2020/02/27/istio官方示例bookinfo/","link":"","permalink":"http://sunshanpeng.com/2020/02/27/istio官方示例bookinfo/","excerpt":"","text":"istio版本:1.4.5 部署bookinfo部署资源1kubectl apply -f <(istioctl kube-inject -f samples/bookinfo/platform/kube/bookinfo.yaml) 确认资源创建1234567$ kubectl get servicesNAME CLUSTER-IP EXTERNAL-IP PORT(S) AGEdetails 10.0.0.31 <none> 9080/TCP 6mkubernetes 10.0.0.1 <none> 443/TCP 7dproductpage 10.0.0.120 <none> 9080/TCP 6mratings 10.0.0.15 <none> 9080/TCP 6mreviews 10.0.0.170 <none> 9080/TCP 6m 镜像加载很慢 12345678$ kubectl get podsNAME READY STATUS RESTARTS AGEdetails-v1-1520924117-48z17 2/2 Running 0 6mproductpage-v1-560495357-jk1lz 2/2 Running 0 6mratings-v1-734492171-rnr5l 2/2 Running 0 6mreviews-v1-874083890-f0qf0 2/2 Running 0 6mreviews-v2-1343845940-b34q5 2/2 Running 0 6mreviews-v3-1813607990-8ch52 2/2 Running 0 6m 访问productpage12$ kubectl exec -it $(kubectl get pod -l app=ratings -o jsonpath='{.items[0].metadata.name}') -c ratings -- curl productpage:9080/productpage | grep -o \"<title>.*</title>\"<title>Simple Bookstore App</title> 部署bookinfo-gateway部署资源1$ kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml 确认资源创建123$ kubectl get gatewayNAME AGEbookinfo-gateway 32s 获取IP端口1234567#IP$ export INGRESS_HOST=$(kubectl get po -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].status.hostIP}')#PORT$ export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name==\"http2\")].nodePort}')$ export SECURE_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name==\"https\")].nodePort}')# IP:PORT$ export GATEWAY_URL=$INGRESS_HOST:$INGRESS_PORT 访问productpage服务器上访问12$ curl -s http://${GATEWAY_URL}/productpage | grep -o \"<title>.*</title>\"<title>Simple Bookstore App</title> 浏览器上访问获取地址 12$ echo $GATEWAY_UR10.22.19.14:24804 打开页面 1http://10.22.19.14:24804/productpage 刷新页面,发现评价有时星星是红色的,有时是黑色的,有时没有星星。说明有三个版本在负载均衡。 只访问V1版本创建destination rule1234567$ kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml$ kubectl get destinationrules.networking.istio.io NAME HOST AGEdetails details 162mproductpage productpage 162mratings ratings 162mreviews reviews 162m 创建V1版本的virtual service1$ kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml 此时刷新页面,显示的内容中不包含带星的评价信息。 指定用户访问V2版本配置V2版本的reviews virtual service1$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-test-v2.yaml 登录jason/jason后,显示的内容变成了黑色星星的评价信息 基于权重的流量比例配置V1和V2权重1$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-80-20.yaml 80%流量到V1版本,20%流量到V2版本 注入延迟故障配置ratings virtual service1$ kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-delay.yaml 普通用户访问正常,jason用户加载评论失败 注入500故障配置ratings virtual service1$ kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-abort.yaml 普通用户访问正常,jason用户提示Ratings service is currently unavailable 卸载1$ samples/bookinfo/platform/kube/cleanup.sh 参考https://istio.io/docs/examples/bookinfo/","categories":[{"name":"istio","slug":"istio","permalink":"http://sunshanpeng.com/categories/istio/"}],"tags":[{"name":"istio","slug":"istio","permalink":"http://sunshanpeng.com/tags/istio/"}]},{"title":"在K8S集群中安装istio","slug":"在K8S集群中安装istio","date":"2020-02-27T13:26:37.000Z","updated":"2020-03-01T13:36:18.274Z","comments":true,"path":"2020/02/27/在K8S集群中安装istio/","link":"","permalink":"http://sunshanpeng.com/2020/02/27/在K8S集群中安装istio/","excerpt":"","text":"istio版本:1.4.5 k8s版本:1.14.8 docker版本:18.6.1 内核版本:4.19.12-1.el7.elrepo.x86_64 安装下载安装包方式一:在线下载1curl -L https://istio.io/downloadIstio | sh - 方式二:离线下载https://github.com/istio/istio/releases 下载安装包后上传到服务器并解压 进入安装包并设置环境变量123456789101112131415$ cd istio-1.4.5# istioctl在/bin目录下# K8S安装yaml文件在/install/kubernetes目录下# 示例程序在/samples下$ lltotal 28drwxr-x--- 2 root root 21 Feb 14 01:23 bindrwxr-xr-x 6 root root 74 Feb 14 01:23 install-rw-r--r-- 1 root root 11348 Feb 14 01:23 LICENSE-rw-r----- 1 root root 657 Feb 14 01:23 manifest.yaml-rw-r--r-- 1 root root 6080 Feb 14 01:23 README.mddrwxr-xr-x 19 root root 4096 Feb 14 01:23 samplesdrwxr-x--- 3 root root 132 Feb 14 01:23 tools$ export PATH=$PWD/bin:$PATH 安装istio安装demo示例(不适用生产环境)123456789101112131415161718192021222324252627282930$ istioctl manifest apply --set profile=demo- Applying manifest for component Base...✔ Finished applying manifest for component Base.- Applying manifest for component Tracing...- Applying manifest for component Policy...- Applying manifest for component Galley...- Applying manifest for component Citadel...- Applying manifest for component EgressGateway...- Applying manifest for component Pilot...- Applying manifest for component Telemetry...- Applying manifest for component Injector...- Applying manifest for component IngressGateway...- Applying manifest for component Prometheus...- Applying manifest for component Kiali...- Applying manifest for component Grafana...✔ Finished applying manifest for component Injector.✔ Finished applying manifest for component Galley.✔ Finished applying manifest for component Citadel.✔ Finished applying manifest for component Prometheus.✔ Finished applying manifest for component IngressGateway.✔ Finished applying manifest for component Kiali.✔ Finished applying manifest for component Pilot.✔ Finished applying manifest for component Policy.✔ Finished applying manifest for component Tracing.✔ Finished applying manifest for component EgressGateway.✔ Finished applying manifest for component Grafana.✔ Finished applying manifest for component Telemetry.✔ Installation complete 查看安装情况123456789101112131415161718$ kubectl get svc -n istio-systemNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEgrafana ClusterIP 172.21.92.238 <none> 3000/TCP 117sistio-citadel ClusterIP 172.21.219.100 <none> 8060/TCP,15014/TCP 119sistio-egressgateway ClusterIP 172.21.132.59 <none> 80/TCP,443/TCP,15443/TCP 117sistio-galley ClusterIP 172.21.181.69 <none> 443/TCP,15014/TCP,9901/TCP,15019/TCP 119sistio-ingressgateway LoadBalancer 172.21.147.170 <pending> 15020:34070/TCP,80:24804/TCP,443:38932/TCP,15029:23713/TCP,15030:30635/TCP,15031:34451/TCP,15032:30379/TCP,15443:39703/TCP 118sistio-pilot ClusterIP 172.21.121.250 <none> 15010/TCP,15011/TCP,8080/TCP,15014/TCP 118sistio-policy ClusterIP 172.21.178.10 <none> 9091/TCP,15004/TCP,15014/TCP 117sistio-sidecar-injector ClusterIP 172.21.223.149 <none> 443/TCP 119sistio-telemetry ClusterIP 172.21.49.247 <none> 9091/TCP,15004/TCP,15014/TCP,42422/TCP 116sjaeger-agent ClusterIP None <none> 5775/UDP,6831/UDP,6832/UDP 2mjaeger-collector ClusterIP 172.21.159.105 <none> 14267/TCP,14268/TCP,14250/TCP 119sjaeger-query ClusterIP 172.21.203.16 <none> 16686/TCP 118skiali ClusterIP 172.21.207.47 <none> 20001/TCP 118sprometheus ClusterIP 172.21.71.218 <none> 9090/TCP 118stracing ClusterIP 172.21.49.79 <none> 80/TCP 118szipkin ClusterIP 172.21.13.237 <none> 9411/TCP 117s 镜像下载速度很慢,需要加速 使用指定仓库12> istioctl manifest apply --set hub=harbor.sunshanpeng.com/istio> 12345678910111213$ kubectl get pods -n istio-systemgrafana-5f798469fd-hm5vs 1/1 Running 0 142mistio-citadel-58bb67f9b8-bkwkm 1/1 Running 0 142mistio-egressgateway-6fd57475b5-wdqck 1/1 Running 0 142mistio-galley-7d4b9874c8-jk495 1/1 Running 0 142mistio-ingressgateway-7d65bf7fdf-jbqst 1/1 Running 0 142mistio-pilot-65f8557545-kbs75 1/1 Running 0 142mistio-policy-6c6449c56f-btzpd 1/1 Running 0 142mistio-sidecar-injector-774969d686-lgnjb 1/1 Running 0 142mistio-telemetry-585cc965f7-vjwwn 1/1 Running 8 142mistio-tracing-cd67ddf8-wpdqr 1/1 Running 0 142mkiali-7964898d8c-4z88n 1/1 Running 0 142mprometheus-586d4445c7-wv9qv 1/1 Running 0 142m 注入Sidecar方式一:指定namespace自动注入1$ kubectl label namespace <namespace> istio-injection=enabled 方式二:指定pod注入1234#三种方式$ istioctl kube-inject -f resource.yaml | kubectl apply -f -$ kubectl apply -f <(istioctl kube-inject -f <resource.yaml>)$ kubectl get deployment deploy -o yaml | istioctl kube-inject -f - | kubectl apply -f - istioctl kube-inject 命令用于在创建部署之前修改yaml 文件,把 Envoy 注入到 Kubernetes 资源。 卸载1$ istioctl manifest generate --set profile=demo | kubectl delete -f - 参考https://istio.io/docs/setup/getting-started/","categories":[{"name":"istio","slug":"istio","permalink":"http://sunshanpeng.com/categories/istio/"}],"tags":[{"name":"istio","slug":"istio","permalink":"http://sunshanpeng.com/tags/istio/"}]},{"title":"设置istioctl自动补全","slug":"设置istioctl自动补全","date":"2020-02-27T13:26:37.000Z","updated":"2020-03-01T13:33:21.141Z","comments":true,"path":"2020/02/27/设置istioctl自动补全/","link":"","permalink":"http://sunshanpeng.com/2020/02/27/设置istioctl自动补全/","excerpt":"","text":"istio版本:1.4.5 安装bash-completionubuntu1$ apt-get install bash-completion centos1$ yum install bash-completion 配置~/.bash_profile1$ echo '[[ -r \"/usr/local/etc/profile.d/bash_completion.sh\" ]] && . \"/usr/local/etc/profile.d/bash_completion.sh\"' >> ~/.bash_profile 启用istioctl.bash1234$ cp istio-1.4.5/tools/istioctl.bash ~/istioctl.bash$ source ~/istioctl.bash$ echo 'source ~/istioctl.bash' >> .bashrc$ source .bashrc 验证12$ istioctl proxy-<TAB>proxy-config proxy-status 参考https://istio.io/docs/ops/diagnostic-tools/istioctl/#enabling-auto-completion","categories":[{"name":"istio","slug":"istio","permalink":"http://sunshanpeng.com/categories/istio/"}],"tags":[{"name":"istio","slug":"istio","permalink":"http://sunshanpeng.com/tags/istio/"}]},{"title":"将ECS上的应用集成到istio","slug":"将ECS上的应用集成到istio","date":"2020-02-16T12:26:37.000Z","updated":"2020-03-01T13:50:01.653Z","comments":true,"path":"2020/02/16/将ECS上的应用集成到istio/","link":"","permalink":"http://sunshanpeng.com/2020/02/16/将ECS上的应用集成到istio/","excerpt":"","text":"网格拓展Mesh ExpansionMesh Expansion是指部署在Kubernetes之中的Istio服务网格提供的一种将虚拟机或物理裸机集成进入到服务网格的方法。 Mesh Expansion对于用户从遗留系统往云上迁移过程中有着非常重要的作用,在微服务体系结构中,无法要求所有的工作负载都在Kubernetes中运行,用户的一些应用程序可能在Kubernetes中运维,而另外一些可能在虚拟机或物理裸机中运行。 通过一套Istio控制面板就可以管理跨Kubernetes与虚拟机或物理裸机的若干服务。这样既能保证原有业务的正常运转,又能实现Kubernetes与虚拟机上的应用服务混合编排的能力。 如图所示,details组件和数据库运行在Kubernetes之外的ECS上。 把一个VM等同于一个Pod,部署业务服务和Sidecar,然后交给istio编排。 参考https://help.aliyun.com/document_detail/90707.html https://istio.io/docs/examples/virtual-machines/single-network/ https://istio.io/docs/examples/virtual-machines/multi-network/","categories":[{"name":"istio","slug":"istio","permalink":"http://sunshanpeng.com/categories/istio/"}],"tags":[{"name":"istio","slug":"istio","permalink":"http://sunshanpeng.com/tags/istio/"}]},{"title":"MySQL开发规范","slug":"MySQL开发规范","date":"2019-12-16T12:26:37.000Z","updated":"2020-03-01T13:48:47.393Z","comments":true,"path":"2019/12/16/MySQL开发规范/","link":"","permalink":"http://sunshanpeng.com/2019/12/16/MySQL开发规范/","excerpt":"","text":"表结构设计1、每张表都必须有三个字段:id,gmt_create,gmt_modify,代表主键ID,记录创建时间,记录修改时间。 id:必须是唯一并且递增的非负数数字类型,数据量小并发写入量不高用数据库自增Id,并发写入高或者数据量大有分表需求用雪花算法等id生成器生成。 12> `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id'> gmt_create:顺序递增,存储每条记录的创建时间,一般场景用datatime类型,有跨时区的需求用timestamp类型。 12> `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'> gmt_modify:存储每条记录的修改时间,一般场景用datatime类型,有跨时区的需求用timestamp类型。 12> `gmt_modify` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间'> 2、没有特殊需求都使用innodb存储引擎,并设置id为主键索引PRIMARY KEY。 12> PRIMARY KEY (`id`)> 因为id字段是innodb的聚簇索引,使用id查询记录的速度最快。 3、主键id字段的长度不宜太大,因为二级索引都会存储主键的值,而且值越大,比较大小就越耗时。 一般用无符号自增主键int(10)即可,最大值4,294,567,294(2,147,283,647*2)。需要存储超过该长度的记录可以用bigint(20)。其中int(N)中的N只是显示长度,详见:https://dev.mysql.com/doc/refman/8.0/en/integer-types.html。 4、数据量大的表不要物理删除,使用字段is_deleted来做逻辑删除,1 表示删除,0 表示未删除。 innodb在磁盘中是分页存储的,如果在记录中间插入或者删除数据都要移动后面的数据。 重要数据也可以使用逻辑删除。 逻辑删除不适合有唯一约束的表。 5、需要并发修改的数据使用version字段做乐观锁,每次修改都对版本号+1。 比如库存、余额等。 6、 长度超过 2000 字符的大字段用blob、text类型,并和主记录分开存储,可以提高查询速度。 mysql已页为单位存储,每个页的大小为16k,如果单条记录内容太大则会影响范围查询。 7、如果存的都是数字就不要用字符类型的字段,存储空间和排序比较时数字都优于字符串。 8、数字类型如果不存负数就用无符号unsigned,可以增加存储大小。 int(10):-2,147,283,647~2,147,283,646 int(10) unsigned:0~4,294,567,294 9、 小数类型为 decimal,禁止使用 float 和 double。 float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。 10、没有特殊需求,所有字段都不允许为null。 11、指定表字符集为utf8mb4,不要用utf8,mysql中utf8mb4才是正宗的UTF-8编码。 12、保证所有表使用同样的排序规则。 排序规则utf8mb4_unicode_ci比utf8mb4_general_ci更精确,推荐都使用utf8mb4_unicode_ci,参考:https://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci 13、禁止使用外键、 一切外键概念在应用层解决 。 索引设计1、 唯一索引名为uk_字段名;普通索引名则为 idx_字段名 。 2、根据业务的唯一特性,尽量给表加上唯一索引。 3、 在长度大于50的 varchar字段上建立索引时,必须指定索引长度,不然会对全部内容建立索引,浪费索引存储空间。 4、 建组合索引的时候,区分度最高的在最左边。 5、 索引需要建立在重复率低的字段上,重复率高的字段建立索引没有太大作用;。 SQL语句1、 禁止使用存储过程和触发器,存储过程和触发器难以调试和扩展,更没有移植性。 2、 超过三个表禁止 join;需要 join 的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段有索引。 3、除了count、sum、max、min等少数几个函数,将计算工作放在应用层来做,把计算和存储分离。 无状态应用迁移、扩容简单,有状态应用迁移、扩容复杂。 参考https://github.com/jly8866/archer/blob/master/src/docs/mysql_db_design_guide.md","categories":[{"name":"java","slug":"java","permalink":"http://sunshanpeng.com/categories/java/"}],"tags":[{"name":"mysql","slug":"mysql","permalink":"http://sunshanpeng.com/tags/mysql/"}]},{"title":"MySQL服务端配置","slug":"MySQL服务端配置","date":"2019-12-16T12:26:37.000Z","updated":"2020-03-01T13:46:18.842Z","comments":true,"path":"2019/12/16/MySQL服务端配置/","link":"","permalink":"http://sunshanpeng.com/2019/12/16/MySQL服务端配置/","excerpt":"","text":"[client]port = 3306socket = /tmp/mysql.sockdefault_character_set = utf8#default-collation = utf8_general_cidefault-collation = utf8_unicode_ci [mysqld]port = 3306socket = /tmp/mysql.sockbasedir = /app/data/mysqldatadir = /data/mysqlpid-file = /data/mysql/mysql.piduser = mysqlbind-address = 0.0.0.0character_set_server = utf8server-id = 1 #表示是本机的序号为1,一般来讲就是master的意思skip-name-resolve# 禁止MySQL对外部连接进行DNS解析,使用这一选项可以消除MySQL进行DNS解析的时间。但需要注意,如果开启该选项,# 则所有远程主机连接授权都要使用IP地址方式,否则MySQL将无法正常处理连接请求#skip-networkingback_log = 600# MySQL能有的连接数量。当主要MySQL线程在一个很短时间内得到非常多的连接请求,这就起作用,# 然后主线程花些时间(尽管很短)检查连接并且启动一个新线程。back_log值指出在MySQL暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中。# 如果期望在一个短时间内有很多连接,你需要增加它。也就是说,如果MySQL的连接数据达到max_connections时,新来的请求将会被存在堆栈中,# 以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过back_log,将不被授予连接资源。# 另外,这值(back_log)限于您的操作系统对到来的TCP/IP连接的侦听队列的大小。# 你的操作系统在这个队列大小上有它自己的限制(可以检查你的OS文档找出这个变量的最大值),试图设定back_log高于你的操作系统的限制将是无效的。max_connections = 1000# MySQL的最大连接数,如果服务器的并发连接请求量比较大,建议调高此值,以增加并行连接数量,当然这建立在机器能支撑的情况下,因为如果连接数越多,介于MySQL会为每个连接提供连接缓冲区,就会开销越多的内存,所以要适当调整该值,不能盲目提高设值。可以过’conn%’通配符查看当前状态的连接数量,以定夺该值的大小。max_connect_errors = 6000# 对于同一主机,如果有超出该参数值个数的中断错误连接,则该主机将被禁止连接。如需对该主机进行解禁,执行:FLUSH HOST。open_files_limit = 65535# MySQL打开的文件描述符限制,默认最小1024;当open_files_limit没有被配置的时候,比较max_connections5和ulimit -n的值,哪个大用哪个,# 当open_file_limit被配置的时候,比较open_files_limit和max_connections5的值,哪个大用哪个。table_open_cache = 12800# MySQL每打开一个表,都会读入一些数据到table_open_cache缓存中,当MySQL在这个缓存中找不到相应信息时,才会去磁盘上读取。默认值64# 假定系统有200个并发连接,则需将此参数设置为200*N(N为每个连接所需的文件描述符数目);# 当把table_open_cache设置为很大时,如果系统处理不了那么多文件描述符,那么就会出现客户端失效,连接不上max_allowed_packet = 40M# 接受的数据包大小;增加该变量的值十分安全,这是因为仅当需要时才会分配额外内存。例如,仅当你发出长查询或MySQLd必须返回大的结果行时MySQLd才会分配更多内存。# 该变量之所以取较小默认值是一种预防措施,以捕获客户端和服务器之间的错误信息包,并确保不会因偶然使用大的信息包而导致内存溢出。binlog_cache_size = 1M# 一个事务,在没有提交的时候,产生的日志,记录到Cache中;等到事务提交需要提交的时候,则把日志持久化到磁盘。默认binlog_cache_size大小32Kmax_heap_table_size = 8M# 定义了用户可以创建的内存表(memory table)的大小。这个值用来计算内存表的最大行数值。这个变量支持动态改变tmp_table_size = 16M# MySQL的heap(堆积)表缓冲大小。所有联合在一个DML指令内完成,并且大多数联合甚至可以不用临时表即可以完成。# 大多数临时表是基于内存的(HEAP)表。具有大的记录长度的临时表 (所有列的长度的和)或包含BLOB列的表存储在硬盘上。# 如果某个内部heap(堆积)表大小超过tmp_table_size,MySQL可以根据需要自动将内存中的heap表改为基于硬盘的MyISAM表。还可以通过设置tmp_table_size选项来增加临时表的大小。也就是说,如果调高该值,MySQL同时将增加heap表的大小,可达到提高联接查询速度的效果read_buffer_size = 2M# MySQL读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySQL会为它分配一段内存缓冲区。read_buffer_size变量控制这一缓冲区的大小。# 如果对表的顺序扫描请求非常频繁,并且你认为频繁扫描进行得太慢,可以通过增加该变量值以及内存缓冲区大小提高其性能read_rnd_buffer_size = 8M# MySQL的随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,# MySQL会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySQL会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大sort_buffer_size = 8M# MySQL执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。# 如果不能,可以尝试增加sort_buffer_size变量的大小join_buffer_size = 8M# 联合查询操作所能使用的缓冲区大小,和sort_buffer_size一样,该参数对应的分配内存也是每连接独享thread_cache_size = 8# 这个值(默认8)表示可以重新利用保存在缓存中线程的数量,当断开连接时如果缓存中还有空间,那么客户端的线程将被放到缓存中,# 如果线程重新被请求,那么请求将从缓存中读取,如果缓存中是空的或者是新的请求,那么这个线程将被重新创建,如果有很多新的线程,# 增加这个值可以改善系统性能.通过比较Connections和Threads_created状态的变量,可以看到这个变量的作用。(–>表示要调整的值)# 根据物理内存设置规则如下:# 1G —> 8# 2G —> 16# 3G —> 32# 大于3G —> 64query_cache_size = 8M#MySQL的查询缓冲大小(从4.0.1开始,MySQL提供了查询缓冲机制)使用查询缓冲,MySQL将SELECT语句和查询结果存放在缓冲区中,# 今后对于同样的SELECT语句(区分大小写),将直接从缓冲区中读取结果。根据MySQL用户手册,使用查询缓冲最多可以达到238%的效率。# 通过检查状态值’Qcache_%’,可以知道query_cache_size设置是否合理:如果Qcache_lowmem_prunes的值非常大,则表明经常出现缓冲不够的情况,# 如果Qcache_hits的值也非常大,则表明查询缓冲使用非常频繁,此时需要增加缓冲大小;如果Qcache_hits的值不大,则表明你的查询重复率很低,# 这种情况下使用查询缓冲反而会影响效率,那么可以考虑不用查询缓冲。此外,在SELECT语句中加入SQL_NO_CACHE可以明确表示不使用查询缓冲query_cache_limit = 2M#指定单个查询能够使用的缓冲区大小,默认1Mkey_buffer_size = 4M#指定用于索引的缓冲区大小,增加它可得到更好处理的索引(对所有读和多重写),到你能负担得起那样多。如果你使它太大,# 系统将开始换页并且真的变慢了。对于内存在4GB左右的服务器该参数可设置为384M或512M。通过检查状态值Key_read_requests和Key_reads,# 可以知道key_buffer_size设置是否合理。比例key_reads/key_read_requests应该尽可能的低,# 至少是1:100,1:1000更好(上述状态值可以使用SHOW STATUS LIKE ‘key_read%’获得)。注意:该参数值设置的过大反而会是服务器整体效率降低ft_min_word_len = 4# 分词词汇最小长度,默认4transaction_isolation = REPEATABLE-READ# MySQL支持4种事务隔离级别,他们分别是:# READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE.# 如没有指定,MySQL默认采用的是REPEATABLE-READ,ORACLE默认的是READ-COMMITTEDlog_bin = mysql-binbinlog_format = mixedexpire_logs_days = 30 #超过30天的binlog删除log_error = /data/mysql/mysql-error.log #错误日志路径slow_query_log = 1long_query_time = 1 #慢查询时间 超过1秒则为慢查询slow_query_log_file = /data/mysql/mysql-slow.logperformance_schema = 0explicit_defaults_for_timestamp#lower_case_table_names = 1 #不区分大小写skip-external-locking #MySQL选项以避免外部锁定。该选项默认开启default-storage-engine = InnoDB #默认存储引擎innodb_file_per_table = 1# InnoDB为独立表空间模式,每个数据库的每个表都会生成一个数据空间# 独立表空间优点:# 1.每个表都有自已独立的表空间。# 2.每个表的数据和索引都会存在自已的表空间中。# 3.可以实现单表在不同的数据库中移动。# 4.空间可以回收(除drop table操作处,表空不能自已回收)# 缺点:# 单表增加过大,如超过100G# 结论:# 共享表空间在Insert操作上少有优势。其它都没独立表空间表现好。当启用独立表空间时,请合理调整:innodb_open_filesinnodb_open_files = 500# 限制Innodb能打开的表的数据,如果库里的表特别多的情况,请增加这个。这个值默认是300innodb_buffer_pool_size = 64M# InnoDB使用一个缓冲池来保存索引和原始数据, 不像MyISAM.# 这里你设置越大,你在存取表里面数据时所需要的磁盘I/O越少.# 在一个独立使用的数据库服务器上,你可以设置这个变量到服务器物理内存大小的80%# 不要设置过大,否则,由于物理内存的竞争可能导致操作系统的换页颠簸.# 注意在32位系统上你每个进程可能被限制在 2-3.5G 用户层面内存限制,# 所以不要设置的太高.innodb_write_io_threads = 4innodb_read_io_threads = 4# innodb使用后台线程处理数据页上的读写 I/O(输入输出)请求,根据你的 CPU 核数来更改,默认是4# 注:这两个参数不支持动态改变,需要把该参数加入到my.cnf里,修改完后重启MySQL服务,允许值的范围从 1-64innodb_thread_concurrency = 0# 默认设置为 0,表示不限制并发数,这里推荐设置为0,更好去发挥CPU多核处理能力,提高并发量innodb_purge_threads = 1# InnoDB中的清除操作是一类定期回收无用数据的操作。在之前的几个版本中,清除操作是主线程的一部分,这意味着运行时它可能会堵塞其它的数据库操作。# 从MySQL5.5.X版本开始,该操作运行于独立的线程中,并支持更多的并发数。用户可通过设置innodb_purge_threads配置参数来选择清除操作是否使用单# 独线程,默认情况下参数设置为0(不使用单独线程),设置为 1 时表示使用单独的清除线程。建议为1innodb_flush_log_at_trx_commit = 2# 0:如果innodb_flush_log_at_trx_commit的值为0,log buffer每秒就会被刷写日志文件到磁盘,提交事务的时候不做任何操作(执行是由mysql的master thread线程来执行的。# 主线程中每秒会将重做日志缓冲写入磁盘的重做日志文件(REDO LOG)中。不论事务是否已经提交)默认的日志文件是ib_logfile0,ib_logfile1# 1:当设为默认值1的时候,每次提交事务的时候,都会将log buffer刷写到日志。# 2:如果设为2,每次提交事务都会写日志,但并不会执行刷的操作。每秒定时会刷到日志文件。要注意的是,并不能保证100%每秒一定都会刷到磁盘,这要取决于进程的调度。# 每次事务提交的时候将数据写入事务日志,而这里的写入仅是调用了文件系统的写入操作,而文件系统是有 缓存的,所以这个写入并不能保证数据已经写入到物理磁盘# 默认值1是为了保证完整的ACID。当然,你可以将这个配置项设为1以外的值来换取更高的性能,但是在系统崩溃的时候,你将会丢失1秒的数据。# 设为0的话,mysqld进程崩溃的时候,就会丢失最后1秒的事务。设为2,只有在操作系统崩溃或者断电的时候才会丢失最后1秒的数据。InnoDB在做恢复的时候会忽略这个值。# 总结# 设为1当然是最安全的,但性能页是最差的(相对其他两个参数而言,但不是不能接受)。如果对数据一致性和完整性要求不高,完全可以设为2,如果只最求性能,例如高并发写的日志服务器,设为0来获得更高性能innodb_log_buffer_size = 2M# 此参数确定些日志文件所用的内存大小,以M为单位。缓冲区更大能提高性能,但意外的故障将会丢失数据。MySQL开发人员建议设置为1-8M之间innodb_log_file_size = 32M# 此参数确定数据日志文件的大小,更大的设置可以提高性能,但也会增加恢复故障数据库所需的时间innodb_log_files_in_group = 3# 为提高性能,MySQL可以以循环方式将日志文件写到多个文件。推荐设置为3innodb_max_dirty_pages_pct = 90# innodb主线程刷新缓存池中的数据,使脏数据比例小于90%innodb_lock_wait_timeout = 120# InnoDB事务在被回滚之前可以等待一个锁定的超时秒数。InnoDB在它自己的锁定表中自动检测事务死锁并且回滚事务。InnoDB用LOCK TABLES语句注意到锁定设置。默认值是50秒bulk_insert_buffer_size = 8M# 批量插入缓存大小, 这个参数是针对MyISAM存储引擎来说的。适用于在一次性插入100-1000+条记录时, 提高效率。默认值是8M。可以针对数据量的大小,翻倍增加。myisam_sort_buffer_size = 8M# MyISAM设置恢复表之时使用的缓冲区的尺寸,当在REPAIR TABLE或用CREATE INDEX创建索引或ALTER TABLE过程中排序 MyISAM索引分配的缓冲区myisam_max_sort_file_size = 10G# 如果临时文件会变得超过索引,不要使用快速排序索引方法来创建一个索引。注释:这个参数以字节的形式给出myisam_repair_threads = 1# 如果该值大于1,在Repair by sorting过程中并行创建MyISAM表索引(每个索引在自己的线程内)interactive_timeout = 28800# 服务器关闭交互式连接前等待活动的秒数。交互式客户端定义为在mysql_real_connect()中使用CLIENT_INTERACTIVE选项的客户端。默认值:28800秒(8小时)wait_timeout = 28800# 服务器关闭非交互连接之前等待活动的秒数。在线程启动时,根据全局wait_timeout值或全局interactive_timeout值初始化会话wait_timeout值,# 取决于客户端类型(由mysql_real_connect()的连接选项CLIENT_INTERACTIVE定义)。参数默认值:28800秒(8小时)# MySQL服务器所支持的最大连接数是有上限的,因为每个连接的建立都会消耗内存,因此我们希望客户端在连接到MySQL Server处理完相应的操作后,# 应该断开连接并释放占用的内存。如果你的MySQL Server有大量的闲置连接,他们不仅会白白消耗内存,而且如果连接一直在累加而不断开,# 最终肯定会达到MySQL Server的连接上限数,这会报’too many connections’的错误。对于wait_timeout的值设定,应该根据系统的运行情况来判断。# 在系统运行一段时间后,可以通过show processlist命令查看当前系统的连接状态,如果发现有大量的sleep状态的连接进程,则说明该参数设置的过大, default-time_zone = ‘+8:00’ #时间区设置北京时间 character_set_server = utf8#default-collation = utf8_general_ci#default-collation = utf8_unicode_cicollation-server = utf8_unicode_ci # 可以进行适当的调整小些。要同时设置interactive_timeout和wait_timeout才会生效。[mysqldump]quickmax_allowed_packet = 16M #服务器发送和接受的最大包长度[myisamchk]key_buffer_size = 8Msort_buffer_size = 8Mread_buffer = 4Mwrite_buffer = 4M","categories":[{"name":"mysql","slug":"mysql","permalink":"http://sunshanpeng.com/categories/mysql/"}],"tags":[{"name":"mysql","slug":"mysql","permalink":"http://sunshanpeng.com/tags/mysql/"}]},{"title":"Web开发规范","slug":"Web开发规范","date":"2019-12-16T12:26:37.000Z","updated":"2020-03-01T13:48:33.601Z","comments":true,"path":"2019/12/16/Web开发规范/","link":"","permalink":"http://sunshanpeng.com/2019/12/16/Web开发规范/","excerpt":"","text":"API设计1、遵循RESTFul规范。 使用请求方法区分动作,使用URI区分资源。 比如用户user: 增:POST /users 删:DELETE /users/{id} 改:PUT /users/{id} 查:GET /users/{id} 2、所有接口必须是幂等的。 失败重试或者重复操作时保证系统的健壮性。新增操作视情况而定,尽量幂等。 MQ消费也需要幂等。 3、基于以上两点,构建声明式API,即用户只需要关注做什么,而不需要关注怎么做。 4、前后端分离开发时,前端负责渲染和交互,后端负责数据和逻辑,不能把逻辑放在前端处理。 5、后端开发时禁止使用Map作为请求参数和数据库返回值。 微服务设计1、为每个微服务定义一个唯一的应用编码(AppId),服务注册调用、资源管理等都要用到。 可以统一由三个字母组成,最多包含17676个微服务。 2、如果有虚拟机混部的场景,保证每个服务的端口是唯一的。 3、每次需求都有对应版本号,需求文档、产品方案、代码、部署的版本号要一致。 版本号一般构成为[大版本.小版本.修复版本]。 如果不兼容之前的功能,或者功能变动较大,需要增加大版本号; 如果兼容之前的功能,但是有新功能增加,加小版本号即可; 如果没有增加新功能而是修复Bug,加修复版本号。 4、抛出异常的提示信息里需要携带当前服务的AppId,否则一整条调用链路排查太耗时耗力。 5、警惕上游调用方,对C端接口做好限流工作,防止被大流量压垮。 6、怀疑下游提供方,做好熔断和降级,防止被慢响应拖垮。","categories":[{"name":"java","slug":"java","permalink":"http://sunshanpeng.com/categories/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://sunshanpeng.com/tags/java/"}]},{"title":"使用Arthas ognl执行表达式","slug":"使用Arthas ognl执行表达式","date":"2019-11-28T13:26:37.000Z","updated":"2020-03-01T13:33:51.798Z","comments":true,"path":"2019/11/28/使用Arthas ognl执行表达式/","link":"","permalink":"http://sunshanpeng.com/2019/11/28/使用Arthas ognl执行表达式/","excerpt":"","text":"Arthas ognl命令介绍 https://alibaba.github.io/arthas/ognl.html 使用方法命令格式: 1ognl 'express' 调用静态函数12$ ognl '@System@getProperty(\"java.home\")'@String[/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.191.b12-1.el7_6.x86_64/jre] 执行多行表达式,赋值给临时变量,返回一个List12345$ ognl '#value1=@System@getProperty(\"java.home\"), #value2=@System@getProperty(\"java.runtime.name\"), {#value1, #value2}'@ArrayList[ @String[/opt/java/8.0.181-zulu/jre], @String[OpenJDK Runtime Environment],]","categories":[{"name":"java","slug":"java","permalink":"http://sunshanpeng.com/categories/java/"}],"tags":[{"name":"arthas","slug":"arthas","permalink":"http://sunshanpeng.com/tags/arthas/"}]},{"title":"使用Arthas jad命令反编译源代码","slug":"使用Arthas jad命令反编译源代码","date":"2019-11-28T13:26:37.000Z","updated":"2020-03-01T13:33:42.985Z","comments":true,"path":"2019/11/28/使用Arthas jad命令反编译源代码/","link":"","permalink":"http://sunshanpeng.com/2019/11/28/使用Arthas jad命令反编译源代码/","excerpt":"","text":"Arthas jad命令介绍 https://alibaba.github.io/arthas/jad.html 将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码 使用方法命令格式: 1jad class 示例: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546[arthas@30]$ jad java.lang.StringClassLoader:Location:/* * Decompiled with CFR. */package java.lang;import java.io.ObjectStreamField;import java.io.Serializable;import java.io.UnsupportedEncodingException;import java.nio.charset.Charset;import java.util.ArrayList;import java.util.Arrays;import java.util.Comparator;import java.util.Formatter;import java.util.List;import java.util.Locale;import java.util.Objects;import java.util.StringJoiner;import java.util.regex.Matcher;import java.util.regex.Pattern;public final class Stringimplements Serializable,Comparable<String>,CharSequence { private final char[] value; private int hash; private static final long serialVersionUID = -6849794470754667710L; private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); public String(byte[] bytes, int offset, int length) { String.checkBounds(bytes, offset, length); this.value = StringCoding.decode(bytes, offset, length); } public String(byte[] bytes, Charset charset) { this(bytes, 0, bytes.length, charset); }...","categories":[{"name":"java","slug":"java","permalink":"http://sunshanpeng.com/categories/java/"}],"tags":[{"name":"arthas","slug":"arthas","permalink":"http://sunshanpeng.com/tags/arthas/"}]},{"title":"使用Arthas thread命令查看线程堆栈","slug":"使用Arthas thread命令查看线程堆栈等信息","date":"2019-11-28T13:26:37.000Z","updated":"2020-03-01T13:33:59.901Z","comments":true,"path":"2019/11/28/使用Arthas thread命令查看线程堆栈等信息/","link":"","permalink":"http://sunshanpeng.com/2019/11/28/使用Arthas thread命令查看线程堆栈等信息/","excerpt":"","text":"Arthas thread命令介绍 https://alibaba.github.io/arthas/thread.html 查看当前Java进程的线程信息和线程的堆栈信息。 使用方法命令格式: 1thread [id] [-n 5] 示例: 显示所有线程的信息12345678910111213141516171819$ threadThreads Total: 16, NEW: 0, RUNNABLE: 7, BLOCKED: 0, WAITING: 5, TIMED_WAITING: 4, TERMINATED: 0ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTE DAEMON30 as-command-execute-daemon system 9 RUNNABLE 72 0:0 false true23 as-session-expire-daemon system 9 TIMED_WAIT 27 0:0 false true22 Attach Listener system 9 RUNNABLE 0 0:0 false true11 pool-2-thread-1 main 5 TIMED_WAIT 0 0:0 false false12 Thread-2 main 5 RUNNABLE 0 0:0 false true13 pool-3-thread-1 main 5 TIMED_WAIT 0 0:0 false false25 as-selector-daemon system 9 RUNNABLE 0 0:0 false true14 Thread-3 main 5 TIMED_WAIT 0 0:0 false false26 pool-5-thread-1 system 5 WAITING 0 0:0 false false15 Thread-4 main 5 RUNNABLE 0 0:0 false false1 main main 5 WAITING 0 0:2 false false2 Reference Handler system 10 WAITING 0 0:0 false true3 Finalizer system 8 WAITING 0 0:0 false true4 Signal Dispatcher system 9 RUNNABLE 0 0:0 false true20 NonBlockingInputStreamThread main 5 WAITING 0 0:0 false true21 Thread-8 main 5 RUNNABLE 0 0:0 false true 显示指定线程的运行堆栈123456789$ thread 1\"main\" Id=1 WAITING on java.util.concurrent.CountDownLatch$Sync@29fafb28 at sun.misc.Unsafe.park(Native Method) - waiting on java.util.concurrent.CountDownLatch$Sync@29fafb28 at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836) at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304) at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231) 显示当前最忙的前N个线程并打印堆栈123456789101112131415161718192021222324thread -n 3\"as-command-execute-daemon\" Id=29 cpuUsage=75% RUNNABLE at sun.management.ThreadImpl.dumpThreads0(Native Method) at sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:440) at com.taobao.arthas.core.command.monitor200.ThreadCommand$1.action(ThreadCommand.java:58) at com.taobao.arthas.core.command.handler.AbstractCommandHandler.execute(AbstractCommandHandler.java:238) at com.taobao.arthas.core.command.handler.DefaultCommandHandler.handleCommand(DefaultCommandHandler.java:67) at com.taobao.arthas.core.server.ArthasServer$4.run(ArthasServer.java:276) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:745) Number of locked synchronizers = 1 - java.util.concurrent.ThreadPoolExecutor$Worker@6cd0b6f8 \"as-session-expire-daemon\" Id=25 cpuUsage=24% TIMED_WAITING at java.lang.Thread.sleep(Native Method) at com.taobao.arthas.core.server.DefaultSessionManager$2.run(DefaultSessionManager.java:85) \"Reference Handler\" Id=2 cpuUsage=0% WAITING on java.lang.ref.Reference$Lock@69ba0f27 at java.lang.Object.wait(Native Method) - waiting on java.lang.ref.Reference$Lock@69ba0f27 at java.lang.Object.wait(Object.java:503) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)","categories":[{"name":"java","slug":"java","permalink":"http://sunshanpeng.com/categories/java/"}],"tags":[{"name":"arthas","slug":"arthas","permalink":"http://sunshanpeng.com/tags/arthas/"}]},{"title":"使用Arthas trace定位并优化接口响应慢的问题","slug":"使用Arthas trace定位并优化接口响应慢的问题","date":"2019-11-28T13:26:37.000Z","updated":"2020-03-01T13:34:10.615Z","comments":true,"path":"2019/11/28/使用Arthas trace定位并优化接口响应慢的问题/","link":"","permalink":"http://sunshanpeng.com/2019/11/28/使用Arthas trace定位并优化接口响应慢的问题/","excerpt":"","text":"Arthas trace命令介绍 https://alibaba.github.io/arthas/trace.html 打印方法内部调用路径,并输出方法路径上的每个节点上耗时。 trace命令只会trace匹配到的函数里的子调用,并不会向下trace多层。因为trace是代价比较贵的,多层trace可能会导致最终要trace的类和函数非常多。 使用方法命令格式: 1trace class method 示例: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566[arthas@30]$ trace com.sunshanpeng.platform.pub.service.impl.PublishServiceImpl checkPrePubPress Q or Ctrl+C to abort.Affect(class-cnt:2 , method-cnt:4) cost in 408 ms.`---ts=2019-11-12 14:47:28;thread_name=http-nio-8266-exec-147;id=7938;is_daemon=true;priority=5;TCCL=org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedWebappClassLoader@6c902fd5 `---[3771.437071ms] com.sunshanpeng.platform.pub.service.impl.PublishServiceImpl:checkPrePub() +---[0.003039ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getClusterCode() #157 +---[0.001022ms] java.util.Map:get() #157 +---[0.002185ms] com.sunshanpeng.platform.pub.common.enums.PubStatusEnum:getCode() #161 +---[0.001843ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setStatus() #161 +---[1645.580557ms] com.sunshanpeng.platform.pub.service.impl.PublishServiceImpl:selectBySelective() #162 +---[0.002757ms] org.springframework.util.CollectionUtils:isEmpty() #163 +---[0.001587ms] com.sunshanpeng.platform.pub.common.enums.PubStatusEnum:getCode() #169 +---[0.002123ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setStatus() #169 +---[1478.93462ms] com.sunshanpeng.platform.pub.service.impl.PublishServiceImpl:selectBySelective() #170 +---[0.002329ms] org.springframework.util.CollectionUtils:isEmpty() #171 `---[646.735412ms] com.sunshanpeng.platform.pub.service.impl.PublishServiceImpl:buildPublishDetailDTO() #178 `---[646.539364ms] com.sunshanpeng.platform.pub.service.impl.PublishServiceImpl:buildPublishDetailDTO() +---[0.011378ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getPubEnv() #513 +---[0.002328ms] org.springframework.util.StringUtils:isEmpty() #513 +---[0.001789ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getClusterCode() #516 +---[0.001076ms] org.springframework.util.StringUtils:isEmpty() #516 +---[0.005232ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getSysCode() #519 +---[0.001092ms] org.springframework.util.StringUtils:isEmpty() #519 +---[0.004531ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getSysName() #522 +---[9.95E-4ms] org.springframework.util.StringUtils:isEmpty() #522 +---[0.001071ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getPubEnv() #525 +---[0.001343ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getClusterCode() #525 +---[0.001108ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getSysCode() #525 +---[1.617415ms] com.sunshanpeng.platform.pub.service.EnvBindService:getRelationByArgs() #525 +---[0.002319ms] java.util.List:get() #526 +---[0.014244ms] java.util.List:size() #527 +---[0.011744ms] com.sunshanpeng.platform.pub.dto.envbind.EnvBindDetailDTO:getJobName() #527 +---[0.001469ms] org.springframework.util.StringUtils:isEmpty() #527 +---[0.001367ms] com.sunshanpeng.platform.pub.dto.envbind.EnvBindDetailDTO:getJobName() #532 +---[0.006322ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setJobName() #532 +---[0.001252ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getPubEnv() #535 +---[0.001285ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getClusterCode() #535 +---[0.001253ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getSysCode() #535 +---[643.723767ms] com.sunshanpeng.platform.pub.service.JenkinsService:getBuildArgs() #535 +---[0.010914ms] com.sunshanpeng.platform.pub.common.enums.CommonStatusEnum:getCode() #536 +---[0.006734ms] com.sunshanpeng.platform.pub.dto.envbind.EnvBindDetailDTO:getSwEnable() #536 +---[0.014911ms] java.lang.Integer:equals() #536 +---[0.002085ms] java.lang.Boolean:valueOf() #536 +---[0.011168ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setApmEnable() #536 +---[0.004956ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getPubParamKey() #542 +---[0.002016ms] org.springframework.util.StringUtils:isEmpty() #542 +---[0.002196ms] java.util.Map:get() #550 +---[0.004316ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getPubParamValue() #551 +---[0.001027ms] org.springframework.util.StringUtils:isEmpty() #551 +---[0.004536ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getRemark() #559 +---[0.001113ms] org.springframework.util.StringUtils:isEmpty() #559 +---[0.004637ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setRemark() #560 +---[0.001502ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getSysCode() #562 +---[0.322113ms] com.sunshanpeng.platform.pub.service.impl.PublishServiceImpl:getCiCode() #562 +---[0.005395ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setCiCode() #562 +---[0.004249ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setConsoleLog() #563 +---[0.001335ms] java.lang.Integer:valueOf() #564 +---[0.004697ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setBuildNo() #564 +---[0.004276ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setCreator() #565 +---[0.004354ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setCreatorCode() #566 +---[0.020689ms] java.time.LocalDateTime:now() #567 +---[0.004397ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setGmtCreate() #567 +---[0.002075ms] java.time.LocalDateTime:now() #568 +---[0.004739ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setGmtModify() #568 +---[0.001317ms] com.sunshanpeng.platform.pub.common.enums.PubStatusEnum:getCode() #569 `---[0.001365ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setStatus() #569 可以看到目标方法的耗时为3771.437071ms,其中com.sunshanpeng.platform.pub.service.impl.PublishServiceImpl:selectBySelective()方法调用了两次,并且耗时都在1.5秒左右。 优化前执行计划123456789mysql> explain select id, ci_code, build_no, creator_code, sys_code, sys_name, pub_env,cluster_code, job_name, pub_param_key, pub_param_value, status, remark, creator, gmt_create, gmt_modifyfrom t_pub_publish where sys_code = \"aim\" and pub_env = \"qa\" and cluster_code = 'cn-hd-idc-test-1' and status = 1;+----+-------------+---------------+------+---------------+------+---------+------+-------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+---------------+------+---------------+------+---------+------+-------+-------------+| 1 | SIMPLE | t_pub_publish | ALL | NULL | NULL | NULL | NULL | 30544 | Using where |+----+-------------+---------------+------+---------------+------+---------+------+-------+-------------+1 row in set 加组合索引12ALTER TABLE `t_pub_publish`ADD INDEX `idx_code` (`sys_code`, `pub_env`, `cluster_code`, `status`) ; 优化后执行计划123456789mysql> explain select id, ci_code, build_no, creator_code, sys_code, sys_name, pub_env,cluster_code, job_name, pub_param_key, pub_param_value, status, remark, creator, gmt_create, gmt_modifyfrom t_pub_publish where sys_code = \"aim\" and pub_env = \"qa\" and cluster_code = 'cn-hd-idc-test-1' and status = 1;+----+-------------+---------------+------+---------------+----------+---------+-------------------------+------+-----------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+---------------+------+---------------+----------+---------+-------------------------+------+-----------------------+| 1 | SIMPLE | t_pub_publish | ref | idx_code | idx_code | 249 | const,const,const,const | 1 | Using index condition |+----+-------------+---------------+------+---------------+----------+---------+-------------------------+------+-----------------------+1 row in set 优化后耗时123456789101112131415161718[arthas@30]$ trace com.sunshanpeng.platform.pub.service.impl.PublishServiceImpl checkPrePub Press Q or Ctrl+C to abort.Affect(class-cnt:2 , method-cnt:1) cost in 390 ms.`---ts=2019-11-13 13:42:15;thread_name=http-nio-8266-exec-143;id=7934;is_daemon=true;priority=5;TCCL=org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedWebappClassLoader@6c902fd5 `---[203.648108ms] com.sunshanpeng.platform.pub.service.impl.PublishServiceImpl:checkPrePub() +---[min=4.7E-4ms,max=0.010168ms,total=0.010638ms,count=2] java.lang.Integer:<init>() #157 +---[4.61E-4ms] java.lang.reflect.Method:invoke() #157 +---[0.003624ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:getClusterCode() #157 +---[0.001096ms] java.util.Map:get() #157 +---[0.002196ms] com.sunshanpeng.platform.pub.common.enums.PubStatusEnum:getCode() #161 +---[0.001674ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setStatus() #161 +---[0.578989ms] com.sunshanpeng.platform.pub.service.impl.PublishServiceImpl:selectBySelective() #162 +---[6.84E-4ms] org.springframework.util.CollectionUtils:isEmpty() #163 +---[5.82E-4ms] com.sunshanpeng.platform.pub.common.enums.PubStatusEnum:getCode() #169 +---[5.45E-4ms] com.sunshanpeng.platform.pub.dto.publish.PublishDetailDTO:setStatus() #169 +---[0.407368ms] com.sunshanpeng.platform.pub.service.impl.PublishServiceImpl:selectBySelective() #170 +---[5.59E-4ms] org.springframework.util.CollectionUtils:isEmpty() #171 `---[202.38997ms] com.sunshanpeng.platform.pub.service.impl.PublishServiceImpl:buildPublishDetailDTO() #178 优化后SQL查询时间降到1ms不到,总耗时200ms,效果明显。","categories":[{"name":"java","slug":"java","permalink":"http://sunshanpeng.com/categories/java/"}],"tags":[{"name":"arthas","slug":"arthas","permalink":"http://sunshanpeng.com/tags/arthas/"}]},{"title":"使用Arthas tt命令记录调用信息","slug":"使用Arthas tt命令记录调用信息","date":"2019-11-28T13:26:37.000Z","updated":"2020-03-01T13:34:19.355Z","comments":true,"path":"2019/11/28/使用Arthas tt命令记录调用信息/","link":"","permalink":"http://sunshanpeng.com/2019/11/28/使用Arthas tt命令记录调用信息/","excerpt":"","text":"Arthas tt命令介绍 https://alibaba.github.io/arthas/tt.html 方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测 使用方法命令格式: 1tt -t class method 记录指定方法的每次调用环境现场12345678910$ tt -t demo.MathGame primeFactorsPress Ctrl+C to abort.Affect(class-cnt:1 , method-cnt:1) cost in 66 ms. INDEX TIMESTAMP COST(ms) IS-RET IS-EXP OBJECT CLASS METHOD------------------------------------------------------------------------------------------------------------------------------------- 1000 2018-12-04 11:15:38 1.096236 false true 0x4b67cf4d MathGame primeFactors 1001 2018-12-04 11:15:39 0.191848 false true 0x4b67cf4d MathGame primeFactors 1002 2018-12-04 11:15:40 0.069523 false true 0x4b67cf4d MathGame primeFactors 1003 2018-12-04 11:15:41 0.186073 false true 0x4b67cf4d MathGame primeFactors 1004 2018-12-04 11:15:42 17.76437 true false 0x4b67cf4d MathGame primeFactors 检索调用记录列出所有调用记录1234567891011$ tt -l INDEX TIMESTAMP COST(ms) IS-RET IS-EXP OBJECT CLASS METHOD------------------------------------------------------------------------------------------------------------------------------------- 1000 2018-12-04 11:15:38 1.096236 false true 0x4b67cf4d MathGame primeFactors 1001 2018-12-04 11:15:39 0.191848 false true 0x4b67cf4d MathGame primeFactors 1002 2018-12-04 11:15:40 0.069523 false true 0x4b67cf4d MathGame primeFactors 1003 2018-12-04 11:15:41 0.186073 false true 0x4b67cf4d MathGame primeFactors 1004 2018-12-04 11:15:42 17.76437 true false 0x4b67cf4d MathGame primeFactors 9 1005 2018-12-04 11:15:43 0.4776 false true 0x4b67cf4d MathGame primeFactorsAffect(row-cnt:6) cost in 4 ms. 筛选调用记录1234567891011$ tt -s 'method.name==\"primeFactors\"' INDEX TIMESTAMP COST(ms) IS-RET IS-EXP OBJECT CLASS METHOD------------------------------------------------------------------------------------------------------------------------------------- 1000 2018-12-04 11:15:38 1.096236 false true 0x4b67cf4d MathGame primeFactors 1001 2018-12-04 11:15:39 0.191848 false true 0x4b67cf4d MathGame primeFactors 1002 2018-12-04 11:15:40 0.069523 false true 0x4b67cf4d MathGame primeFactors 1003 2018-12-04 11:15:41 0.186073 false true 0x4b67cf4d MathGame primeFactors 1004 2018-12-04 11:15:42 17.76437 true false 0x4b67cf4d MathGame primeFactors 9 1005 2018-12-04 11:15:43 0.4776 false true 0x4b67cf4d MathGame primeFactorsAffect(row-cnt:6) cost in 607 ms. 查看调用信息12345678910111213141516$ tt -i 1003 INDEX 1003 GMT-CREATE 2018-12-04 11:15:41 COST(ms) 0.186073 OBJECT 0x4b67cf4d CLASS demo.MathGame METHOD primeFactors IS-RETURN false IS-EXCEPTION true PARAMETERS[0] @Integer[-564322413] THROW-EXCEPTION java.lang.IllegalArgumentException: number is: -564322413, need >= 2 at demo.MathGame.primeFactors(MathGame.java:46) at demo.MathGame.run(MathGame.java:24) at demo.MathGame.main(MathGame.java:16) Affect(row-cnt:1) cost in 11 ms. 重新发起一次调用123456789101112131415161718$ tt -i 1004 -p RE-INDEX 1004 GMT-REPLAY 2018-12-04 11:26:00 OBJECT 0x4b67cf4d CLASS demo.MathGame METHOD primeFactors PARAMETERS[0] @Integer[946738738] IS-RETURN true IS-EXCEPTION false COST(ms) 0.186073 RETURN-OBJ @ArrayList[ @Integer[2], @Integer[11], @Integer[17], @Integer[2531387], ]Time fragment[1004] successfully replayed.Affect(row-cnt:1) cost in 14 ms. replay的注意事项 ThreadLocal 信息丢失 很多框架偷偷的将一些环境变量信息塞到了发起调用线程的 ThreadLocal 中,由于调用线程发生了变化,这些 ThreadLocal 线程信息无法通过 Arthas 保存,所以这些信息将会丢失。 一些常见的 CASE 比如:鹰眼的 TraceId 等。 引用的对象 需要强调的是,tt 命令是将当前环境的对象引用保存起来,但仅仅也只能保存一个引用而已。如果方法内部对入参进行了变更,或者返回的对象经过了后续的处理,那么在 tt 查看的时候将无法看到当时最准确的值。这也是为什么 watch 命令存在的意义。","categories":[{"name":"java","slug":"java","permalink":"http://sunshanpeng.com/categories/java/"}],"tags":[{"name":"arthas","slug":"arthas","permalink":"http://sunshanpeng.com/tags/arthas/"}]},{"title":"使用Arthas watch观察方法执行","slug":"使用Arthas watch观察方法执行数据","date":"2019-11-28T13:26:37.000Z","updated":"2020-03-01T13:34:29.237Z","comments":true,"path":"2019/11/28/使用Arthas watch观察方法执行数据/","link":"","permalink":"http://sunshanpeng.com/2019/11/28/使用Arthas watch观察方法执行数据/","excerpt":"","text":"Arthas watch命令介绍 https://alibaba.github.io/arthas/watch.html 方便的观察到指定方法的调用情况。 能观察到的范围为:入参、返回值、抛出异常和当前对象的属性,通过编写 OGNL 表达式进行对应变量的查看。 使用方法命令格式: 1watch class method \"{params,returnObj,throwExp}\" -x 2 观察方法出参和返回值1234567891011121314$ watch demo.MathGame primeFactors \"{params,returnObj}\" -x 2Press Ctrl+C to abort.Affect(class-cnt:1 , method-cnt:1) cost in 44 ms.ts=2018-12-03 19:16:51; [cost=1.280502ms] result=@ArrayList[ @Object[][ @Integer[535629513], ], @ArrayList[ @Integer[3], @Integer[19], @Integer[191], @Integer[49199], ],] 调整-x的值,观察具体的方法参数值12345678910111213141516171819202122232425262728$ watch demo.MathGame primeFactors \"{params,target}\" -x 3Press Ctrl+C to abort.Affect(class-cnt:1 , method-cnt:1) cost in 58 ms.ts=2018-12-03 19:34:19; [cost=0.587833ms] result=@ArrayList[ @Object[][ @Integer[47816758], ], @MathGame[ random=@Random[ serialVersionUID=@Long[3905348978240129619], seed=@AtomicLong[3133719055989], multiplier=@Long[25214903917], addend=@Long[11], mask=@Long[281474976710655], DOUBLE_UNIT=@Double[1.1102230246251565E-16], BadBound=@String[bound must be positive], BadRange=@String[bound must be greater than origin], BadSize=@String[size must be non-negative], seedUniquifier=@AtomicLong[-3282039941672302964], nextNextGaussian=@Double[0.0], haveNextNextGaussian=@Boolean[false], serialPersistentFields=@ObjectStreamField[][isEmpty=false;size=3], unsafe=@Unsafe[sun.misc.Unsafe@2eaa1027], seedOffset=@Long[24], ], illegalArgumentCount=@Integer[13159], ],] -x表示遍历深度,可以调整来打印具体的参数和结果内容,默认值是1。 条件表达式的例子1234567$ watch demo.MathGame primeFactors \"{params[0],target}\" \"params[0]<0\"Press Ctrl+C to abort.Affect(class-cnt:1 , method-cnt:1) cost in 68 ms.ts=2018-12-03 19:36:04; [cost=0.530255ms] result=@ArrayList[ @Integer[-18178089], @MathGame[demo.MathGame@41cf53f9],] 按照耗时进行过滤123456789101112$ watch demo.MathGame primeFactors '{params, returnObj}' '#cost>200' -x 2Press Ctrl+C to abort.Affect(class-cnt:1 , method-cnt:1) cost in 66 ms.ts=2018-12-03 19:40:28; [cost=2112.168897ms] result=@ArrayList[ @Object[][ @Integer[2141897465], ], @ArrayList[ @Integer[5], @Integer[428379493], ],] #cost>200(单位是ms)表示只有当耗时大于200ms时才会输出,过滤掉执行时间小于200ms的调用 观察当前对象中的属性1234567$ watch demo.MathGame primeFactors 'target'Press Ctrl+C to abort.Affect(class-cnt:1 , method-cnt:1) cost in 52 ms.ts=2018-12-03 19:41:52; [cost=0.477882ms] result=@MathGame[ random=@Random[java.util.Random@522b408a], illegalArgumentCount=@Integer[13355],]","categories":[{"name":"java","slug":"java","permalink":"http://sunshanpeng.com/categories/java/"}],"tags":[{"name":"arthas","slug":"arthas","permalink":"http://sunshanpeng.com/tags/arthas/"}]},{"title":"Git开发规范","slug":"Git开发规范","date":"2019-11-17T12:46:37.000Z","updated":"2020-03-01T13:42:50.992Z","comments":true,"path":"2019/11/17/Git开发规范/","link":"","permalink":"http://sunshanpeng.com/2019/11/17/Git开发规范/","excerpt":"","text":"注释规范12345678910feature:新增featurefix: 修复bugdocs: 仅仅修改了文档,比如README, CHANGELOG, CONTRIBUTE等等sql: 新增、修改SQL文件style: 仅仅修改了空格、格式缩进、符号等等,不改变代码逻辑refactor: 代码重构,没有加新功能或者修复bugperf: 优化相关,比如提升性能、体验test: 测试用例,包括单元测试、集成测试等chore: 改变构建流程、或者增加依赖库、工具等revert: 回滚到上一个版本 每个commit必须包含以上注释,方便review。 分支规范 master分支:生产分支,用于存放发布到生产环境的代码。该分支只能从其他分支合并,不能直接修改推送。 feature/*分支:功能分支,用来开发新功能。开发完成后合入测试或者生产分支,被合并后删除。 hotfix/*分支:补丁分支,用来紧急修复生产环境的缺陷。开发完成后合入测试或者生产分支,被合并后删除。 环境分支:可选项,如果有要求可以加测试环境的分支,规范同生产分支。 每次开发从master分支中检出,命名为feature/version_no,开发完成后通过pull request/merger request合入环境分支进行测试,最后合入master分支。上线部署后,在master分支增加版本号tag。 Label规范 feature:新功能 enhancement:性能优化或者功能增强 bug:问题修复 backend:后端功能(如果前后端代码都有) frontend:前端功能(如果前后端代码都有) 其他特性label Issues和Pull Request/Merge Request最好都加上Label,方便分类。","categories":[{"name":"开发规范","slug":"开发规范","permalink":"http://sunshanpeng.com/categories/开发规范/"}],"tags":[{"name":"git","slug":"git","permalink":"http://sunshanpeng.com/tags/git/"}]},{"title":"ChaosBlade使用","slug":"ChaosBlade使用","date":"2019-09-27T13:17:37.000Z","updated":"2020-03-01T13:41:16.548Z","comments":true,"path":"2019/09/27/ChaosBlade使用/","link":"","permalink":"http://sunshanpeng.com/2019/09/27/ChaosBlade使用/","excerpt":"","text":"安装二进制1234567#下载wget https://github.com/chaosblade-io/chaosblade/releases/download/v0.3.0/chaosblade-0.3.0.linux-amd64.tar.gz#解压tar -zxvf chaosblade-0.3.0.linux-amd64.tar.gz#运行cd chaosblade-0.3.0/./blade version 命令命令介绍 最主要的命令是创建命令create和创建后的销毁命令destroy。 创建了以后一定要销毁!!! 创建了以后一定要销毁!!! 创建了以后一定要销毁!!! prepare 和 revoke 命令调用混沌实验准备执行器准备或者恢复实验环境。 混沌实验和混沌实验环境准备记录都可以通过status 命令查询。 12345678910111213141516171819202122[root@master4 ~]# ./blade helpAn easy to use and powerful chaos engineering experiment toolkitUsage: blade [command]Available Commands: create Create a chaos engineering experiment destroy Destroy a chaos experiment help Help about any command prepare Prepare to experiment query Query the parameter values required for chaos experiments revoke Undo chaos engineering experiment preparation server Server mode starts, exposes web services status Query preparation stage or experiment status version Print version infoFlags: -d, --debug Set client to DEBUG mode -h, --help help for bladeUse \"blade [command] --help\" for more information about a command. 创建命令 可以创建CPU、内存、磁盘、网络、进程、脚本、Docker、K8S的故障场景。 123456789101112131415161718192021222324252627282930313233343536373839[root@master4 ~]# blade create helpCreate a chaos engineering experimentUsage: blade create [command]Aliases: create, cExamples:create dubbo delay --time 3000 --offset 100 --service com.example.Service --consumerAvailable Commands: cplus c++ experiment cpu Cpu experiment disk Disk experiment docker Execute a docker experiment druid Druid experiment dubbo dubbo experiment http http experiment jedis jedis experiment jvm method k8s Kubernetes experiment mem Mem experiment mysql mysql experiment network Network experiment process Process experiment psql Postgrelsql experiment rocketmq Rocketmq experiment,can make message send or pull delay and exception script Script chaos experiment servlet java servlet experimentFlags: -h, --help help for createGlobal Flags: -d, --debug Set client to DEBUG modeUse \"blade create [command] --help\" for more information about a command. CPUCPU满负载12[root@master4 chaosblade-0.3.0]# ./blade create cpu fullload{\"code\":200,\"success\":true,\"result\":\"4077e008d9a1b63b\"} 返回参数中的result是Uid,后续destroy命令需要用到。 指定CPU负载12[root@master4 chaosblade-0.3.0]# ./blade create cpu fullload --cpu-percent 80{\"code\":200,\"success\":true,\"result\":\"8b465a05ec5f3468\"} 80%负载。 另外也可以使用--cpu-count指定满负载的CPU个数或者--cpu-list指定具体满负载的CPU。 指定故障时间12[root@master4 chaosblade-0.3.0]# ./blade create cpu fullload --cpu-percent 80 --timeout 10{\"code\":200,\"success\":true,\"result\":\"0136822f8f505c61\"} --timeout可以指定该故障的持续时间,单位是秒,指定持续时间后自动终止该故障。 后续的其他故障也都可以指定故障时间。 查看CPU负载 top iostat -c 1 1000 内存指定内存负载12[root@master4 chaosblade-0.3.0]# ./blade c mem load --mem-percent 90{\"code\":200,\"success\":true,\"result\":\"55d737f88301d0a5\"} 查看内存负载 top free -lh 磁盘磁盘容量不足12[root@master4 chaosblade-0.3.0]# ./blade create disk fill --path /home --size 60000{\"code\":200,\"success\":true,\"result\":\"5e16812a5393e610\"} 在指定目录--path填充指定大小--size的数据,单位是M。 查看磁盘容量1234567[root@master4 chaosblade-0.3.0]# df -h /homeFilesystem Size Used Avail Use% Mounted on/dev/mapper/centos-root 58G 5.4G 53G 10% /[root@master4 chaosblade-0.3.0]# df -h /homeFilesystem Size Used Avail Use% Mounted on/dev/mapper/centos-root 58G 53G 5.1G 92% / 磁盘读 IO高负载1./blade create disk burn --read --path /home 磁盘写IO高负载1./blade create disk burn --write --path /home 查看IO负载1iostat -x -t 2 网络网络延时参数12345678--destination-ip string 目标 IP. 支持通过子网掩码来指定一个网段的IP地址, 例如 192.168.1.0/24. 则 192.168.1.0~192.168.1.255 都生效。你也可以指定固定的 IP,如 192.168.1.1 或者 192.168.1.1/32。--exclude-port string 排除掉的端口,可以指定多个,使用逗号分隔或者连接符表示范围,例如 22,8000 或者 8000-8010。 这个参数不能与 --local-port 或者 --remote-port 参数一起使用--interface string 网卡设备,例如 eth0 (必要参数)--local-port string 本地端口,一般是本机暴露服务的端口。可以指定多个,使用逗号分隔或者连接符表示范围,例如 80,8000-8080--offset string 延迟时间上下浮动的值, 单位是毫秒--remote-port string 远程端口,一般是要访问的外部暴露服务的端口。可以指定多个,使用逗号分隔或者连接符表示范围,例如 80,8000-8080--time string 延迟时间,单位是毫秒 (必要参数)--timeout string 设定运行时长,单位是秒,通用参数 指定本地端口延时所有其他服务器访问本地端口延时。 12[root@master4 chaosblade-0.3.0]# ./blade create network delay --time 3000 --offset 1000 --interface eno16780032 --local-port 9100{\"code\":200,\"success\":true,\"result\":\"88626fd1175317ff\"} 在另一台机器验证:telnet xxx.xxx.xxx.xxx 9100 到远程IP端口延时当前机器访问远程IP端口延时,其他机器不受影响。 12[root@master4 chaosblade-0.3.0]# ./blade create network delay --time 3000 --interface eno16780032 --remote-port 8080 --destination-ip 10.22.19.8{\"code\":200,\"success\":true,\"result\":\"fb75e208d0793104\"} 演练机器验证: 12345[root@master4 chaosblade-0.3.0]# curl -o /dev/null -w %{time_namelookup}::%{time_connect}::%{time_starttransfer}::%{time_total}::%{speed_download}\"\\n\" \"http://10.22.19.8:8080/\" % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed100 11266 0 11266 0 0 1251 0 --:--:-- 0:00:09 --:--:-- 28190.000::3.005::6.009::9.002::1251.000 同时间正常机器: 12345[root@master1 ~]# curl -o /dev/null -w %{time_namelookup}::%{time_connect}::%{time_starttransfer}::%{time_total}::%{speed_download}\"\\n\" \"http://10.22.19.8:8080/\" % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed100 11266 0 11266 0 0 4998k 0 --:--:-- --:--:-- --:--:-- 5500k0.000::0.001::0.002::0.002::5118582.000 让一个容器服务超时首先找到该服务的宿主机,再找到容器对应的网卡,然后在服务宿主机上设置该网卡超时。 1./blade create network delay --time 3000 --interface cali4c37fec8d88 --timeout 30 网络丢包参数1234567--destination-ip string 目标 IP. 支持通过子网掩码来指定一个网段的IP地址, 例如 192.168.1.0/24. 则 192.168.1.0~192.168.1.255 都生效。你也可以指定固定的 IP,如 192.168.1.1 或者 192.168.1.1/32。--exclude-port string 排除掉的端口,可以指定多个,使用逗号分隔或者连接符表示范围,例如 22,8000 或者 8000-8010。 这个参数不能与 --local-port 或者 --remote-port 参数一起使用--interface string 网卡设备,例如 eth0 (必要参数)--local-port string 本地端口,一般是本机暴露服务的端口。可以指定多个,使用逗号分隔或者连接符表示范围,例如 80,8000-8080--percent string 丢包百分比,取值在[0, 100]的正整数 (必要参数)--remote-port string 远程端口,一般是要访问的外部暴露服务的端口。可以指定多个,使用逗号分隔或者连接符表示范围,例如 80,8000-8080--timeout string 设定运行时长,单位是秒,通用参数 指定本地端口丢包所有其他服务器访问本地端口延时。 12[root@master4 chaosblade-0.3.0]# ./blade create network loss --percent 70 --interface eno16780032 --local-port 9100{\"code\":200,\"success\":true,\"result\":\"88626fd1175317ff\"} 在另一台机器验证: curl xxx.xxx.xxx.xxx:8080,不使用 telnet 的原因是 telnet 内部有重试机制,影响实验验证。如果将 percent 的值设置为 100,可以使用 telnet 验证 到远程IP端口丢包当前机器访问远程IP端口延时,其他机器不受影响。 12[root@master4 chaosblade-0.3.0]# ./blade create network loss --percent 70 --interface eno16780032 --remote-port 8080 --destination-ip 10.22.19.8{\"code\":200,\"success\":true,\"result\":\"fb75e208d0793104\"} 演练机器验证: 12345[root@master4 chaosblade-0.3.0]# curl -o /dev/null -w %{time_namelookup}::%{time_connect}::%{time_starttransfer}::%{time_total}::%{speed_download}\"\\n\" \"http://10.22.19.8:8080/\" % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed100 11266 0 11266 0 0 1502 0 --:--:-- 0:00:07 --:--:-- 32230.000::7.082::7.499::7.499::1502.000 同时间正常机器: 12345[root@master1 ~]# curl -o /dev/null -w %{time_namelookup}::%{time_connect}::%{time_starttransfer}::%{time_total}::%{speed_download}\"\\n\" \"http://10.22.19.8:8080/\" % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed100 11266 0 11266 0 0 5254k 0 --:--:-- --:--:-- --:--:-- 10.7M0.000::0.000::0.002::0.002::5380133.000 让一个容器服务丢包首先找到该服务的宿主机,再找到容器对应的网卡,然后在服务宿主机上设置该网卡丢包。 1./blade create network loss --percent 70 --interface cali4c37fec8d88 --timeout 30 网络隔离丢包率100%即网络隔离。 DNS解析1234567[root@master4 chaosblade-0.3.0]# ./blade create network dns --domain www.baidu.com --ip 10.0.0.0{\"code\":200,\"success\":true,\"result\":\"5923c8feae34ec52\"}[root@master4 chaosblade-0.3.0]# ping www.baidu.comPING www.baidu.com (10.0.0.0) 56(84) bytes of data.^C--- www.baidu.com ping statistics ---24 packets transmitted, 0 received, 100% packet loss, time 23533ms 销毁实验12[root@master4 chaosblade-0.3.0]# ./blade destroy a6fa3362742e8c6f{\"code\":200,\"success\":true,\"result\":\"command: network loss --help false --interface eno16780032 --debug false --local-port 9100 --percent 70, destroy time: 2019-10-22T17:35:36.557547578+08:00\"} 每个实验要么在创建的时候指定销毁时间--timeout,要么手动销毁,不然会一直执行。 查看实验状态创建只会如果忘记了uid,可以通过status命令查询。 123456789101112131415161718192021222324252627[root@master4 chaosblade-0.3.0]# ./blade status --type create{ \"code\": 200, \"success\": true, \"result\": [ { \"Uid\": \"4cdc5e72e623d449\", \"Command\": \"network\", \"SubCommand\": \"delay\", \"Flag\": \"--debug false --interface cali4c37fec8d88 --time 3000 --timeout 30 --help false\", \"Status\": \"Destroyed\", \"Error\": \"\", \"CreateTime\": \"2019-10-22T16:35:53.572066698+08:00\", \"UpdateTime\": \"2019-10-22T16:36:23.898669337+08:00\" }, { \"Uid\": \"01e7e9b19770834c\", \"Command\": \"network\", \"SubCommand\": \"delay\", \"Flag\": \"--timeout 60 --debug false --interface cali4c37fec8d88 --time 3000 --help false\", \"Status\": \"Destroyed\", \"Error\": \"\", \"CreateTime\": \"2019-10-22T16:42:30.821076685+08:00\", \"UpdateTime\": \"2019-10-22T16:43:30.937855907+08:00\" } ]}","categories":[{"name":"混沌工程","slug":"混沌工程","permalink":"http://sunshanpeng.com/categories/混沌工程/"}],"tags":[{"name":"chaosBlade","slug":"chaosBlade","permalink":"http://sunshanpeng.com/tags/chaosBlade/"}]},{"title":"混沌工程","slug":"混沌工程","date":"2019-09-27T13:17:37.000Z","updated":"2020-03-01T14:08:51.583Z","comments":true,"path":"2019/09/27/混沌工程/","link":"","permalink":"http://sunshanpeng.com/2019/09/27/混沌工程/","excerpt":"","text":"为什么需要混沌工程 减少故障的最好方法就是让故障经常性的发生。通过不断重复失败过程,持续提升系统的容错和弹性能力。 在分布式架构环境下,服务间的依赖日益复杂,没有人能说清单个故障对整个系统的影响,构建一个高可用的分布式系统面临着很大挑战。 分布式系统天生包含大量的交互、依赖点,可以出错的地方数不胜数。硬盘故障、网络不通、流量激增压垮某些组件,这都是每天要面临的常事儿,处理不好就会导致业务停滞,性能低下,或者是其他各种无法预期的异常行为。 在复杂的分布式系统中,人力并不能够阻止这些故障的发生,我们应该致力于在这些异常行为被触发之前,尽可能多地识别出会导致这些异常的,在系统中脆弱的、易出故障的环节。 在线上事故出现之前, 通过观察分布式系统在受控的故障注入测试中的行为变化,提前识别出系统中有哪些弱点以及这些弱点的影响范围。 当我们识别出这些风险,我们就可以有针对性地进行加固、防范, 提高系统可靠性,建立系统抵御失控条件的能力,从而避免故障发生时所带来的严重后果。 相比等待故障发生然后被动式的故障响应流程,混沌工程可以在可控范围或环境下对系统注入各种故障,持续提升分布式系统的容错和弹性能力,从而构建高可用的分布式系统。 怎么样实践混沌工程实践原则完善的监控系统如果没有对系统的可见能力,就无法从实验中得出有效的结论。 if you can't measure it you can't improve it. 尽可能的贴近生产(流量、数据量、配置、架构等)越贴近生产环境演练效果越好。 演练事件真的可能发生 故障类:服务器异常、断网等硬件故障,外部服务不可用,人工误操作等; 非故障类:流量激增、 伸缩事件; 可以分析曾经引起生产系统故障的事件,针对性的排列优先级并实施,避免故障重现。 让故障演练成为常态服务迭代频繁,系统不断变化,需要经常性的演练才能覆盖到系统的变更。 混沌工程不是制造问题,而是揭示问题。 最小化影响范围混沌工程可能导致线上功能不可用,所以在以找出系统弱点为目标的前提下,需要最小化故障影响范围,并且当出现问题时可以迅速恢复,即故障是可控的。 演练方案先测试后生产演练计划先在测试环境验证,故障可控的前提下再到生产环境实施。 先次要后主要先在边缘系统进行故障演练,再对在线业务实施故障演练。 跟踪演练计划记录每次演练的故障对象、故障场景、期望状态、实际状态、影响范围、修复方案。 如果故障对象对该故障场景的实际状态和期望状态一致,则可以认为该故障对象对这种故障是高可用的。 如果故障对象对该故障场景的实际状态和期望状态不一致,那就是一个系统弱点,需要制定方案来修复,修复后继续演练来验证。 如果不能准确的观察结果和深入分析原因并得出修复方案,那就只是在搞破坏,而不是在做混沌工程。 演练目标提升系统的容错和弹性能力,达到小的故障不需要人工介入,大故障人工介入可以快速恢复的目的。 不需要人工介入的故障:做成不定时不定目标不定异常类型的自动化实现,常态化执行。 需要人工介入的故障:告警及时、准确,监控数据清晰、有效,定位问题迅速,预案完善。 阶段一在测试环境制造故障,观察服务状态和监控数据,评估系统可靠性,寻找系统弱点。 建立查找问题-解决问题-验证问题的循环,明确该问题是否需要人工介入。 对于需要人工介入的故障优化监控、告警,制定预案。 阶段二将测试环境演练成功的计划放到生产环境执行验证。明确故障影响范围及解决时效,汇总实验数据。 不需要人工介入的故障是否有对用户造成影响,自动解决的时间多久。 需要人工介入的故障影响范围及问题解决时长。 持续提高团队对故障的检测、响应、处理还有恢复能力。 阶段三建设全链路压测来模拟流量激增现象,建设A/B测试来最小化影响范围,增强混沌工程演练能力。 阶段四建设故障演练平台,自动化演练(启动执行、监控、终止、结果分析),常态化执行。 实施步骤 首先,用系统在正常行为下的一些可测量的输出来定义“稳定状态”。 其次,假设系统在控制组和实验组都会继续保持稳定状态。 然后,在实验组中引入反映真实世界事件的变量,如服务器崩溃、硬盘故障、网络连接断开等。 最后,通过控制组和实验组之间的状态差异来反驳稳定状态的假说。 破坏稳态的难度越大,我们对系统行为的信心就越强。如果发现了一个弱点,那么我们就有了一个改进目标。可以避免在系统规模化之后被放大。 系统弱点 以 A 调用 B,B 调用 C,A 同时也调用 D 举例,B1、B2 是 B 服务的多个实例,依次类推。 受上游服务影响被上游服务的高并发压垮请求限流场景模拟:高并发请求B1。 预期方案:B1设有最大并发量,达到最大并发量后B1服务触发限流,新请求快速失败。 修复方案:添加限流能力。 受下游服务影响下游服务异常导致本服务异常失败重试场景模拟:对B1注入故障,A服务调用到B1时会出现调用失败。 预期方案:A服务的请求路由到B2进行重试。 修复方案:添加失败检测和请求重试能力,B服务的接口需要有幂等性。 实例隔离场景模拟:对B1注入宕机故障,A服务调用到B1时会出现调用失败。 预期方案:给定的失败时间或者给定的失败次数后,自动隔离或下线 B1 实例。 修复方案:添加服务质量检查,下线不可用的服务实例。 下游服务异常拖垮本服务服务降级场景模拟:对B所有实例注入故障,A服务调用B时都失败。 预期方案:调用本地降级方法。 修复方案:准备一个本地的fallback回调,返回一个默认值。 服务熔断场景模拟:对B所有实例注入故障,A服务调用到B时都失败。 预期方案:给定的失败时间或者给定的失败次数后,A服务触发熔断,快速失败。 修复方案:添加熔断能力,下游服务不可用时能立即熔断,快速失败。 受中间件影响中间件异常导致的程序逻辑出错中间件异常导致的服务不可用受第三方服务商影响第三方服务不可用导致本服务功能不可用场景模拟:A短信供应商不可用。 预期方案:切换B短信供应商。 修复方案:供应商备份。 第三方服务异常拖垮本服务场景模拟:调用OSS服务响应慢。 预期方案:熔断第三方服务,暂不提供该服务。 修复方案:供应商备份。 故障场景CPU异常 CPU高负载 内存异常 内存高负载 磁盘异常 磁盘高负载 磁盘空间不足 部分数据丢失 全部数据丢失 不可读 不可写 网络异常 瞬断 延时 丢包 隔离 外部服务不可达 进程异常服务进程 Tomcat线程池打满 异常关闭 重启 内存溢出 系统进程 系统时间不同步 OOM kill 中间件Redis 节点故障 重启 批量删除 数据丢失 响应慢 MySQL CPU升高(*2019-06-01) IO升高 节点故障 数据丢失 响应慢 大量慢SQL 连接池占满 死锁 不可写 不可读 数据同步延迟 主备延迟 RocketMQ/Kafka NameServer故障 Broker故障 数据丢失 消息推送延迟 大量消息挤压 Apollo ConfigService故障 AdminService故障 Portal故障 数据丢失 XXL-Job 调度器故障 执行器故障 ElasticSearch 节点故障 内存溢出 写请求飙高 读请求飙高 DNS 解析超时(*2019-10-01) 解析错误 代码异常 空指针 异常操作 删除数据 强杀进程 服务器异常 宕机 重启 中木马 修改系统配置 第三方服务不可用 响应慢 访问不了 数据中心故障 断电 断网 演练工具chaosbladeGitHub:https://github.com/chaosblade-io/chaosblade 文档:https://chaosblade-io.gitbook.io/chaosblade-help-zh-cn/blade chaosmonkeyGitHub:https://github.com/Netflix/chaosmonkey pumbaGitHub:https://github.com/alexei-led/pumba kube-monkeyGitHub:https://github.com/asobti/kube-monkey 参考 https://www.infoq.cn/article/jjp0c2bR4*Ulld0wb88r https://tech.youzan.com/chaosmonkey/ https://www.cnblogs.com/tianqing/p/10499611.html","categories":[{"name":"混沌工程","slug":"混沌工程","permalink":"http://sunshanpeng.com/categories/混沌工程/"}],"tags":[{"name":"chaosBlade","slug":"chaosBlade","permalink":"http://sunshanpeng.com/tags/chaosBlade/"}]},{"title":"在k8s中使用eureka的几种姿势","slug":"在k8s中使用eureka的几种姿势","date":"2019-09-17T11:29:37.000Z","updated":"2019-10-08T00:27:46.314Z","comments":true,"path":"2019/09/17/在k8s中使用eureka的几种姿势/","link":"","permalink":"http://sunshanpeng.com/2019/09/17/在k8s中使用eureka的几种姿势/","excerpt":"","text":"前言在Kubernetes中,service通过label关联pod,使用service的clusterIp即可访问到对应pod。 如果觉得clusterIp不好记,可以用coredns将service name解析成service的clusterIp,通过访问service name来访问对应的pod。 通过coredns+service,可以在单个kubernetes集群中无需其他依赖即可实现服务发现。 但由于某些其他原因我们需要使用额外的注册中心eureka,本文列举了几种注册方式和注意事项。 使用Pod IP注册因为Pod的IP是跟随生命周期的,重新发布以后IP就变更了。 但是eureka的缓存机制相当长,重新发布后服务消费者很长一段时间会访问之前的Pod IP。 要解决这个问题有以下几个优化点: 优雅停止Pod如果Pod被强杀掉,就不能立即从Eureka Server下线,直到Eureka Server的EvictionTask 来定时清理过期的客户端,默认60秒清理一次,清理掉之前还能被其他客户端发现并调用。 kubernetes提供了生命周期回调函数,会在容器被终止前调用preStop,执行停止Java进程的命令 1234lifecycle: preStop: exec: command: [\"/bin/bash\", \"-c\", \"ps -ef | grep jav[a] | awk 'NR==1{print $2}' | xargs kill\"] 因为遵循一个容器只运行一个进程的原则,所以可以直接查找Java的pid然后kill,也可以用pidof命令查找Java的pid。 缩短服务发现时间Eureka客户端默认30秒的缓存时间,ribbon默认30秒的缓存时间,加上Eureka Server的三级缓存机制readOnlyCache也有30秒的缓存时间,在开启Eureka Server的自我保护模式时,即使客户端从Eureka Server下线了,极端情况下也有可能在90秒以后才能在其他客户端的缓存中清除。 可以通过设置deployment中的环境变量修改默认的缓存时间: eureka client缓存(单位:秒) 1eureka.client.registryFetchIntervalSeconds = 10 ribbon缓存(单位:秒) 1ribbon.ServerListRefreshInterval = 10 关闭Eureka Server的readOnlyCache(可选)因为readWriteCacheMap用的LoadingCache有读写锁,使用readOnlyCacheMap可以增加吞吐量,中小集群可以关闭readOnlyCacheMap。 关闭readWriteCacheMap: 1eureka.server.use-read-only-response-cache = false 也可以缩短readOnlyCache的刷新时间: 1eureka.server.response-cache-update-interval-ms = 10 强制下线即使优雅停止Pod、缩短客户端和Eureka Server的缓存时间,但是Eureka客户端还是有几率请求到已下线的Pod。 Eureka提供了强制下线的接口,可以先从Eureka Server强制下线,等待缓存时间过期再停止Pod。 client强制下线接口: 1POST /pause 1POST /service-registry 比如: 12curl -X POST \"http://localhost:$MANAGEMENT_PORT/pause\"curl -X POST \"http://localhost:$MANAGEMENT_PORT/service-registry/instance-status\" -H \"Content-Type: text/plain; charset=utf-8\" -d \"OUT_OF_SERVICE\" Eureka Server强制下线接口: 1PUT /eureka/apps/${appId}/${ip:port}/status?value=OUT_OF_SERVICE 比如: 1curl -X PUT http://eureka:8080/eureka/apps/EUREKA-1/192.168.0.10:8080/status?value=OUT_OF_SERVICE 关于强制下线可以看一下参考里的文章。 具体配置实践配置为Eureka客户端缓存10秒,ribbon缓存10秒,不关闭readOnlyCache(缓存30秒),加起来缓存时间50秒。 将停止脚本放在指定目录,并在preStop调用。 脚本逻辑:1.强制下线当前实例;2.暂停总缓存时间;3.停止服务。 配置环境变量:12345678910- name: eureka.client.registryFetchIntervalSeconds # Eureka客户端缓存时间 value: \"10\"- name: ribbon.ServerListRefreshInterval #ribbon缓存时间 value: \"10\"- name: MANAGEMENT_PORT #management.port的端口 value: \"${management.port}\"- name: SLEEP_SECOND #总缓存时间 value: \"50\"- name: management.endpoints.web.exposure.include #启用并暴露service-registry value: service-registry preStop1234lifecycle: preStop: exec: command: [\"/bin/sh\", \"/app/script/stop.sh\"] stop.sh1234567891011121314151617181920212223242526# 如果没有管理端口直接下线if [ -z $MANAGEMENT_PORT ];then echo \"MANAGEMENT_PORT is empty, kill java\" ps -ef | grep jav[a] | awk 'NR==1{print $2}' | xargs kill exit 1fi# 默认暂停时间if [ -z $SLEEP_SECOND ];then echo \"SLEEP_SECOND is empty, set 50\" SLEEP_SECOND=50fi# 从EUREKA下线echo \"curl pause $MANAGEMENT_PORT\"# curl -X POST \"http://localhost:$MANAGEMENT_PORT/pause\"curl -X \"POST\" \"http://localhost:$MANAGEMENT_PORT/service-registry/instance-status\" -H \"Content-Type: text/plain; charset=utf-8\" -d \"OUT_OF_SERVICE\"# 等待SLEEP_SECOND秒,SLEEP_SECOND = 客户端缓存时间(eureka+ribbon)+ readOnlyCache时间echo \"\"echo \"sleep $SLEEP_SECOND s\"sleep $SLEEP_SECOND# 停止服务echo \"kill java\"ps -ef | grep jav[a] | awk 'NR==1{print $2}' | xargs kill 使用Service的Cluster IP注册Service的Cluster IP是一个虚拟IP,会经kube-proxy把流量负载均衡到后端endpoint。 而且Service的Cluster IP是固定的,除非主动删除后重建,不然可以一直固定。 使用Cluster IP等于使用了Kubernetes的服务发现,当Pod不可用时就会从service的endpoint列表中移除,流量就不会再负载均衡到该Pod。 配置方式(properties): 12eureka.instance.ipAddress = ${Cluster IP}eureka.instance.nonSecurePort = ${port} #service的port 使用Service注册虽然可以自动过滤下线的服务,但是ribbon内部使用keepalive进行会话保持,因为是客户端负载均衡所以A服务的A1实例所有请求都会经过service转发到同一个B服务的B1实例,从而出现负载不均衡的情况 使用Service的NodePort注册默认情况下Pod的IP只能在本集群内的Node上访问,非本集群的服务器访问不了。 可以通过写路由表或者BGP来发布Pod路由。 NodePort是Pod对外提供访问的一种方式,使用Host IP+NodePort可以简单实现跨集群及虚拟机之间的服务互相访问。 配置方式(环境变量): 123456- name: eureka.instance.ipAddress valueFrom: fieldRef: fieldPath: status.hostIP- name: eureka.instance.nonSecurePort value: \"${NodePort}\" NodePort方式本质也是Service,所以也存在负载均衡的情况。 另外管理NodePort也是件麻烦事。 参考http://www.itmuch.com/spring-cloud-sum/how-to-unregister-service-in-eureka/","categories":[{"name":"容器","slug":"容器","permalink":"http://sunshanpeng.com/categories/容器/"}],"tags":[{"name":"eureka","slug":"eureka","permalink":"http://sunshanpeng.com/tags/eureka/"},{"name":"k8s","slug":"k8s","permalink":"http://sunshanpeng.com/tags/k8s/"}]},{"title":"Eureka服务注册发现","slug":"Eureka服务注册发现","date":"2019-09-17T11:19:37.000Z","updated":"2019-10-08T00:27:30.802Z","comments":true,"path":"2019/09/17/Eureka服务注册发现/","link":"","permalink":"http://sunshanpeng.com/2019/09/17/Eureka服务注册发现/","excerpt":"","text":"注册发现流程通常服务注册发现都分为服务提供者(provider)、服务消费者(consumer)、注册中心(register)三个部分。 一个服务可以是服务提供者可以是服务消费者,也可以即是提供者也是消费者 其中provider和consumer作为客户端client,注册中心作为服务端server,都可以同时存在多个实例。 服务提供者把自身的实例信息(比如IP、PORT、状态等)在注册中心做登记,服务消费者通过注册中心获取服务提供者的信息然后发起调用。 常用的注册中心有Zookeeper、ETCD、Eureka、Consul、Nacos。 服务注册 首先我们会在client配置eureka server的地址,如: 12eureka.client.service-url.defaultZone=http://localhost:8081/eureka/,http://localhost:8082/eureka/,http://localhost:8083/eureka/ 客户端会尝试向配置的eureka server发起注册请求。 入口:com.netflix.discovery.DiscoveryClient#register 1234567891011121314boolean register() throws Throwable { logger.info(PREFIX + appPathIdentifier + \": registering service...\"); EurekaHttpResponse<Void> httpResponse; try { httpResponse = eurekaTransport.registrationClient.register(instanceInfo); } catch (Exception e) { logger.warn(\"{} - registration failed {}\", PREFIX + appPathIdentifier, e.getMessage(), e); throw e; } if (logger.isInfoEnabled()) { logger.info(\"{} - registration status: {}\", PREFIX + appPathIdentifier, httpResponse.getStatusCode()); } return httpResponse.getStatusCode() == 204; } 核心代码:com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient#execute 12345678910111213141516171819202122232425262728293031323334353637383940414243444546protected <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor) { List<EurekaEndpoint> candidateHosts = null; int endpointIdx = 0; for (int retry = 0; retry < numberOfRetries; retry++) { //currentHttpClient是上面配置的三个eureka server中的其中一个 //如果eureka server能用,就会一直请求同一个eureka server EurekaHttpClient currentHttpClient = delegate.get(); EurekaEndpoint currentEndpoint = null; if (currentHttpClient == null) { if (candidateHosts == null) { //获取eureka server列表 candidateHosts = getHostCandidates(); if (candidateHosts.isEmpty()) { throw new TransportException(\"There is no known eureka server; cluster server list is empty\"); } } if (endpointIdx >= candidateHosts.size()) { throw new TransportException(\"Cannot execute request on any known server\"); } currentEndpoint = candidateHosts.get(endpointIdx++); currentHttpClient = clientFactory.newClient(currentEndpoint); } try { EurekaHttpResponse<R> response = requestExecutor.execute(currentHttpClient); if (serverStatusEvaluator.accept(response.getStatusCode(), requestExecutor.getRequestType())) { delegate.set(currentHttpClient); if (retry > 0) { logger.info(\"Request execution succeeded on retry #{}\", retry); } return response; } logger.warn(\"Request execution failure with status code {}; retrying on another server if available\", response.getStatusCode()); } catch (Exception e) { logger.warn(\"Request execution failed with message: {}\", e.getMessage()); // just log message as the underlying client should log the stacktrace } //如果请求则把eureka server置为空,下次重试时会换一个eureka server delegate.compareAndSet(currentHttpClient, null); if (currentEndpoint != null) { quarantineSet.add(currentEndpoint); } } throw new TransportException(\"Retry limit reached; giving up on completing the request\"); } Eureka server获取我们实际注册的eureka server和配置的eureka server顺序是不一样的,比如我们配置的顺序是http://localhost:8081/eureka,http://localhost:8082/eureka,http://localhost:8083/eureka,原先以为会先注册到http://localhost:8081/eureka,但实际是先尝试注册到http://localhost:8083/eureka。 因为获取列表的时候做了一次randomize,如果是三个server实例,会交互1和3的位置: 123456789101112131415public static <T extends EurekaEndpoint> List<T> randomize(List<T> list) { List<T> randomList = new ArrayList<>(list); if (randomList.size() < 2) { return randomList; } Random random = new Random(LOCAL_IPV4_ADDRESS.hashCode()); int last = randomList.size() - 1; for (int i = 0; i < last; i++) { int pos = random.nextInt(randomList.size() - i); if (pos != i) { Collections.swap(randomList, i, pos); } } return randomList; } 客户端发起注册请求源码:com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#register 12345678910111213141516171819202122public EurekaHttpResponse<Void> register(InstanceInfo info) { String urlPath = \"apps/\" + info.getAppName(); ClientResponse response = null; try { Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder(); addExtraHeaders(resourceBuilder); response = resourceBuilder .header(\"Accept-Encoding\", \"gzip\") .type(MediaType.APPLICATION_JSON_TYPE) .accept(MediaType.APPLICATION_JSON) .post(ClientResponse.class, info); return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build(); } finally { if (logger.isDebugEnabled()) { logger.debug(\"Jersey HTTP POST {}/{} with instance {}; statusCode={}\", serviceUrl, urlPath, info.getId(), response == null ? \"N/A\" : response.getStatus()); } if (response != null) { response.close(); } } } 服务端接受注册请求入口:com.netflix.eureka.resources.ApplicationResource#addInstance 1234567891011121314151617181920212223242526272829303132333435363738394041424344@POST @Consumes({\"application/json\", \"application/xml\"}) public Response addInstance(InstanceInfo info, @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) { logger.debug(\"Registering instance {} (replication={})\", info.getId(), isReplication); // validate that the instanceinfo contains all the necessary required fields if (isBlank(info.getId())) { return Response.status(400).entity(\"Missing instanceId\").build(); } else if (isBlank(info.getHostName())) { return Response.status(400).entity(\"Missing hostname\").build(); } else if (isBlank(info.getAppName())) { return Response.status(400).entity(\"Missing appName\").build(); } else if (!appName.equals(info.getAppName())) { return Response.status(400).entity(\"Mismatched appName, expecting \" + appName + \" but was \" + info.getAppName()).build(); } else if (info.getDataCenterInfo() == null) { return Response.status(400).entity(\"Missing dataCenterInfo\").build(); } else if (info.getDataCenterInfo().getName() == null) { return Response.status(400).entity(\"Missing dataCenterInfo Name\").build(); } // handle cases where clients may be registering with bad DataCenterInfo with missing data DataCenterInfo dataCenterInfo = info.getDataCenterInfo(); if (dataCenterInfo instanceof UniqueIdentifier) { String dataCenterInfoId = ((UniqueIdentifier) dataCenterInfo).getId(); if (isBlank(dataCenterInfoId)) { boolean experimental = \"true\".equalsIgnoreCase(serverConfig.getExperimental(\"registration.validation.dataCenterInfoId\")); if (experimental) { String entity = \"DataCenterInfo of type \" + dataCenterInfo.getClass() + \" must contain a valid id\"; return Response.status(400).entity(entity).build(); } else if (dataCenterInfo instanceof AmazonInfo) { AmazonInfo amazonInfo = (AmazonInfo) dataCenterInfo; String effectiveId = amazonInfo.get(AmazonInfo.MetaDataKey.instanceId); if (effectiveId == null) { amazonInfo.getMetadata().put(AmazonInfo.MetaDataKey.instanceId.getName(), info.getId()); } } else { logger.warn(\"Registering DataCenterInfo of type {} without an appropriate id\", dataCenterInfo.getClass()); } } } registry.register(info, \"true\".equals(isReplication)); return Response.status(204).build(); // 204 to be backwards compatible } 核心代码:com.netflix.eureka.registry.AbstractInstanceRegistry 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) { try { // 上只读锁 read.lock(); // 从本地MAP里面获取当前实例的信息。 Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName()); // 增加注册次数到监控信息里面去。 REGISTER.increment(isReplication); if (gMap == null) { // 如果第一次进来,那么gMap为空,则创建一个ConcurrentHashMap放入到registry里面去 final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>(); // putIfAbsent方法主要是在向ConcurrentHashMap中添加键—值对的时候,它会先判断该键值对是否已经存在。 // 如果不存在(新的entry),那么会向map中添加该键值对,并返回null。 // 如果已经存在,那么不会覆盖已有的值,直接返回已经存在的值。 gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap); if (gMap == null) { // 表明map中确实不存在,则设置gMap为最新创建的那个 gMap = gNewMap; } } // 从MAP中查询已经存在的Lease信息 (比如第二次来) Lease<InstanceInfo> existingLease = gMap.get(registrant.getId()); // 当Lease的对象不为空时。 if (existingLease != null && (existingLease.getHolder() != null)) { // 当instance已经存在是,和客户端的instance的信息做比较,时间最新的那个,为有效instance信息 Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp(); // server Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp(); // client logger.debug(\"Existing lease found (existing={}, provided={}\", existingLastDirtyTimestamp, registrationLastDirtyTimestamp); if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) { logger.warn(\"There is an existing lease and the existing lease's dirty timestamp {} is greater\" + \" than the one that is being registered {}\", existingLastDirtyTimestamp, registrationLastDirtyTimestamp); logger.warn(\"Using the existing instanceInfo instead of the new instanceInfo as the registrant\"); registrant = existingLease.getHolder(); } } else { // 这里只有当existinglease不存在时,才会进来。 像那种恢复心跳,信息过期的,都不会进入这里。 // Eureka-Server的自我保护机制做的操作,为每分钟最大续约数+2 ,同时重新计算每分钟最小续约数 synchronized (lock) { if (this.expectedNumberOfRenewsPerMin > 0) { // Since the client wants to cancel it, reduce the threshold // (1 // for 30 seconds, 2 for a minute) this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2; this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold()); } } logger.debug(\"No previous lease information found; it is new registration\"); } // 构建一个最新的Lease信息 Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration); if (existingLease != null) { // 当原来存在Lease的信息时,设置他的serviceUpTimestamp, 保证服务开启的时间一直是第一次的那个 lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp()); } // 放入本地Map中 gMap.put(registrant.getId(), lease); // 添加到最近的注册队列里面去,以时间戳作为Key, 名称作为value,主要是为了运维界面的统计数据。 synchronized (recentRegisteredQueue) { recentRegisteredQueue.add(new Pair<Long, String>( System.currentTimeMillis(), registrant.getAppName() + \"(\" + registrant.getId() + \")\")); } // This is where the initial state transfer of overridden status happens // 分析instanceStatus if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) { logger.debug(\"Found overridden status {} for instance {}. Checking to see if needs to be add to the \" + \"overrides\", registrant.getOverriddenStatus(), registrant.getId()); if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) { logger.info(\"Not found overridden id {} and hence adding it\", registrant.getId()); overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus()); } } InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId()); if (overriddenStatusFromMap != null) { logger.info(\"Storing overridden status {} from map\", overriddenStatusFromMap); registrant.setOverriddenStatus(overriddenStatusFromMap); } // Set the status based on the overridden status rules InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication); registrant.setStatusWithoutDirty(overriddenInstanceStatus); // If the lease is registered with UP status, set lease service up timestamp // 得到instanceStatus,判断是否是UP状态, if (InstanceStatus.UP.equals(registrant.getStatus())) { lease.serviceUp(); } // 设置注册类型为添加 registrant.setActionType(ActionType.ADDED); // 租约变更记录队列,记录了实例的每次变化, 用于注册信息的增量获取、 recentlyChangedQueue.add(new RecentlyChangedItem(lease)); registrant.setLastUpdatedTimestamp(); // 清理缓存 ,传入的参数为key invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress()); logger.info(\"Registered instance {}/{} with status {} (replication={})\", registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication); } finally { read.unlock(); }} 到这里,服务注册就算完啦。 服务发现Eureka server缓存eureka server默认情况下有三个地方存储了服务信息,分别是com.netflix.eureka.registry.AbstractInstanceRegistry#registry、com.netflix.eureka.registry.ResponseCacheImpl#readWriteCacheMap、com.netflix.eureka.registry.ResponseCacheImpl#readOnlyCacheMap。 registry数据结构ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>,外面Map的key是AppName,就是注册在eureka上的服务;里面Map的key是IP,value是服务的具体实例。 所有的服务都是注册到registry上面的,eureka server提供的UI界面查询到的服务信息也是直接从registry中获取的。 入口:org.springframework.cloud.netflix.eureka.server.EurekaController#status 核心代码:com.netflix.eureka.registry.AbstractInstanceRegistry#getApplicationsFromMultipleRegions 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162public Applications getApplicationsFromMultipleRegions(String[] remoteRegions) { boolean includeRemoteRegion = null != remoteRegions && remoteRegions.length != 0; logger.debug(\"Fetching applications registry with remote regions: {}, Regions argument {}\", includeRemoteRegion, Arrays.toString(remoteRegions)); if (includeRemoteRegion) { GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS.increment(); } else { GET_ALL_CACHE_MISS.increment(); } Applications apps = new Applications(); apps.setVersion(1L); for (Entry<String, Map<String, Lease<InstanceInfo>>> entry : registry.entrySet()) { Application app = null; if (entry.getValue() != null) { for (Entry<String, Lease<InstanceInfo>> stringLeaseEntry : entry.getValue().entrySet()) { Lease<InstanceInfo> lease = stringLeaseEntry.getValue(); if (app == null) { app = new Application(lease.getHolder().getAppName()); } app.addInstance(decorateInstanceInfo(lease)); } } if (app != null) { apps.addApplication(app); } } if (includeRemoteRegion) { for (String remoteRegion : remoteRegions) { RemoteRegionRegistry remoteRegistry = regionNameVSRemoteRegistry.get(remoteRegion); if (null != remoteRegistry) { Applications remoteApps = remoteRegistry.getApplications(); for (Application application : remoteApps.getRegisteredApplications()) { if (shouldFetchFromRemoteRegistry(application.getName(), remoteRegion)) { logger.info(\"Application {} fetched from the remote region {}\", application.getName(), remoteRegion); Application appInstanceTillNow = apps.getRegisteredApplications(application.getName()); if (appInstanceTillNow == null) { appInstanceTillNow = new Application(application.getName()); apps.addApplication(appInstanceTillNow); } for (InstanceInfo instanceInfo : application.getInstances()) { appInstanceTillNow.addInstance(instanceInfo); } } else { logger.debug(\"Application {} not fetched from the remote region {} as there exists a \" + \"whitelist and this app is not in the whitelist.\", application.getName(), remoteRegion); } } } else { logger.warn(\"No remote registry available for the remote region {}\", remoteRegion); } } } apps.setAppsHashCode(apps.getReconcileHashCode()); return apps; } readWriteCacheMap数据结构LoadingCache<Key, Value>。 LoadingCache是谷歌guava提供的一个缓存实现,核心代码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081this.readWriteCacheMap = CacheBuilder.newBuilder().initialCapacity(1000) //缓存失效时间,可以配置,默认180秒 .expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS) .removalListener(new RemovalListener<Key, Value>() { @Override public void onRemoval(RemovalNotification<Key, Value> notification) { Key removedKey = notification.getKey(); if (removedKey.hasRegions()) { Key cloneWithNoRegions = removedKey.cloneWithoutRegions(); regionSpecificKeys.remove(cloneWithNoRegions, removedKey); } } }) //如果key对应的value为空,调用该方法获取value并放到缓存中,该方法是线程安全的 //这里是去registry中获取 .build(new CacheLoader<Key, Value>() { @Override public Value load(Key key) throws Exception { if (key.hasRegions()) { Key cloneWithNoRegions = key.cloneWithoutRegions(); regionSpecificKeys.put(cloneWithNoRegions, key); } Value value = generatePayload(key); return value; } }); /* * Generate pay load for the given key. */ private Value generatePayload(Key key) { Stopwatch tracer = null; try { String payload; switch (key.getEntityType()) { case Application: boolean isRemoteRegionRequested = key.hasRegions(); if (ALL_APPS.equals(key.getName())) { if (isRemoteRegionRequested) { tracer = serializeAllAppsWithRemoteRegionTimer.start(); payload = getPayLoad(key, registry.getApplicationsFromMultipleRegions(key.getRegions())); } else { tracer = serializeAllAppsTimer.start(); payload = getPayLoad(key, registry.getApplications()); } } else if (ALL_APPS_DELTA.equals(key.getName())) { if (isRemoteRegionRequested) { tracer = serializeDeltaAppsWithRemoteRegionTimer.start(); versionDeltaWithRegions.incrementAndGet(); versionDeltaWithRegionsLegacy.incrementAndGet(); payload = getPayLoad(key, registry.getApplicationDeltasFromMultipleRegions(key.getRegions())); } else { tracer = serializeDeltaAppsTimer.start(); versionDelta.incrementAndGet(); versionDeltaLegacy.incrementAndGet(); payload = getPayLoad(key, registry.getApplicationDeltas()); } } else { tracer = serializeOneApptimer.start(); payload = getPayLoad(key, registry.getApplication(key.getName())); } break; case VIP: case SVIP: tracer = serializeViptimer.start(); payload = getPayLoad(key, getApplicationsForVip(key, registry)); break; default: logger.error(\"Unidentified entity type: \" + key.getEntityType() + \" found in the cache key.\"); payload = \"\"; break; } return new Value(payload); } finally { if (tracer != null) { tracer.stop(); } } } 缓存除了到失效时间后会失效以外,每次对registry进行写操作的时候也会调用com.netflix.eureka.registry.ResponseCacheImpl#invalidate删除缓存。 123456789101112131415161718192021222324252627282930313233343536public void invalidate(String appName, @Nullable String vipAddress, @Nullable String secureVipAddress) { for (Key.KeyType type : Key.KeyType.values()) { for (Version v : Version.values()) { invalidate( new Key(Key.EntityType.Application, appName, type, v, EurekaAccept.full), new Key(Key.EntityType.Application, appName, type, v, EurekaAccept.compact), new Key(Key.EntityType.Application, ALL_APPS, type, v, EurekaAccept.full), new Key(Key.EntityType.Application, ALL_APPS, type, v, EurekaAccept.compact), new Key(Key.EntityType.Application, ALL_APPS_DELTA, type, v, EurekaAccept.full), new Key(Key.EntityType.Application, ALL_APPS_DELTA, type, v, EurekaAccept.compact) ); if (null != vipAddress) { invalidate(new Key(Key.EntityType.VIP, vipAddress, type, v, EurekaAccept.full)); } if (null != secureVipAddress) { invalidate(new Key(Key.EntityType.SVIP, secureVipAddress, type, v, EurekaAccept.full)); } } } } public void invalidate(Key... keys) { for (Key key : keys) { logger.debug(\"Invalidating the response cache key : {} {} {} {}, {}\", key.getEntityType(), key.getName(), key.getVersion(), key.getType(), key.getEurekaAccept()); readWriteCacheMap.invalidate(key); Collection<Key> keysWithRegions = regionSpecificKeys.get(key); if (null != keysWithRegions && !keysWithRegions.isEmpty()) { for (Key keysWithRegion : keysWithRegions) { logger.debug(\"Invalidating the response cache key : {} {} {} {} {}\", key.getEntityType(), key.getName(), key.getVersion(), key.getType(), key.getEurekaAccept()); readWriteCacheMap.invalidate(keysWithRegion); } } } } 由此可以看出,readWriteCacheMap的数据和registry的数据基本是一致的。 readOnlyCacheMap数据结构ConcurrentMap<Key, Value>。 可以用useReadOnlyResponseCache配置控制是否启用readOnlyCacheMap,默认为true启用readOnlyCacheMap。 初始化com.netflix.eureka.registry.ResponseCacheImpl的时候会判断useReadOnlyResponseCache,启用的话会开一个更新readOnlyCacheMap的定时任务: 123456if (shouldUseReadOnlyResponseCache) { timer.schedule(getCacheUpdateTask(), new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs) + responseCacheUpdateIntervalMs), responseCacheUpdateIntervalMs); } responseCacheUpdateIntervalMs是readOnlyCacheMap从readWriteCacheMap同步缓存的时间,默认30s。 核心代码:com.netflix.eureka.registry.ResponseCacheImpl#getCacheUpdateTask 1234567891011121314151617181920212223242526private TimerTask getCacheUpdateTask() { return new TimerTask() { @Override public void run() { logger.debug(\"Updating the client cache from response cache\"); for (Key key : readOnlyCacheMap.keySet()) { if (logger.isDebugEnabled()) { Object[] args = {key.getEntityType(), key.getName(), key.getVersion(), key.getType()}; logger.debug(\"Updating the client cache from response cache for key : {} {} {} {}\", args); } try { CurrentRequestVersion.set(key.getVersion()); Value cacheValue = readWriteCacheMap.get(key); Value currentCacheValue = readOnlyCacheMap.get(key); //从readWriteCacheMap取出对应Key的value, //如果不一样以readWriteCacheMap的为准 if (cacheValue != currentCacheValue) { readOnlyCacheMap.put(key, cacheValue); } } catch (Throwable th) { logger.error(\"Error while updating the client cache from response cache\", th); } } } }; } 由此可以看出,默认配置下从readOnlyCacheMap获取的实例信息,可能有30S的缓存时间数据是不一致的。 服务消费者缓存服务消费者从eureka server获取实例信息并缓存在eureka client,如果用ribbon调用的话也会在ribbon内缓存一份实例信息。 Eureka Client缓存Eureka Client的缓存放在com.netflix.discovery.shared.Applications#appNameApplicationMap中,appNameApplicationMap的数据结构是Map<String, Application>,key是appName。 应用启动初始化com.netflix.discovery.DiscoveryClient的时候会全量拉取一次服务信息,后面增量获取。 com.netflix.discovery.DiscoveryClient#initScheduledTasks 12345678910111213141516if (clientConfig.shouldFetchRegistry()) { // registry cache refresh timer int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds(); int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound(); scheduler.schedule( new TimedSupervisorTask( \"cacheRefresh\", scheduler, cacheRefreshExecutor, registryFetchIntervalSeconds, TimeUnit.SECONDS, expBackOffBound, new CacheRefreshThread() ), registryFetchIntervalSeconds, TimeUnit.SECONDS); } registryFetchIntervalSeconds是eureka client从eureka server同步实例信息的时间,默认30S。 核心代码:com.netflix.discovery.DiscoveryClient#fetchRegistry 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748private boolean fetchRegistry(boolean forceFullRegistryFetch) { Stopwatch tracer = FETCH_REGISTRY_TIMER.start(); try { // If the delta is disabled or if it is the first time, get all // applications Applications applications = getApplications(); if (clientConfig.shouldDisableDelta() || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress())) || forceFullRegistryFetch || (applications == null) || (applications.getRegisteredApplications().size() == 0) || (applications.getVersion() == -1)) //Client application does not have latest library supporting delta { logger.info(\"Disable delta property : {}\", clientConfig.shouldDisableDelta()); logger.info(\"Single vip registry refresh property : {}\", clientConfig.getRegistryRefreshSingleVipAddress()); logger.info(\"Force full registry fetch : {}\", forceFullRegistryFetch); logger.info(\"Application is null : {}\", (applications == null)); logger.info(\"Registered Applications size is zero : {}\", (applications.getRegisteredApplications().size() == 0)); logger.info(\"Application version is -1: {}\", (applications.getVersion() == -1)); //全量获取 getAndStoreFullRegistry(); } else { //增量获取 getAndUpdateDelta(applications); } applications.setAppsHashCode(applications.getReconcileHashCode()); logTotalInstances(); } catch (Throwable e) { logger.error(PREFIX + appPathIdentifier + \" - was unable to refresh its cache! status = \" + e.getMessage(), e); return false; } finally { if (tracer != null) { tracer.stop(); } } // Notify about cache refresh before updating the instance remote status onCacheRefreshed(); // Update remote status based on refreshed data held in the cache updateInstanceRemoteStatus(); // registry was fetched successfully, so return true return true;} Ribbon缓存Ribbon的缓存放在com.netflix.loadbalancer.BaseLoadBalancer#allServerList中,应用启动的时候调用com.netflix.loadbalancer.PollingServerListUpdater#start启动一个更新ribbon缓存的定时任务。 123456789101112131415161718192021222324252627282930public synchronized void start(final UpdateAction updateAction) { if (isActive.compareAndSet(false, true)) { final Runnable wrapperRunnable = new Runnable() { @Override public void run() { if (!isActive.get()) { if (scheduledFuture != null) { scheduledFuture.cancel(true); } return; } try { updateAction.doUpdate(); lastUpdated = System.currentTimeMillis(); } catch (Exception e) { logger.warn(\"Failed one update cycle\", e); } } }; scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay( wrapperRunnable, initialDelayMs, refreshIntervalMs, TimeUnit.MILLISECONDS ); } else { logger.info(\"Already active, no-op\"); } } refreshIntervalMs是ribbon缓存的刷新时间,可以配置单个应用的刷新时间aim-ms.ribbon.ServerListRefreshInterval,也可以配置全局的刷新时间ribbon.ServerListRefreshInterval,都不配置的话默认是30S。 获取配置代码:com.netflix.loadbalancer.PollingServerListUpdater 123456public PollingServerListUpdater(IClientConfig clientConfig) { this(LISTOFSERVERS_CACHE_UPDATE_DELAY, getRefreshIntervalMs(clientConfig)); } private static long getRefreshIntervalMs(IClientConfig clientConfig) { return clientConfig.get(CommonClientConfigKey.ServerListRefreshInterval, LISTOFSERVERS_CACHE_REPEAT_INTERVAL); } com.netflix.client.config.DefaultClientConfigImpl 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657public <T> T get(IClientConfigKey<T> key, T defaultValue) { T value = get(key); if (value == null) { value = defaultValue; } return value; } public <T> T get(IClientConfigKey<T> key) { Object obj = getProperty(key.key()); if (obj == null) { return null; } Class<T> type = key.type(); if (type.isInstance(obj)) { return type.cast(obj); } else { if (obj instanceof String) { String stringValue = (String) obj; if (Integer.class.equals(type)) { return (T) Integer.valueOf(stringValue); } else if (Boolean.class.equals(type)) { return (T) Boolean.valueOf(stringValue); } else if (Float.class.equals(type)) { return (T) Float.valueOf(stringValue); } else if (Long.class.equals(type)) { return (T) Long.valueOf(stringValue); } else if (Double.class.equals(type)) { return (T) Double.valueOf(stringValue); } else if (TimeUnit.class.equals(type)) { return (T) TimeUnit.valueOf(stringValue); } throw new IllegalArgumentException(\"Unable to convert string value to desired type \" + type); } throw new IllegalArgumentException(\"Unable to convert value to desired type \" + type); } } protected Object getProperty(String key) { if (enableDynamicProperties) { String dynamicValue = null; DynamicStringProperty dynamicProperty = dynamicProperties.get(key); if (dynamicProperty != null) { dynamicValue = dynamicProperty.get(); } if (dynamicValue == null) { dynamicValue = DynamicProperty.getInstance(getConfigKey(key)).getString(); if (dynamicValue == null) { dynamicValue = DynamicProperty.getInstance(getDefaultPropName(key)).getString(); } } if (dynamicValue != null) { return dynamicValue; } } return properties.get(key); } 刷新缓存代码:com.netflix.loadbalancer.BaseLoadBalancer 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869public void setServersList(List lsrv) { Lock writeLock = allServerLock.writeLock(); logger.debug(\"LoadBalancer [{}]: clearing server list (SET op)\", name); ArrayList<Server> newServers = new ArrayList<Server>(); writeLock.lock(); try { ArrayList<Server> allServers = new ArrayList<Server>(); for (Object server : lsrv) { if (server == null) { continue; } if (server instanceof String) { server = new Server((String) server); } if (server instanceof Server) { logger.debug(\"LoadBalancer [{}]: addServer [{}]\", name, ((Server) server).getId()); allServers.add((Server) server); } else { throw new IllegalArgumentException( \"Type String or Server expected, instead found:\" + server.getClass()); } } boolean listChanged = false; if (!allServerList.equals(allServers)) { listChanged = true; if (changeListeners != null && changeListeners.size() > 0) { List<Server> oldList = ImmutableList.copyOf(allServerList); List<Server> newList = ImmutableList.copyOf(allServers); for (ServerListChangeListener l: changeListeners) { try { l.serverListChanged(oldList, newList); } catch (Exception e) { logger.error(\"LoadBalancer [{}]: Error invoking server list change listener\", name, e); } } } } if (isEnablePrimingConnections()) { for (Server server : allServers) { if (!allServerList.contains(server)) { server.setReadyToServe(false); newServers.add((Server) server); } } if (primeConnections != null) { primeConnections.primeConnectionsAsync(newServers, this); } } // This will reset readyToServe flag to true on all servers // regardless whether // previous priming connections are success or not allServerList = allServers; if (canSkipPing()) { for (Server s : allServerList) { s.setAlive(true); } upServerList = allServerList; } else if (listChanged) { forceQuickPing(); } } finally { writeLock.unlock(); }} 获取应用实例 eureka server入口:com.netflix.eureka.resources.ApplicationsResource#getContainers 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849@GETpublic Response getContainers(@PathParam(\"version\") String version, @HeaderParam(HEADER_ACCEPT) String acceptHeader, @HeaderParam(HEADER_ACCEPT_ENCODING) String acceptEncoding, @HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept, @Context UriInfo uriInfo, @Nullable @QueryParam(\"regions\") String regionsStr) { boolean isRemoteRegionRequested = null != regionsStr && !regionsStr.isEmpty(); String[] regions = null; if (!isRemoteRegionRequested) { EurekaMonitors.GET_ALL.increment(); } else { regions = regionsStr.toLowerCase().split(\",\"); Arrays.sort(regions); // So we don't have different caches for same regions queried in different order. EurekaMonitors.GET_ALL_WITH_REMOTE_REGIONS.increment(); } // Check if the server allows the access to the registry. The server can // restrict access if it is not // ready to serve traffic depending on various reasons. if (!registry.shouldAllowAccess(isRemoteRegionRequested)) { return Response.status(Status.FORBIDDEN).build(); } CurrentRequestVersion.set(Version.toEnum(version)); KeyType keyType = Key.KeyType.JSON; String returnMediaType = MediaType.APPLICATION_JSON; if (acceptHeader == null || !acceptHeader.contains(HEADER_JSON_VALUE)) { keyType = Key.KeyType.XML; returnMediaType = MediaType.APPLICATION_XML; } Key cacheKey = new Key(Key.EntityType.Application, ResponseCacheImpl.ALL_APPS, keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions ); Response response; if (acceptEncoding != null && acceptEncoding.contains(HEADER_GZIP_VALUE)) { response = Response.ok(responseCache.getGZIP(cacheKey)) .header(HEADER_CONTENT_ENCODING, HEADER_GZIP_VALUE) .header(HEADER_CONTENT_TYPE, returnMediaType) .build(); } else { response = Response.ok(responseCache.get(cacheKey)) .build(); } return response;} 核心代码:com.netflix.eureka.registry.ResponseCacheImpl#getValue 12345678910111213141516171819Value getValue(final Key key, boolean useReadOnlyCache) { Value payload = null; try { if (useReadOnlyCache) { final Value currentPayload = readOnlyCacheMap.get(key); if (currentPayload != null) { payload = currentPayload; } else { payload = readWriteCacheMap.get(key); readOnlyCacheMap.put(key, payload); } } else { payload = readWriteCacheMap.get(key); } } catch (Throwable t) { logger.error(\"Cannot get value for key :\" + key, t); } return payload; } 如果useReadOnlyCache为true从readOnlyCacheMap中获取实例信息,如果false从readWriteCacheMap中获取实例信息,默认为true。 因为readWriteCacheMap用的LoadingCache有读写锁,使用readOnlyCacheMap可以增加吞吐量,中小集群可以关闭readOnlyCacheMap。 参考文档https://blog.csdn.net/u012394095/article/details/80693713 http://blog.didispace.com/spring-cloud-eureka-register-detail/ https://www.infoq.cn/article/jlDJQ*3wtN2PcqTDyokh","categories":[{"name":"java","slug":"java","permalink":"http://sunshanpeng.com/categories/java/"}],"tags":[{"name":"eureka","slug":"eureka","permalink":"http://sunshanpeng.com/tags/eureka/"}]},{"title":"Kubernetes高可用部署","slug":"Kubernetes高可用架构","date":"2019-08-24T04:29:30.000Z","updated":"2019-08-25T10:45:23.110Z","comments":true,"path":"2019/08/24/Kubernetes高可用架构/","link":"","permalink":"http://sunshanpeng.com/2019/08/24/Kubernetes高可用架构/","excerpt":"","text":"基本架构 Kubernetes分为Master Node和Worker Node,其中Master Node作为控制节点,调度管理整个系统;Worker Node作为工作节点,运行业务容器。 Master Node api-server 作为kubernetes系统的入口,对外暴露了Kubernetes API,封装了核心对象的增删改查操作,以RESTFul接口方式提供给外部客户和内部组件调用。它维护的REST对象将持久化到etcd(一个分布式强一致性的key/value存储)。 scheduler 负责集群的资源调度,为新建的pod分配机器。这部分工作分出来变成一个组件,意味着可以很方便地替换成其他的调度器。 controller-manager 运行控制器,它们是处理集群中常规任务的后台线程。逻辑上,每个控制器是一个单独的进程,但为了降低复杂性,它们都被编译成独立的可执行文件,并在单个进程中运行。 这些控制器包括: 节点控制器: 当节点移除时,负责注意和响应。 副本控制器: 负责维护系统中每个副本控制器对象正确数量的 Pod。 端点控制器: 填充 端点(Endpoints) 对象(即连接 Services & Pods)。 服务帐户和令牌控制器: 为新的命名空间创建默认帐户和 API 访问令牌 Worker Node kubeletkubelet是主要的节点代理,它监测已分配给其节点的 Pod(通过 apiserver 或通过本地配置文件),提供如下功能: 挂载 Pod 所需要的数据卷(Volume)。 下载 Pod 的 secrets。 通过 Docker 运行(或通过其他容器运行时)运行 Pod 的容器。 周期性的对容器生命周期进行探测。 如果需要,通过创建 镜像 Pod(Mirror Pod) 将 Pod 的状态报告回系统的其余部分。将节点的状态报告回系统的其余部分。 kube-proxy通过维护主机上的网络规则并执行连接转发,实现了Kubernetes服务抽象。 存储Kubernetes 所有集群数据都存储在etcd里。 etcd通常部署在Master Node上,也可以单独部署。 官网地址:https://github.com/etcd-io/etcd 网络CNI(Container Network Interface)是CNCF旗下的一个项目,由一组用于配置Linux容器的网络接口的规范和库组成,同时还包含了一些插件。CNI仅关心容器创建时的网络分配,和当容器被删除时释放网络资源。 每个pod都会有一个集群内唯一的IP,不同Node上的Pod可以互相访问。 常用的网络插件有Calico和Flannel。 容器CRI(Container Runtime Interface)是容器运行时接口,kubelet可以运行实现了CRI的各种容器运行时而不仅仅是Docker。 高可用架构要实现高可用,就要实现所有Node没有单点故障,任何一台服务器宕机均不影响Kubernetes的正常工作。Kubernetes的高可用主要是Master Node和存储的高可用,包括以下几个组件: etcd属于CP架构,保证一致性和分区容错性。因为其内部使用 Raft 协议进行选举,所以需要部署基数个实例,比如3、5、7个。可以和Master Node一起部署也可以外置部署。 controller-manager和scheduler的高可用相对容易,是基于etcd的锁进行leader election。相当于基于etcd的分布式锁,谁拿到谁干活,同时只会有一个实例在工作,部署2个及以上的实例数量。 api-server是无状态的,需要部署2个及以上的实例数量,然后使用HAProxy和Keepalived作为负载均衡实现高可用。除了HAProxy也可以使用Nginx或其他负载均衡策略。 方案一 使用三台4核8G的服务器当Master Node,上面部署api-server、scheduler、controller-manager、kubelet、kube-proxy、docker、calico、etcd、HAProxy、keepalived。 Worker Node的配置根据实际情况,最好CPU和内存的大小、比例都一样。上面部署kubelet、kube-proxy、docker、calico。 该方案集群内外连接api-server都是高可用,使用方便。但是公有云不能用虚拟IP所以不能用该方案。另外所有的网络请求都会集中到一台服务器(虚拟IP所在的服务器)进行转发,负载不均衡,HAProxy的上限即api-server的上限。 方案二 使用三台4核8G的服务器当Master Node,上面部署api-server、scheduler、controller-manager、kubelet、kube-proxy、docker、calico、etcd。 Worker Node的配置根据实际情况,最好CPU和内存的大小、比例都一样。上面部署kubelet、kube-proxy、docker、calico、HAProxy。 该方案兼容自有机房和公有云,Master Node负载比较均衡。但是集群外访问api-server没有做到高可用,需要额外解决。如果Worker Node上的HAProxy宕机则该节点不能正常工作。另外如果添加和删除Master Node需要更新所有Worker Node的负载均衡配置(可选项,不修改也可以)。 部署方式二进制部署所有组件均使用二进制文件部署,手动部署可以参考:https://github.com/opsnull/follow-me-install-kubernetes-cluster;ansible脚本部署可以用:https://github.com/easzlab/kubeasz。 容器化部署kubernetes的所有组件中,除了docker和kubelet是运行和管理容器的,其他组件都可以使用容器化的方式部署,最流行的方式就是使用kubeadm。 官网地址:https://github.com/kubernetes/kubeadm。 参考https://kubernetes.io/zh/docs/concepts/overview/components/ https://www.kubernetes.org.cn/2931.html https://jimmysong.io/kubernetes-handbook/concepts/cni.html https://jimmysong.io/kubernetes-handbook/concepts/cri.html","categories":[{"name":"容器","slug":"容器","permalink":"http://sunshanpeng.com/categories/容器/"}],"tags":[{"name":"k8s","slug":"k8s","permalink":"http://sunshanpeng.com/tags/k8s/"}]},{"title":"Docker常用命令","slug":"Docker常用命令","date":"2019-03-24T14:26:37.000Z","updated":"2020-03-01T13:13:39.766Z","comments":true,"path":"2019/03/24/Docker常用命令/","link":"","permalink":"http://sunshanpeng.com/2019/03/24/Docker常用命令/","excerpt":"","text":"拉取镜像1docker pull mysql:5.6 格式:docker pull [OPTIONS] NAME[:TAG|@DIGEST],tag不写的话默认latest。 登录镜像仓库1docker login --username sunshanpeng --password 123456 harbor.sunshanpeng.com 格式:docker login [OPTIONS] [SERVER] SERVER为空的情况下默认登录hub.docker.com password建议不要显示在控制台上 列出所有镜像1docker images 格式:docker images [OPTIONS] [REPOSITORY[:TAG]],可选参数显示全部镜像或者过滤镜像。 列出所有容器1docker ps -a 格式:docker ps [OPTIONS],去掉-a显示运行中的容器。 给镜像打tag1docker tag mysql:5.6 harbor.sunshanpeng.com/database/mysql:5.6 格式:docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG] 当我们从公共仓库拉取的镜像,想推送到我们自己搭建的私有仓库,需要先打一个tag; 当我们拉取不到一些需要翻墙才能拉取的镜像时,也可以从国内镜像仓库拉取镜像然后打个tag给成目标镜像。 推送镜像1docker push harbor.sunshanpeng.com/database/mysql:5.6 格式:docker push [OPTIONS] NAME[:TAG] 推送镜像必须先登录要推送的目标仓库,而且需要对目标仓库有推送的权限。 类似docker push mysql:5.6是不行的。 制作镜像1docker build -t simpleApp:v1 . 格式:docker build [OPTIONS] PATH | URL | -。 根据Dockerfile制作镜像。 删除镜像1docker rmi mysql:5.6 格式:docker rmi [OPTIONS] IMAGE [IMAGE...],不能删除已经创建了容器的镜像,必须先删除对应容器。 运行容器1docker run nginx 格式:docker run [OPTIONS] IMAGE [COMMAND] [ARG...] OPTIONS参数有很多,比较常用的如下: --name指定容器名字 -p指定容器映射宿主机的端口号 -e指定环境变量 -v指定挂载卷 -it指定容器前台运行 -d指定容器后台运行 其他还有很多指定IP、Hostname、CPU、内存、网络、内核参数、重启策略等等参数,可以通过docker run --help查询。 mysql容器启动命令1docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -v=/docker/mysql/data:/var/lib/mysql -v=/docker/mysql/my.cnf:/etc/mysql/my.cnf -v=/usr/share/zoneinfo/Asia/Shanghai:/etc/localtime -d mysql:5.6 redis容器启动命令1docker run --name redis -p 6379:6379 -v=/docker/redis/data:/data -d redis:3.2 重命名容器1docker rename mysql mysql1 格式:docker rename CONTAINER NEW_NAME。 停止容器1docker stop mysql 格式:docker stop [OPTIONS] CONTAINER [CONTAINER...],停止多个容器用空格分隔容器名字或者容器id。 强制停止容器1docker kill mysql 格式:docker kill [OPTIONS] CONTAINER [CONTAINER...]。 启动/重启容器1docker start/restart mysql 格式:docker start/restart [OPTIONS] CONTAINER [CONTAINER...]。 删除容器1docker rm mysql 格式:docker rm [OPTIONS] CONTAINER [CONTAINER...],不带参数时只能删除已停止的容器,带上参数-f可以强制删除运行中的容器。 更新容器1docker update --restart always mysql 格式:docker update [OPTIONS] CONTAINER [CONTAINER...]。 更新容器的重启策略、CPU和内存大小。 进入容器1docker exec -it mysql /bin/bash 格式:docker exec [OPTIONS] CONTAINER COMMAND [ARG...]。 CONTAINER 可以是容器Id或者容器名字; /bin/bash是进入容器后执行的命令,有些容器可能不存在/bin/bash。 导出镜像文件1docker save -o rook-ceph-093.tar rook/ceph:v0.9.3 格式:docker save [OPTIONS] IMAGE [IMAGE...]。 如果同时导出多个镜像到一个文件里,后面的镜像列表用空格来分隔。 导入镜像文件1docker load -i /root/rook-ceph-093.tar 格式:docker load [OPTIONS] 当不方便使用自建的镜像仓库来传输镜像时,可以用镜像的导出导入功能来移动镜像。 查看容器资源消耗123456docker statsCONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS63ebc0e88e5a happy_volhard 0.02% 35.3MiB / 15.46GiB 0.22% 11.5MB / 19MB 13.3MB / 45MB 227ed66f584a25 redis 0.08% 7.82MiB / 15.46GiB 0.05% 304kB / 274kB 5.59MB / 0B 3500eed21d914 dockercompose_mqbroker_1 2.98% 752.3MiB / 15.46GiB 4.75% 0B / 0B 3.07GB / 19GB 204ddd919a5bf52 dockercompose_mqnamesrv_1 0.21% 329.2MiB / 15.46GiB 2.08% 0B / 0B 60.3MB / 16.4kB 41 查看Docker占用磁盘1234567docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 29 28 8.575GB 1.873GB (21%)Containers 29 17 7.545GB 410.8MB (5%)Local Volumes 303 23 6.339GB 4.138GB (65%)Build Cache 0 0 0B 0B 可以查看镜像文件大小、容器大小、本地存储使用的磁盘大小。 清理Docker镜像和容器1docker system prune -a 此命令会清除所有未运行的容器和清理所有未使用的镜像。","categories":[{"name":"容器","slug":"容器","permalink":"http://sunshanpeng.com/categories/容器/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://sunshanpeng.com/tags/docker/"}]},{"title":"Kubernetes集群配置优化","slug":"Kubernetes集群配置优化","date":"2019-03-24T14:26:37.000Z","updated":"2019-08-16T18:20:56.314Z","comments":true,"path":"2019/03/24/Kubernetes集群配置优化/","link":"","permalink":"http://sunshanpeng.com/2019/03/24/Kubernetes集群配置优化/","excerpt":"","text":"一、Docker配置123456789101112131415{ \"exec-opts\": [\"native.cgroupdriver=systemd\"], \"log-driver\": \"json-file\", \"log-opts\": { \"max-size\": \"100m\", \"max-file\": \"10\" }, \"bip\": \"169.254.123.1/24\", \"oom-score-adjust\": -1000, \"registry-mirrors\": [\"https://registry.docker-cn.com\", \"https://docker.mirrors.ustc.edu.cn\"], \"storage-driver\": \"overlay2\", \"storage-opts\":[\"overlay2.override_kernel_check=true\"], \"data-root\": \"/var/lib/docker\", \"live-restore\": true} data-root设置存储持久数据和资源配置的目录,默认值/var/lib/docker,17.05.0以下版本参数为:graph。 如果只有一个系统盘可以不用指定该参数,如果有数据盘并且数据盘不是/var,则需要指定数据目录。 registry-mirrors镜像仓库地址,用于镜像加速。 直接连接国外dockerHub拉取镜像速度太慢,一般都会配置国内的镜像地址。 bip设置docker0网桥地址,默认是172.17.0.1/16。 默认网段有可能和现有服务器的网段冲突,建议设置成169.254.123.1/24 live-restore当docker daemon停止时,让容器继续运行,默认false关闭,设为true打开。 默认情况下重启或者关闭docker daemon时,容器都会停止,通过设置live-restore让容器继续运行,在修改docker配置或者升级docker的时候很有用。(如果daemon重启之后使用了不同的bip或不同的data-root,则live restore无法正常工作) log-optsdocker日志设置,默认为空。 不设置最大值的话日志文件可能会撑爆硬盘。 exec-opts设置docker cgroup驱动。 需要注意docker的Cgroups和kubelet的Cgroups配置是否一致。 二、Kubelet配置123456789101112131415161718192021222324--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true --network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin --cluster-dns=192.168.176.10 --pod-infra-container-image=registry-vpc.cn-shanghai.aliyuncs.com/acs/pause-amd64:3.1 --enable-controller-attach-detach=false --enable-load-reader --cluster-domain=cluster.local --cloud-provider=external --hostname-override=$(NODE_NAME)--provider-id=cn-shanghai.i-uf657oy682jirk1oad62 --authorization-mode=Webhook --client-ca-file=/etc/kubernetes/pki/ca.crt --system-reserved=memory=300Mi --kube-reserved=memory=400Mi --eviction-hard=imagefs.available<15%,memory.available<300Mi,nodefs.available<10%,nodefs.inodesFree<5% --cgroup-driver=systemd --anonymous-auth=false --rotate-certificates=true --cert-dir=/var/lib/kubelet/pki--root-dir=/var/lib/kubelet pod-infra-container-image基础镜像,默认使用的k8s.gcr.io/pause-amd64:3.1需要翻墙,可以使用国内镜像或者自己上传的镜像。 root-dir设置存储持久数据和资源配置的目录,默认值/var/lib/kubelet。 用法同Docker的data-root参数。 cgroup-driver设置kubelet Cgroups驱动。 需要注意docker的Cgroups和kubelet的Cgroups配置是否一致。 system-reserved给系统保留的资源大小,可以设置CPU、内存、硬盘。 kube-reserved给kubernetes保留的资源大小,可以设置CPU、内存、硬盘。 eviction-hard驱逐条件,当节点资源小于设置的驱逐条件时,kubelet开始驱逐Pod。 三、Kube-proxy1234- --proxy-mode=ipvs- --kubeconfig=/var/lib/kube-proxy/kubeconfig.conf- --cluster-cidr=172.17.0.0/18- --hostname-override=$(NODE_NAME) proxy-modeipvs可以大大提高性能。 参考资料: https://docs.docker.com/engine/reference/commandline/dockerd/ https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/ https://kubernetes.io/docs/tasks/administer-cluster/out-of-resource/ https://docs.docker.com/engine/deprecated/","categories":[{"name":"容器","slug":"容器","permalink":"http://sunshanpeng.com/categories/容器/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://sunshanpeng.com/tags/docker/"},{"name":"k8s","slug":"k8s","permalink":"http://sunshanpeng.com/tags/k8s/"}]},{"title":"Windows电脑本地开发时连接Apollo配置中心启动慢","slug":"Windows电脑本地开发时连接Apollo配置中心启动慢","date":"2019-03-24T14:26:37.000Z","updated":"2019-08-18T03:58:48.733Z","comments":true,"path":"2019/03/24/Windows电脑本地开发时连接Apollo配置中心启动慢/","link":"","permalink":"http://sunshanpeng.com/2019/03/24/Windows电脑本地开发时连接Apollo配置中心启动慢/","excerpt":"","text":"表现:启动时中间停顿了10秒左右 原因:电脑上网卡太多 解决方法: 禁用多余网卡 在启动参数指定本机IP:-Dhost.ip=10.208.202.251 10.208.202.251改成自己的IP地址 原理:指定本机IP后就会跳过查找有效IP这一步 具体代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125package com.ctrip.framework.foundation.internals;import java.net.Inet4Address;import java.net.InetAddress;import java.net.NetworkInterface;import java.net.SocketException;import java.net.UnknownHostException;import java.util.ArrayList;import java.util.Collections;import java.util.Enumeration;import java.util.List;import java.util.Objects;public enum NetworkInterfaceManager { INSTANCE; private InetAddress m_local; private InetAddress m_localHost; private NetworkInterfaceManager() { load(); } public InetAddress findValidateIp(List<InetAddress> addresses) { InetAddress local = null; int maxWeight = -1; for (InetAddress address : addresses) { if (address instanceof Inet4Address) { int weight = 0; if (address.isSiteLocalAddress()) { weight += 8; } if (address.isLinkLocalAddress()) { weight += 4; } if (address.isLoopbackAddress()) { weight += 2; } // has host name // TODO fix performance issue when calling getHostName if (!Objects.equals(address.getHostName(), address.getHostAddress())) { weight += 1; } if (weight > maxWeight) { maxWeight = weight; local = address; } } } return local; } public String getLocalHostAddress() { return m_local.getHostAddress(); } public String getLocalHostName() { try { if (null == m_localHost) { m_localHost = InetAddress.getLocalHost(); } return m_localHost.getHostName(); } catch (UnknownHostException e) { return m_local.getHostName(); } } private String getProperty(String name) { String value = null; value = System.getProperty(name); if (value == null) { value = System.getenv(name); } return value; } private void load() { String ip = getProperty(\"host.ip\");//除了启动参数也可以在环境变量中指定host.ip if (ip != null) {//如果指定了host.ip就直接拿指定IP的网卡信息 try { m_local = InetAddress.getByName(ip); return; } catch (Exception e) { System.err.println(e); // ignore } } try { Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); List<NetworkInterface> nis = interfaces == null ? Collections.<NetworkInterface>emptyList() : Collections.list(interfaces); List<InetAddress> addresses = new ArrayList<InetAddress>(); InetAddress local = null; try { for (NetworkInterface ni : nis) { if (ni.isUp() && !ni.isLoopback()) { addresses.addAll(Collections.list(ni.getInetAddresses())); } } local = findValidateIp(addresses); } catch (Exception e) { // ignore } if (local != null) { m_local = local; return; } } catch (SocketException e) { // ignore it } m_local = InetAddress.getLoopbackAddress(); }} 注: 官方说还可以通过改host的方式实现,但是我在win10电脑上没有成功。 1.4修复了该问题 参考链接:https://github.com/ctripcorp/apollo/wiki/%E9%83%A8%E7%BD%B2&%E5%BC%80%E5%8F%91%E9%81%87%E5%88%B0%E7%9A%84%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98#6-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%A4%9A%E5%9D%97%E7%BD%91%E5%8D%A1%E9%80%A0%E6%88%90%E8%8E%B7%E5%8F%96ip%E4%B8%8D%E5%87%86%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3 https://github.com/ctripcorp/apollo/issues/1457","categories":[{"name":"java","slug":"java","permalink":"http://sunshanpeng.com/categories/java/"}],"tags":[{"name":"apollo","slug":"apollo","permalink":"http://sunshanpeng.com/tags/apollo/"}]},{"title":"Kubernetes介绍","slug":"Kubernetes介绍","date":"2019-02-24T14:52:37.000Z","updated":"2019-08-16T18:21:22.028Z","comments":true,"path":"2019/02/24/Kubernetes介绍/","link":"","permalink":"http://sunshanpeng.com/2019/02/24/Kubernetes介绍/","excerpt":"","text":"有了Docker之后,单个应用进程的使用管理变的更简单了,但是如果想要管理一个集群的不同应用,处理应用之间的关系, 比如:一个Web应用与数据库之间的访问关系,负载均衡和后端服务之间的代理关系,门户应用与授权组件的调用关系, 再比如说一个服务单位的不同功能之间的关系,如一个Web应用与日志搜集组件之间的文件交换关系, 在传统虚拟机环境对这种关系的处理方法都比较“粗颗粒”,一股脑的部署在同一台虚拟机中,需要手动维护很多跟应用协作的守护进程,用来处理它的日志搜集、灾难恢复、数据备份等辅助工作。 使用容器和编排技术以后,那些原来挤在同一个虚拟机里的各个应用、组件、守护进程,都可以被分别做成镜像,然后运行在一个个专属的容器中。 它们之间互不干涉,拥有各自的资源配额,可以被调度在整个集群里的任何一台机器上。 如果只是用来封装微服务、拉取用户镜像、调度单容器,用Docker Swarm就足够了,而且方便有效,但是如果需要路由网关、水平扩展、弹性伸缩、监控、备份、灾难恢复等一系列运维能力,就需要使用kubernetes来解决了。 Kubernetes介绍 Kubernetes是Google开源的容器编排调度引擎,它的目标不仅仅是一个编排系统,而是提供一个规范,可以用来描述集群的架构,定义服务的最终状态,Kubernetes可以将系统自动得到和维持在这个状态。 更直白的说,Kubernetes用户可以通过编写一个yaml或者json格式的配置文件,也可以通过工具/代码生成或直接请求Kubernetes API创建应用,该配置文件中包含了用户想要应用程序保持的状态,不论整个Kubernetes集群中的个别主机发生什么问题,都不会影响应用程序的状态,你还可以通过改变该配置文件或请求Kubernetes API来改变应用程序的状态。 配置文件里面怎么定义的,整个系统就是怎么运行的。 kubernetes 的马斯洛需求 Kubernetes架构图 Kubernets属于主从的分布式集群架构,包含Master和Node: Master作为控制节点,调度管理整个系统,包含以下组件: API Server作为kubernetes系统的入口,封装了核心对象的增删改查操作,以RESTFul接口方式提供给外部客户和内部组件调用。它维护的REST对象将持久化到etcd(一个分布式强一致性的key/value存储)。 Scheduler:负责集群的资源调度,为新建的pod分配机器。这部分工作分出来变成一个组件,意味着可以很方便地替换成其他的调度器。 Controller Manager:负责执行各种控制器,目前有两类:(1)Endpoint Controller:定期关联service和pod(关联信息由endpoint对象维护),保证service到pod的映射总是最新的。(2)Replication Controller:定期关联replicationController和pod,保证replicationController定义的复制数量与实际运行pod的数量总是一致的。 Node是运行节点,运行业务容器,包含以下组件: Kubelet:负责管控docker容器,如启动/停止、监控运行状态等。它会定期从etcd获取分配到本机的pod,并根据pod信息启动或停止相应的容器。同时,它也会接收apiserver的HTTP请求,汇报pod的运行状态。 Kube Proxy:负责为pod提供代理。它会定期从etcd获取所有的service,并根据service信息创建代理。当某个客户pod要访问其他pod时,访问请求会经过本机proxy做转发。 Kubernets使用Etcd作为存储和通信中间件,实现Master和Node的一致性,这是目前比较常见的做法,典型的SOA架构,解耦Master和Node。 基本概念 PodPod是Kubernetes的基本操作单元,把相关的一个或多个容器构成一个Pod,通常Pod里的容器运行相同的应用。Pod包含的容器运行在同一个Node(Host)上,看作一个统一管理单元,共享相同的volumes和network namespace/IP和Port空间。 Replication ControllerReplication Controller确保任何时候Kubernetes集群中有指定数量的pod副本(replicas)在运行, 如果少于指定数量的pod副本(replicas),Replication Controller会启动新的Container,反之会杀死多余的以保证数量不变。 Replication Controller使用预先定义的pod模板创建pods,一旦创建成功,pod 模板和创建的pods没有任何关联,可以修改pod 模板而不会对已创建pods有任何影响,也可以直接更新通过Replication Controller创建的pods。 对于利用pod 模板创建的pods,Replication Controller根据label selector来关联,通过修改pods的label可以删除对应的pods。 ServiceService也是Kubernetes的基本操作单元,是真实应用服务的抽象,每一个服务后面都有很多对应的容器来支持,通过Proxy的port和服务selector决定服务请求传递给后端提供服务的容器,对外表现为一个单一访问接口,外部不需要了解后端如何运行,这给扩展或维护后端带来很大的好处。 LabelLabel是用于区分Pod、Service、Replication Controller的key/value键值对,Pod、Service、 Replication Controller可以有多个label,但是每个label的key只能对应一个value。Labels是Service和Replication Controller运行的基础,为了将访问Service的请求转发给后端提供服务的多个容器,正是通过标识容器的labels来选择正确的容器。同样,Replication Controller也使用labels来管理通过pod 模板创建的一组容器,这样Replication Controller可以更加容易,方便地管理多个容器,无论有多少容器。","categories":[{"name":"容器","slug":"容器","permalink":"http://sunshanpeng.com/categories/容器/"}],"tags":[{"name":"k8s","slug":"k8s","permalink":"http://sunshanpeng.com/tags/k8s/"}]},{"title":"Pod的意义","slug":"Pod的意义","date":"2019-02-24T14:29:37.000Z","updated":"2019-08-16T18:21:54.305Z","comments":true,"path":"2019/02/24/Pod的意义/","link":"","permalink":"http://sunshanpeng.com/2019/02/24/Pod的意义/","excerpt":"","text":"Kubernetes 使用 Pod 来管理容器,每个 Pod 可以包含一个或多个紧密关联的容器。 Pod 是一组紧密关联的容器集合,它们共享 PID、IPC、Network 和 UTS namespace,是 Kubernetes 调度的基本单位。 Pod 内的多个容器共享网络和文件系统,可以通过进程间通信和文件共享这种简单高效的方式组合完成服务。 Pod的意义容器都是单进程运行的,不是因为容器只能运行一个进程,而是容器没有管理多个进程的能力。 容器里PID=1的进程就是应用本身,其他的进程都是这个PID=1进程的子进程。如果启动了第二个进程,那这第二个进程异常退出的时候,外部并不能感知,也就管理不了。 在一个真正的操作系统里,进程并不是“孤苦伶仃”地独自运行的,应用之间可能有着密切的协作关系,必须部署在同一台机器上。 这些密切关系包括但不限于:互相之间会发生直接的文件交换,使用localhost或者Socket文件进行本地通信,会发生非常频繁的远程调用,共享某些Linux Namespace。 Pod的实现原理首先,Pod只是一个逻辑概念,是一组共享了某些资源的容器。 Kubernetes真正处理的,还是宿主机操作系统上Linux容器的Namespace和Cgroups,而并不存在一个所谓的Pod的边界或者隔离环境。 Kubernetes里,Pod的实现需要使用一个中间容器,叫做Infra容器,在Pod中,Infra容器永远都是第一个被创建的容器。 其他用户自定义的容器,通过Join Network Namespace的方式与Infra容器关联在一起。 如图所示,这个Pod里除了两个用户容器,还有一个Infra容器。 这个Infra容器占用极少的资源,使用了一个非常特殊的镜像:k8s.gcr.io/pause,这个镜像永远处于“暂停”状态,100-200KB左右大小。 Infra容器生成NetWork Namespace后,用户容器加入Infra容器的Network Namespace中,用户容器和Infra容器在宿主机上的Namespace文件是一样的。 这意味着,对Pod中的容器A和容器B来说: 它们可以直接使用localhost进行通信; 他们看到的网络设备和Infra容器的一样; 一个Pod只有一个IP地址,也就是容器A、容器B、Infra容器的ip地址一致; 所有的网络资源被改Pod中的所有容器共享; Pod的生命周期只跟Infra容器一致,与容器A和B无关。 对于同一个Pod里面的所有用户容器来说,它们的进出流量,都是通过Infra容器完成的。 Pod的本质,实际上是在扮演传统基础设施“虚拟机”的角色;容器则是这个虚拟机里运行的用户进程。","categories":[{"name":"容器","slug":"容器","permalink":"http://sunshanpeng.com/categories/容器/"}],"tags":[{"name":"k8s","slug":"k8s","permalink":"http://sunshanpeng.com/tags/k8s/"}]},{"title":"使用apollo配置中心生成雪花算法的workerId","slug":"使用apollo配置中心生成雪花算法的workerId","date":"2019-02-24T14:29:37.000Z","updated":"2019-10-08T00:49:03.510Z","comments":true,"path":"2019/02/24/使用apollo配置中心生成雪花算法的workerId/","link":"","permalink":"http://sunshanpeng.com/2019/02/24/使用apollo配置中心生成雪花算法的workerId/","excerpt":"","text":"前言在分布式系统中,有一些需要使用全局唯一ID的场景,这时候雪花算法(snowflake)就是一种不错的解决方式。 但是雪花算法需要用到一个workerId,同一个应用部署多个实例的时候workerId不能相同。 使用环境变量或者启动参数来指定workerId的方式虽然在一定程度上解决了workerId的问题,但是给容器环境下的自动化部署或者动态扩容带来了新的挑战。 Apollo配置中心介绍Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。 GitHub地址:https://github.com/ctripcorp/apollo Apollo分为Client、Config Service、Admin Service、Portal四个部分: Client是一个jar包集成在业务应用里,通过Meta Server从Config Service获取配置信息; Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端(Client); Admin Service提供配置的修改、发布等功能,服务对象是Apollo管理界面(Portal); Portal通过Meta Server连接Admin Service修改、发布配置信息。 ConfigDB存储配置信息,由Config Service和Admin Service使用; PortalDB存储权限和审计信息,由Portal使用。 改造Config Service生成workerId原理介绍Client端通过/configs/{appId}/{clusterName}/{namespace:.+}接口拉取全量配置信息,对应方法是com.ctrip.framework.apollo.configservice.controller.ConfigController#queryConfig。方法的返回值ApolloConfig有5个属性:appId、cluster、namespaceName、configurations、releaseKey,只要把生成的workerId放到configurations这个Map类型的属性中,Client端就能直接通过Key值拿到workerId来使用。 具体实现一、数据库改动ConfigDB库的instance表存储了使用配置的应用实例。 在Instance表新加一个NodeId字段用来存储生成的workerId,并且将AppId和NodeId设置为唯一索引:123456789101112131415CREATE TABLE `instance` ( `Id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增Id', `AppId` varchar(32) NOT NULL DEFAULT 'default' COMMENT 'AppID', `ClusterName` varchar(32) NOT NULL DEFAULT 'default' COMMENT 'ClusterName', `DataCenter` varchar(64) NOT NULL DEFAULT 'default' COMMENT 'Data Center Name', `Ip` varchar(32) NOT NULL DEFAULT '' COMMENT 'instance ip', `NodeId` int(5) DEFAULT NULL COMMENT '节点id', `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `DataChange_LastTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间', PRIMARY KEY (`Id`), UNIQUE KEY `IX_UNIQUE_KEY` (`AppId`,`ClusterName`,`Ip`,`DataCenter`), UNIQUE KEY `uk_appId_nodeId` (`AppId`,`NodeId`) USING BTREE, KEY `IX_IP` (`Ip`), KEY `IX_DataChange_LastTime` (`DataChange_LastTime`)) ENGINE=InnoDB AUTO_INCREMENT=6779 DEFAULT CHARSET=utf8mb4 COMMENT='使用配置的应用实例'; 二、代码改动原来Instance表的记录是在获取配置时调用com.ctrip.framework.apollo.configservice.util.InstanceConfigAuditUtil#audit异步添加的,这里需要改成获取配置时同步添加实例信息并且放到ApolloConfig的configurations属性中。 1.com.ctrip.framework.apollo.configservice.controller.ConfigController queryConfig方法返回前获取实例信息并返回123456Instance instance = instanceService.findInstance(appId, clusterName, dataCenter, clientIp);Integer nodeId = instance.getNodeId();if (nodeId == null) { nodeId = -1;}apolloConfig.addConfig(\"apollo.node.id\", String.valueOf(nodeId)); 2.com.ctrip.framework.apollo.biz.service.InstanceService12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879@Value(\"${apollo.node.minId}\")//workerId最小值,0-1023之间private Integer minId;@Value(\"${apollo.node.maxId}\")//workerId最大值,0-1023之间private Integer maxId;private static final Integer DEFAULT_NODE_ID = null;public Instance findInstance(String appId, String clusterName, String dataCenter, String ip) { Instance instance = instanceRepository.findByAppIdAndClusterNameAndDataCenterAndIp(appId, clusterName, dataCenter, ip); if (instance == null) { instance = createInstance(appId, clusterName, dataCenter, ip); } if (instance.getNodeId() == null) { updateInstance(instance); } return instance;}private Instance createInstance(String appId, String clusterName, String dataCenter, String ip) { Instance instance = new Instance(); instance.setAppId(appId); instance.setClusterName(clusterName); instance.setDataCenter(dataCenter); instance.setIp(ip); instance.setNodeId(generateNodeId(appId)); try { instance = createInstance(instance); } catch (DataIntegrityViolationException e) { //使用数据库的唯一约束来保证workerId的唯一性 logger.error(\"createInstance.error,instance={}\", instance); instance = findInstance(appId, clusterName, dataCenter, ip); if (instance == null) { instance = createInstance(appId, clusterName, dataCenter, ip); } } return instance;}private void updateInstance(Instance instance) { try { instance.setNodeId(generateNodeId(instance.getAppId())); instanceRepository.save(instance); } catch (DataIntegrityViolationException e) { logger.error(\"updateInstance.error,instance={}\", instance); updateInstance(instance); }}private Integer generateNodeId(String appId) { //生成规则是当前appId最大workerId + 1 //如果workerId已经到最大值了就拿最早的workerId(这里假设一个应用不会同时有1023(maxId)个实例在运行) Integer maxNodeId = instanceRepository.maxNodeIdByAppId(appId); if (maxNodeId == null || minId > maxNodeId) { return minId; } if (maxNodeId < maxId) { return maxNodeId + 1; } return getEarliestNodeId(appId);} private Integer getEarliestNodeId(String appId) { InstanceConfig instanceConfig = instanceConfigRepository.findTopByConfigAppIdOrderByReleaseDeliveryTime(appId); if (instanceConfig == null) { return DEFAULT_NODE_ID; } Instance instance = instanceRepository.findOne(instanceConfig.getInstanceId()); if (instance == null) { return DEFAULT_NODE_ID; } Integer nodeId = instance.getNodeId(); instance.setNodeId(null); instanceRepository.save(instance); return nodeId; } 三、使用方式和其他配置的使用方式一样,直接用@Value("${apollo.node.id}")。 这种方式生成的workerId可以直接使用在只需要一个workerId参数的雪花算法,另一种需要datacenterId和workerId两个参数的雪花算法在使用时需要做一下转换:12datacenterId=apollo.node.id >> 5workerId=apolo.node.id & 31 取高五位的值作为datacenterId,取低五位的值作为workerId。 一个workerId参数的雪花算法,workerId最大1023,二进制为1111111111; datacenterId和workerId两个参数的雪花算法,datacenterId最大31,二进制为11111,workerId最大31,二进制为11111,datacenterId作为二进制的高5位,workerId作为二进制的低5位,组合起来1111111111,十进制为1023。","categories":[{"name":"java","slug":"java","permalink":"http://sunshanpeng.com/categories/java/"}],"tags":[{"name":"apollo","slug":"apollo","permalink":"http://sunshanpeng.com/tags/apollo/"}]},{"title":"修改Kubernetes集群Pod容器的内核参数","slug":"修改Kubernetes集群Pod容器的内核参数","date":"2019-02-24T14:29:37.000Z","updated":"2019-08-16T18:26:30.880Z","comments":true,"path":"2019/02/24/修改Kubernetes集群Pod容器的内核参数/","link":"","permalink":"http://sunshanpeng.com/2019/02/24/修改Kubernetes集群Pod容器的内核参数/","excerpt":"","text":"容器的本质是一个进程,共享Node的内核。原以为修改了Node的内核参数容器中也会改,但实际上并不是这样,容器的内核参数可以和Node不同。 Docker Daemon1docker run -it --rm --sysctl net.core.somaxconn=65535 busybox cat /proc/sys/net/core/somaxconn 多个参数: 12345678910111213docker run -itd --restart=always --net=host \\--name=centos01 --hostname=centos01 \\--sysctl kernel.msgmnb=13107200 \\--sysctl kernel.msgmni=256 \\--sysctl kernel.msgmax=65536 \\--sysctl kernel.shmmax=69719476736 \\--sysctl kernel.sem='500 256000 250 1024' \\-v /mnt:/update \\centos /bin/bash docker exec centos01 sysctl -a |grep -E \\'kernel.msgmnb|kernel.msgmni|kernel.msgmax|kernel.shmmax|kernel.sem' KubernetesKubernetes Sysctls 增加Kubelet启动参数 1kubelet --allowed-unsafe-sysctls=net.* 重新加载配置 1systemctl daemon-reload 重启kubelet 1systemctl restart kubelet 配置Pod的securityContext参数(注意是pod不是container) 1234securityContext: sysctls: - name: net.ipv4.tcp_keepalive_time value: "180" Kubernetes允许配置的内核参数如下:12345kernel.shm*,kernel.msg*,kernel.sem,fs.mqueue.*,net.*. 使用Kubernetes Sysctls推荐指定几台Node,利用污点或者节点亲和性把需要修改内核参数的pod调度到指定节点,而不是修改所有Node。 Kubernetes Init Container使用init container不需要修改kubelet参数123456789initContainers:- command: - sysctl - -w - net.ipv4.tcp_keepalive_time=180 image: busybox:1.27 name: init-sysctl securityContext: privileged: true 参考资料 https://www.cnblogs.com/DaweiJ/articles/8528687.html https://yq.aliyun.com/articles/603745?utm_content=m_1000003707 https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/","categories":[{"name":"容器","slug":"容器","permalink":"http://sunshanpeng.com/categories/容器/"}],"tags":[{"name":"k8s","slug":"k8s","permalink":"http://sunshanpeng.com/tags/k8s/"}]},{"title":"使用同一个deployment实现金丝雀(灰度)发布","slug":"使用同一个deployment实现金丝雀(灰度)发布","date":"2019-02-24T14:29:37.000Z","updated":"2019-08-16T18:32:04.071Z","comments":true,"path":"2019/02/24/使用同一个deployment实现金丝雀(灰度)发布/","link":"","permalink":"http://sunshanpeng.com/2019/02/24/使用同一个deployment实现金丝雀(灰度)发布/","excerpt":"","text":"一、发布前发布前,nginx运行三个实例(pod),副本(replicaset)版本编号855b564c8d,此时运行状态如下:123nginx-855b564c8d-5qhk7 1/1 Running 0 12h 172.20.199.135 10.22.19.14 <none>nginx-855b564c8d-hclp9 1/1 Running 0 12h 172.20.235.210 10.22.19.5 <none>nginx-855b564c8d-n7kn8 1/1 Running 0 12h 172.20.35.53 10.22.19.17 <none> 二、发布中步骤一:发布此时进行发布,新副本版本编号77b6c7b585,此时发布状态如下:1234nginx-77b6c7b585-vzc9g 0/1 Running 0 21s 172.20.42.156 10.22.19.15 <none>nginx-855b564c8d-5qhk7 1/1 Running 0 12h 172.20.199.135 10.22.19.14 <none>nginx-855b564c8d-hclp9 1/1 Running 0 12h 172.20.235.210 10.22.19.5 <none>nginx-855b564c8d-n7kn8 1/1 Running 0 12h 172.20.35.53 10.22.19.17 <none> 步骤二:暂停执行暂停命令kubectl rollout pause deployment -n qa nginx 等待第一个实例运行成功,此时状态如下: 1234nginx-77b6c7b585-vzc9g 1/1 Running 0 1m 172.20.42.156 10.22.19.15 <none>nginx-855b564c8d-5qhk7 1/1 Running 0 12h 172.20.199.135 10.22.19.14 <none>nginx-855b564c8d-hclp9 1/1 Running 0 12h 172.20.235.210 10.22.19.5 <none>nginx-855b564c8d-n7kn8 1/1 Running 0 12h 172.20.35.53 10.22.19.17 <none> 此时为暂停发布状态,三个旧版本实例正常运行,一个新版本实例运行成功,流量将分摊到这四个实例上。 步骤三:恢复执行恢复命令kubectl rollout resume deployment -n qa nginx 此时状态如下:1234nginx-77b6c7b585-4bt6h 0/1 Running 0 33s 172.20.190.56 10.22.19.6 <none>nginx-77b6c7b585-g4wnx 1/1 Running 0 1m 172.20.42.172 10.22.19.15 <none>nginx-855b564c8d-5qhk7 1/1 Running 0 12h 172.20.199.135 10.22.19.14 <none>nginx-855b564c8d-n7kn8 1/1 Running 0 12h 172.20.35.53 10.22.19.17 <none> 恢复后会执行正常的滚动升级操作,即新增一个新副本实例,删除一个旧副本实例。滚动升级时,新旧版本同时存在,总实例最大数量不会超过目标数量的25%,最小数量不会低于目标数量的75%。 三、发布完成实例的副本版本编号都变成77b6c7b585,说明全部升级成功,此时状态如下:123nginx-77b6c7b585-4bt6h 1/1 Running 0 36m 172.20.190.56 10.22.19.6 <none>nginx-77b6c7b585-g4wnx 1/1 Running 0 36m 172.20.42.172 10.22.19.15 <none>nginx-77b6c7b585-kcsqm 1/1 Running 0 2m 172.20.235.253 10.22.19.5 <none> 四、回滚发布中发现功能与预期不符,需要取消发布并回滚至上个发布版本,此时可以执行回滚操作。执行回滚命令kubectl rollout undo deployment -n qa nginx。回滚操作在发布中,发布后都可以执行。 发布中回滚当暂停发布进行灰度验证时,发现功能不符合预期,需要取消发布。暂停时灰度验证状态:1234nginx-77b6c7b585-vzc9g 1/1 Running 0 2m 172.20.42.156 10.22.19.15 <none>nginx-855b564c8d-5qhk7 1/1 Running 0 12h 172.20.199.135 10.22.19.14 <none>nginx-855b564c8d-hclp9 1/1 Running 0 12h 172.20.235.210 10.22.19.5 <none>nginx-855b564c8d-n7kn8 1/1 Running 0 12h 172.20.35.53 10.22.19.17 <none> 回滚后状态:123nginx-855b564c8d-5qhk7 1/1 Running 0 12h 172.20.199.135 10.22.19.14 <none>nginx-855b564c8d-jqbjk 1/1 Running 0 2m 172.20.235.215 10.22.19.5 <none>nginx-855b564c8d-n7kn8 1/1 Running 0 12h 172.20.35.53 10.22.19.17 <none> 副本编号变成855b564c8d,说明回滚成功,恢复到新版本。 发布完成后回滚全部升级成功后,发现功能异常,回滚到之前版本。回滚前状态:123nginx-77b6c7b585-4bt6h 1/1 Running 0 36m 172.20.190.56 10.22.19.6 <none>nginx-77b6c7b585-g4wnx 1/1 Running 0 36m 172.20.42.172 10.22.19.15 <none>nginx-77b6c7b585-kcsqm 1/1 Running 0 2m 172.20.235.253 10.22.19.5 <none> 回滚后状态:123nginx-855b564c8d-5qhk7 1/1 Running 0 6m 172.20.199.135 10.22.19.14 <none>nginx-855b564c8d-jqbjk 1/1 Running 0 6m 172.20.235.215 10.22.19.5 <none>nginx-855b564c8d-n7kn8 1/1 Running 0 6m 172.20.35.53 10.22.19.17 <none> 滚动升级状态目标实例数3个,旧实例数3个,总实例数3个:123nginx-855b564c8d-5qhk7 1/1 Running 0 12h 172.20.199.135 10.22.19.14 <none>nginx-855b564c8d-hclp9 1/1 Running 0 12h 172.20.235.210 10.22.19.5 <none>nginx-855b564c8d-n7kn8 1/1 Running 0 12h 172.20.35.53 10.22.19.17 <none> 目标实例数3个,旧实例数3个,新实例数1个,总实例数4个:1234nginx-77b6c7b585-vzc9g 1/1 Running 0 1m 172.20.42.156 10.22.19.15 <none>nginx-855b564c8d-5qhk7 1/1 Running 0 12h 172.20.199.135 10.22.19.14 <none>nginx-855b564c8d-hclp9 1/1 Running 0 12h 172.20.235.210 10.22.19.5 <none>nginx-855b564c8d-n7kn8 1/1 Running 0 12h 172.20.35.53 10.22.19.17 <none> 目标实例数3个,旧实例数2个,新实例数2个,总实例数4个:1234nginx-77b6c7b585-pmw2f 1/1 Running 0 15s 172.20.190.51 10.22.19.6 <none>nginx-77b6c7b585-vzc9g 1/1 Running 0 3m 172.20.42.156 10.22.19.15 <none>nginx-855b564c8d-5qhk7 1/1 Running 0 12h 172.20.199.135 10.22.19.14 <none>nginx-855b564c8d-n7kn8 1/1 Running 0 12h 172.20.35.53 10.22.19.17 <none> 目标实例数3个,旧实例数1个,新实例数3个,总实例数4个1234nginx-77b6c7b585-4bt6h 1/1 Running 0 35m 172.20.190.56 10.22.19.6 <none>nginx-77b6c7b585-g4wnx 1/1 Running 0 35m 172.20.42.172 10.22.19.15 <none>nginx-77b6c7b585-kcsqm 1/1 Running 0 1m 172.20.235.253 10.22.19.5 <none>nginx-855b564c8d-5qhk7 1/1 Running 0 13h 172.20.199.135 10.22.19.14 <none> 目标实例数3个,新实例数3个,总实例数3个123nginx-77b6c7b585-4bt6h 1/1 Running 0 36m 172.20.190.56 10.22.19.6 <none>nginx-77b6c7b585-g4wnx 1/1 Running 0 36m 172.20.42.172 10.22.19.15 <none>nginx-77b6c7b585-kcsqm 1/1 Running 0 2m 172.20.235.253 10.22.19.5 <none>","categories":[{"name":"容器","slug":"容器","permalink":"http://sunshanpeng.com/categories/容器/"}],"tags":[{"name":"k8s","slug":"k8s","permalink":"http://sunshanpeng.com/tags/k8s/"}]},{"title":"配置Jenkins打包Vue项目","slug":"配置Jenkins打包VUE项目","date":"2019-02-24T14:29:37.000Z","updated":"2020-03-01T13:10:36.548Z","comments":true,"path":"2019/02/24/配置Jenkins打包VUE项目/","link":"","permalink":"http://sunshanpeng.com/2019/02/24/配置Jenkins打包VUE项目/","excerpt":"","text":"一、下载NodeJS插件进入插件管理 在可选插件里面安装NodeJS 等待安装完成 二、配置NodeJS插件进入全局工具配置 新增NodeJS 三、配置Job配置执行shell shell命令: 1234567# 打包node -vnpm -v npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedrivernpm installrm -rf distnpm run build cnpm: 123456789# 打包node -vnpm -v npm install -g cnpm --registry=https://registry.npm.taobao.org#cnpm install rimraf -g#rimraf node_modulescnpm installrm -rf distnpm run build 升级Vue: 1234567891011# 打包node -vnpm -v npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedrivernpm install -g @vue/clicnpm install -s vue-template-compilervue -Vrm -rf node_modulescnpm installrm -rf distnpm run build","categories":[{"name":"vue","slug":"vue","permalink":"http://sunshanpeng.com/categories/vue/"}],"tags":[{"name":"nodejs","slug":"nodejs","permalink":"http://sunshanpeng.com/tags/nodejs/"},{"name":"jenkins","slug":"jenkins","permalink":"http://sunshanpeng.com/tags/jenkins/"}]},{"title":"常用的IntelliJ IDEA 插件推荐","slug":"常用的IntelliJ IDEA 插件推荐","date":"2019-02-24T14:29:37.000Z","updated":"2019-08-25T10:40:47.180Z","comments":true,"path":"2019/02/24/常用的IntelliJ IDEA 插件推荐/","link":"","permalink":"http://sunshanpeng.com/2019/02/24/常用的IntelliJ IDEA 插件推荐/","excerpt":"","text":"一、Free Mybatis plugin https://plugins.jetbrains.com/plugin/8321-free-mybatis-plugin/ 1.把mybatis的xml文件和代码关联上,可以在mapper接口直接跳转到对应的SQL语句。 2.如果没有对应的SQL语句,可以快捷补全(当然具体SQL语句还是要自己写)。 二、Maven Helper https://plugins.jetbrains.com/plugin/7179-maven-helper/ 简单、方便的查看maven依赖和冲突 三、GenerateAllSetter https://plugins.jetbrains.com/plugin/9360-generateallsetter/ 一键调用一个对象的所有的set方法 四、RestfulToolkit https://plugins.jetbrains.com/plugin/10292-restfultoolkit/ 通过URL查询对应的方法,可以根据请求方法过滤,另外支持简单的HTTP调用。 五、Key Promoter X https://plugins.jetbrains.com/plugin/9792-key-promoter-x/ 帮助我们快速的掌握 IntelliJ IDEA的快捷键。 六、GsonFormat https://plugins.jetbrains.com/plugin/7654-gsonformat/ 快速方便的把json字符串转换为实体类(快捷键ALT+S)。 七、String Manipulation https://plugins.jetbrains.com/plugin/2162-string-manipulation/ 快捷方便的把字符串在驼峰、大小写等格式之间转换(快捷键Alt+M)。 八、Alibaba Java Coding Guidelines https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines/ 扫描代码是否符合规范 九、Lombok https://plugins.jetbrains.com/plugin/6317-lombok/ 简化java bean的代码量,自动生成get set toString EqualsAndHashCode 构造器。 需要引入依赖包 12345> <dependency> > <groupId>org.projectlombok</groupId> > <artifactId>lombok</artifactId> > </dependency>> 十、.ignore https://plugins.jetbrains.com/plugin/7495--ignore/ 使用协作或者打包工具时,方便的处理要忽略的文件和文件夹。 十一、Vue.js https://plugins.jetbrains.com/plugin/9442-vue-js/ 用IDEA开发Vue的插件。 十二、element https://plugins.jetbrains.com/plugin/10524-element/ 饿了么开源前端框架element的插件。","categories":[{"name":"java","slug":"java","permalink":"http://sunshanpeng.com/categories/java/"}],"tags":[{"name":"idea","slug":"idea","permalink":"http://sunshanpeng.com/tags/idea/"}]},{"title":"Docker的本质","slug":"Docker的本质","date":"2019-02-24T14:26:37.000Z","updated":"2019-08-16T18:16:25.991Z","comments":true,"path":"2019/02/24/Docker的本质/","link":"","permalink":"http://sunshanpeng.com/2019/02/24/Docker的本质/","excerpt":"","text":"Docker容器其实是一种沙盒技术,能够像一个集装箱一样,把应用“装”起来。 这样应用与应用之间,就因为有了边界而不会相互干扰; 被装进集装箱的应用,也可以被方便地搬来搬去。 容器的本质,是由Namespace、Cgroups和rootfs三种技术构建出来的进程的隔离环境。 Docke容器技术的核心功能,是通过约束和修改进程的动态表现,从而为其创造出一个“边界”。 Namespace技术是用来修改进程视图的主要方法,Cgroups技术是用来制造约束的主要手段,rootfs技术是用来限制进程文件系统的主要方式。 Namespace:正常情况下,每当我们在宿主机上运行一个程序的时候,操作系统都会给它分配一个进程编号,比如PID=100。这是进程的唯一标识,就像员工的工号一样。 而通过Docker把程序运行在容器中的时候,利用Namespace技术给进程施一个“障眼法”,让它看不到其他的进程,误以为只有它一个进程。 这种机制对被隔离的应用的进程空间做了限制,使其只能看到重新计算过的进程编号,比如PID=1,但事实上它在宿主机的操作系统里,还是原来的100号进程。 除了PID Namespace,Linux系统还提供了Mount、UTS、IPC、Network和User这些Namespace,用来对各种不同的进程上下文进行“障眼法”操作。 比如用Mount Namespace让被隔离的进程只能看到当前Namespace里的挂载点信息;Network Namespace让被隔离的进程只能看到当前Namespace的网络设备和配置。 所以,实际上Docker容器只是指定了这个进程所需要启用的一组Namespace参数,从而让进程只能看到当前Namespace所限定的资源、文件、设备、状态和配置等,对宿主机和其他不相关的程序完全看不到。 所以也可以说,容器其实就是一种特殊的进程而已。 虚拟机和Docker容器比较 在理解Docker的时候,通常会用这张图来比较虚拟机和Docker。 左边画出了虚拟机的工作原理:其中Hypervisor是虚拟机最主要的部分,它通过硬件虚拟化功能,模拟出运行一个操作系统需要的各种硬件,比如CPU、内存、I/O设备等等。然后它在这些虚拟的硬件上安装了一个新的操作系统,即Guest OS。 这样,用户的应用进程就可以运行在这个虚拟机的机器中,它能够看到的自然也只有Guest OS的文件和目录,以及这个机器里的虚拟设备。 右边用一个名为Docker Engine的软件替换了Hypervisor,把虚拟机的概念套在了容器上,实际上这个说法是不严谨的,跟真正存在的虚拟机不同,实际上并没有Docker Engine这一层。 Docker帮助用户启动的还是原来的应用进程,只不过在创建这些进程时给它们加上了各种各样的Namespace参数。 这样,这些进程就会觉得自己是各自PID Namespace里的第1号进程,只能看到各自Mount Namespace里挂载的目录和文件,只能访问各自Network Namespace里的网络设备,就好像运行在一个个独立的容器里。 Docker的架构其实这样画更合适 如果用虚拟化技术作为应用沙盒,就必须要由Hypervisor来负责创建虚拟机,这个虚拟机是真实存在的,并且它里面必须运行一个完整的Guest OS才能执行用户的应用进程。这就不可避免的带来的额外的资源消耗和占用。 根据实验,一个运行着CentOS的KVM虚拟机启动后,在不做优化的情况下,虚拟机自己就需要占用100~·200内存。此外,用户应用运行在虚拟机里面,对宿主机操作系统的调用就不可避免地进过虚拟化软件的拦截和处理,这本身又是一层性能损失。 使用Docker容器化后的用户应用,依然还是一个宿主机上的普通进程,这就意味着因为虚拟化而带来的性能损耗都是不存在的; 另一方面,使用Namespace作为隔离手段的容器并不需要单独的Guest OS,这使得容器额外的资源占用几乎可以忽略不计。 所以“敏捷”和“高性能”是容器相较于虚拟化最大的优势。 当然有利就有弊,Docker容器相较于虚拟化最大的弊端就是隔离的不彻底。 因为容器只是运行在宿主机上的一种特殊的进程,所以多个容器之间使用的还是同一个宿主机的操作系统内核。 另外,很多资源和对象不能被Namespace化,比如:时间。 Cgroups:虽然容器内的进程在Namespace的作用下只能看到容器里的情况,但是在宿主机上,它作为第100号进程,与其它所有的进程之间依然是平等的竞争关系。 这就意味着,虽然这个进程表面上被隔离起来了,但它所能够使用到的资源(CPU、内存),却是可以随时被宿主机的其他进程(或者其他容器)占用。 当然这个进程也可能把所有的资源吃光,而Linux Cgroups就是Linux内核中用来为进程设置资源限制的一个重要功能。 跟Namespace的情况类似,Cgroups对资源的限制能力也有很多不完善的地方,比如/proc文件系统的问题。 /proc目录存储的是记录当前内核运行状态的一系列特殊文件,用户可以通过访问这些文件,查看系统以及当前正在运行的进程的信息,比如CPU使用情况,内存占用率等。 当我们在容器内执行top命令的时候,会发现它显示的信息是宿主机的CPU和内存,而不是当前容器的数据。 rootfs:在Linux操作系统里,有一个名为chroot的命令,作用是帮助我们“change root file”,即改变进程的根目录到指定的位置。 而对于被chroot的进程来说,它并不知道自己的根目录已经被修改了。Mount Namespace就是基于chroot的不断改良被发明出来的。 为了让容器的根目录更加真实,一般会在这个容器的根目录下挂载一个完整操作系统的文件系统,比如Ubuntu 16.04的ISO。 这样,在容器启动之后,我们在容器里通过执行“ls /”查看根目录下的内容,就是Ubuntu 16.04的所有目录和文件。 这个挂载在容器根目录上,用来为容器进程提供隔离后执行环境的文件系统,就是容器镜像,专业名字rootfs(根文件系统)。 一个常见的rootfs,或者说容器镜像,会包含比如bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys test tmp usr var等的目录和文件。 进入容器后执行的/bin/bash,就是/bin目录下的可执行文件,与宿主机的/bin/bash完全不同。 rootfs只是一个操作系统所包含的文件、配置和目录,并不包括操作系统内核。 所以说rootfs只包括了操作系统的“躯壳”,并没有包括操作系统的“灵魂”,同一台机器上的所有容器,都是共享宿主机操作系统的内核。 这意味着,容器进程需要配置内核参数、加载额外的内核模块,以及跟内核进行直接交互的时候,需要注意这些操作和依赖的对象,都是宿主机操作系统的内核,对该机器上的所有容器生效。 不过也真是由于rootfs,容器才有了一个被反复宣传至今的重要特性:一致性。 由于rootfs里打包的不只是应用,而是整个操作系统的文件和目录,也就意味着,应用以及它运行所需要的所有依赖,都被封装在了一起。 有了容器镜像“打包操作系统”的能力,这个最基础的依赖环境也变成了应用沙盒的一部分,这就赋予了容器的所谓一致性。 无论在本地、云端,还是在任何机器上,用户只需要解压打包好的容器镜像,那么这个应用运行所需要的完整的执行环境就被重现出来了。 这种深入到操作系统级别的运行环境一致性,打通了应用在本地开发和远端执行环境之间难以逾越的鸿沟。 另外,Docker在镜像的设计中,引入了层(layer)的概念,也就是说,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量的rootfs。 这个设计用到了一种叫做联合文件系统(Union File System)的能力,也叫UnionFS,最主要的功能是将多个不同位置的目录联合挂载到同一个目录下。 如图所示,容器中的rootfs分为只读层、Init层、可读写层。 只读层:包含了操作系统的一部分。 Init层:夹在只读层和可读写层中间,是Docker项目单独生成的一个内部层,用来存在/etc/hosts、/etc/resolv.conf等信息。 可读写层:用来存放修改rootfs后产生的增量,无论增、删、改,都发生在这里。 使用docker commit和push指令保存并上传被修改过的容器的时候,只读层和Init层不会有任何变化,变化的只是可读写层。 Dockerfile实际操作中,我们不会使用docker commit命令来把一个运行中的docker容器提交成为一个docker镜像。 使用 docker commit 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为黑箱镜像,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。 另外结合之前的分层,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。 这样每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到。 把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决,这个脚本就是 Dockerfile。 Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。 每一个 RUN 的行为,就和手动docker commit建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。 Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。","categories":[{"name":"容器","slug":"容器","permalink":"http://sunshanpeng.com/categories/容器/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://sunshanpeng.com/tags/docker/"}]},{"title":"Kubectl常用命令","slug":"Kubectl常用命令","date":"2019-02-24T14:26:37.000Z","updated":"2019-08-16T18:19:38.363Z","comments":true,"path":"2019/02/24/Kubectl常用命令/","link":"","permalink":"http://sunshanpeng.com/2019/02/24/Kubectl常用命令/","excerpt":"","text":"运行容器前台运行的容器1kubectl run -it --rm --image=centos --restart=Never test bash 常驻后台的容器1kubectl run nginx --image=nginx --replicas=2 通常使用yaml文件创建容器,只在调试或者排错的时候使用kubectl run临时创建容器。 比如: 创建网络排查容器1kubectl run -it --rm --restart=Never --image=infoblox/dnstools:latest dnstools 如果创建了一个MySQL容器并且暴露了service,但是不能使用service访问mysql,这个时候可以使用nslookup或者dig这些网络命令排查。 123456dnstools# nslookup mysqlServer: 172.21.0.2Address: 172.21.0.2#53Name: mysql.qa.svc.cluster.localAddress: 172.21.147.86 创建MySQL排查容器1kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql --port 3306 -u root -p123456 如果创建的MySQL容器在集群外连接不上了,可以创建一个mysql容器在内部连接看看能不能连上。 -h参数是MySQL的host; --port参数是MySQL的端口号; -u参数是MySQL的用户名; -p参数是用户名对应的密码,-p和密码之间没有空格。 应用资源1kubectl apply -f deployment.yaml 推荐使用这种方式创建或者更新资源。 获取容器1kubectl get pods --all-namespaces -o wide 获取所有namespace的容器。 --all-namespaces表示所有namespace,默认获取default namespace的资源,可以通过-n指定namespace。 -o wide表示输出格式,其他还要json,yaml。 获取所有不是Running状态的容器1kubectl get pods --all-namespaces -o wide | awk '{if ($4 != \"Running\") print $0}' 获取其他资源除了容器,还有很多其他资源例如node、service、deployment、statefulset、daemonset、job、pvc、pv等等可以通过kubectl来获取,大多数是区分namespace的,也有不区分namespace的比如node、pv、storageclass等。 删除容器1kubectl delete pod nginx-deployment-599c95f496-hd2jc 强制删除:1kubectl delete pod nginx-deployment-599c95f496-hd2jc --force --grace-period=0 批量强制删除:1kubectl get pods | grep Terminating | awk '{print $1}' | xargs kubectl delete pod --force --grace-period=0 批量强制删除非运行容器1kubectl get pods --all-namespaces | awk '{if ($4 != \"Running\") system (\"kubectl -n \" $1 \" delete pods \" $2 \" --grace-period=0 \" \" --force \")}' 扩缩容1kubectl scale deployment nginx --replicas 4 最小可以缩容到0个。 暂停滚动升级1kubectl rollout pause deployment nginx 滚动升级时,可以使用该命令暂停升级来实现金丝雀发布。 恢复滚动升级1kubectl rollout resume deployment nginx 暂停后恢复。 回滚1kubectl rollout undo deployment nginx 回滚到上一次发布。 给node加taint(污点)1kubectl taint nodes node1 key=value:NoSchedule 给节点添加污点后只有容忍了该污点的容器才能调度上来。 查看node信息1kubectl describe nodes node1 通过该命令可以查看node的资源、内核、容器、标签和污点等等。 给node加标签1kubectl label node node1 kubernetes.io/role=node --overwrite 给node加标签后可以用节点亲和性指定某些pod调度到固定node。 删除node上的标签1kubectl label node node1 kubernetes.io/role- 根据标签筛选1kubectl get nodes -l node-type=iot 禁止节点调度只禁止不驱逐1kubectl cordon node1 这种方式只把node标记为SchedulingDisabled,已经在node上运行的pod不会受影响,之后不会再有新的pod调度上去。 禁止并驱逐1kubectl drain node1 --ignore-daemonsets --delete-local-data --force 这种方式除了把node标记为SchedulingDisabled,已经运行的pod也会被驱逐,保证节点除daemonset外没有其他pod。 恢复调度1kubectl uncordon node1","categories":[{"name":"容器","slug":"容器","permalink":"http://sunshanpeng.com/categories/容器/"}],"tags":[{"name":"k8s","slug":"k8s","permalink":"http://sunshanpeng.com/tags/k8s/"}]},{"title":"CentOS升级内核kernel的几种方式","slug":"CentOS升级内核kernel的几种方式","date":"2019-01-17T12:18:01.000Z","updated":"2019-08-18T14:17:48.357Z","comments":true,"path":"2019/01/17/CentOS升级内核kernel的几种方式/","link":"","permalink":"http://sunshanpeng.com/2019/01/17/CentOS升级内核kernel的几种方式/","excerpt":"","text":"小版本升级1234# installyum install kernel* -y# rebootinit 6 安装最新稳定版内核1234567891011121314# import keyrpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org# install elrepo reporpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm# list kernelyum --disablerepo=\\* --enablerepo=elrepo-kernel list kernel*# install kernelyum --enablerepo=elrepo-kernel install kernel-ml-devel kernel-ml -y# yum --disablerepo=\\* --enablerepo=elrepo-kernel install -y kernel-ml.x86_64# modify grubgrub2-set-default 0grub2-mkconfig -o /boot/grub2/grub.cfg# rebootreboot 安装指定版本内核推荐一个可以找到各个版本内核的国内镜像站:http://mirror.rc.usf.edu/compute_lock/elrepo/kernel/el7/x86_64/RPMS 本次安装以4.19版本的内核示例: 1234567# installrpm -ivh http://mirror.rc.usf.edu/compute_lock/elrepo/kernel/el7/x86_64/RPMS/kernel-ml-4.19.12-1.el7.elrepo.x86_64.rpm# modify grubsed -i \"s/GRUB_DEFAULT=saved/GRUB_DEFAULT=0/\" /etc/default/grubgrub2-mkconfig -o /boot/grub2/grub.cfg# rebootreboot 编译源码安装没试过 参考https://mritd.me/2016/11/08/update-centos-kernel/","categories":[{"name":"linux","slug":"linux","permalink":"http://sunshanpeng.com/categories/linux/"}],"tags":[{"name":"kernel","slug":"kernel","permalink":"http://sunshanpeng.com/tags/kernel/"}]},{"title":"创建NFS的StorageClass","slug":"创建NFS的StorageClass","date":"2018-12-17T08:26:37.000Z","updated":"2019-08-18T13:54:01.327Z","comments":true,"path":"2018/12/17/创建NFS的StorageClass/","link":"","permalink":"http://sunshanpeng.com/2018/12/17/创建NFS的StorageClass/","excerpt":"","text":"前言在Kubernetes的几种网络存储中,NFS是成本较低、使用简单的一种方案。 但是NFS存储不建议用在生产环境,因为我们测试环境的MySQL数据库部署在NFS上都经常出问题,比如nfs4_reclaim_open_state: Lock reclaim failed和kernel:NMI watchdog: BUG: soft lockup - CPU#0 stuck for 26s。 NFS服务端12345678910111213141516171819yum install -y net-tools lsof nfs-utils rpcbindmkdir /data/nfs -pvi /etc/exports#挂载NFS服务器上的/data/nfs/目录到自己的文件系统中,rw表示可读写,no_root_squash 是让root保持权限/data/nfs/ *(insecure,rw,no_root_squash)关闭防火墙systemctl stop firewalld先为rpcbind和nfs做开机启动:(必须先启动rpcbind服务)systemctl enable rpcbind.servicesystemctl enable nfs-server.service然后分别启动rpcbind和nfs服务:systemctl start rpcbind.servicesystemctl start nfs-server.service exportfs -r#可以查看到已经okexportfs/home/nfs 192.168.248.0/24 NFS客户端123456789101112#安装nfs工具yum install -y nfs-utils#建立挂载目录mkdir /data#挂载nfsmount -t nfs 192.168.80.145:/data/nfs /data卸载挂载umount /data 查看是目录挂载状态df -hshowmount -e 192.168.80.145 创建NFS-StorageClassnfs-rbac.yaml123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657kind: ServiceAccountapiVersion: v1metadata: name: nfs-client-provisioner---kind: ClusterRoleapiVersion: rbac.authorization.k8s.io/v1metadata: name: nfs-client-provisioner-runnerrules: - apiGroups: [\"\"] resources: [\"persistentvolumes\"] verbs: [\"get\", \"list\", \"watch\", \"create\", \"delete\"] - apiGroups: [\"\"] resources: [\"persistentvolumeclaims\"] verbs: [\"get\", \"list\", \"watch\", \"update\"] - apiGroups: [\"storage.k8s.io\"] resources: [\"storageclasses\"] verbs: [\"get\", \"list\", \"watch\"] - apiGroups: [\"\"] resources: [\"events\"] verbs: [\"create\", \"update\", \"patch\"]---kind: ClusterRoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata: name: run-nfs-client-provisionersubjects: - kind: ServiceAccount name: nfs-client-provisioner namespace: defaultroleRef: kind: ClusterRole name: nfs-client-provisioner-runner apiGroup: rbac.authorization.k8s.io---kind: RoleapiVersion: rbac.authorization.k8s.io/v1metadata: name: leader-locking-nfs-client-provisionerrules: - apiGroups: [\"\"] resources: [\"endpoints\"] verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\", \"patch\"]---kind: RoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata: name: leader-locking-nfs-client-provisionersubjects: - kind: ServiceAccount name: nfs-client-provisioner namespace: defaultroleRef: kind: Role name: leader-locking-nfs-client-provisioner apiGroup: rbac.authorization.k8s.io nfs-deployment.yaml1234567891011121314151617181920212223242526272829303132333435363738kind: DeploymentapiVersion: extensions/v1beta1metadata: name: nfs-client-provisionerspec: replicas: 1 strategy: type: Recreate template: metadata: labels: app: nfs-client-provisioner spec: tolerations: - key: node-env value: pre effect: NoSchedule operator: Equal priorityClassName: cluster serviceAccountName: nfs-client-provisioner containers: - name: nfs-client-provisioner image: quay.io/external_storage/nfs-client-provisioner:latest volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: nfs-provisioner - name: NFS_SERVER value: ##NFS_SERVER_IP## - name: NFS_PATH value: ##NFS_PATH## volumes: - name: nfs-client-root nfs: server: ##NFS_SERVER_IP## path: ##NFS_PATH## nfs-storage.yaml1234567apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: nfs-storageprovisioner: nfs-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'parameters: archiveOnDelete: \"false\" test-nfs-storage.yaml123456789101112131415161718192021222324252627282930313233343536kind: PersistentVolumeClaimapiVersion: v1metadata: name: test-claim annotations: volume.beta.kubernetes.io/storage-class: \"nfs-storage\"spec: accessModes: - ReadWriteMany resources: requests: storage: 1Mi---kind: PodapiVersion: v1metadata: name: test-podspec: containers: - name: test-pod image: busybox command: - \"/bin/sh\" args: - \"-c\" - \"touch /mnt/SUCCESS && exit 0 || exit 1\" volumeMounts: - name: nfs-pvc mountPath: \"/mnt\" restartPolicy: \"Never\" volumes: - name: nfs-pvc persistentVolumeClaim: claimName: test-claim 创建顺序 nfs-rbac.yaml nfs-deployment.yaml nfs-storage.yaml 参数说明nfs-deployment.yaml ##NFS_SERVER_IP##是NFS服务端的IP,根据实际IP进行替换。 ##NFS_PATH##是NFS服务端的目录,根据实际目录进行替换。 部署12345NFS_SERVER_IP=192.168.80.145 #换成自己的实际IPNFS_PATH=/data/nfs # 换成自己的实际目录kubectl apply -f nfs-rbac.yamlsed \"s|##NFS_SERVER_IP##|${NFS_SERVER_IP}|g;s|##NFS_PATH##|${NFS_PATH}|g\" nfs-deployment.yaml | kubectl apply -f -kubectl apply -f nfs-storage.yaml 验证方法1kubectl apply -f test-nfs-storage.yaml 可选:设置默认存储设置这个StorageClass为Kubernetes的默认存储1234 kubectl patch storageclass nfs-storage -p '{\"metadata\": {\"annotations\":{\"storageclass.kubernetes.io/is-default-class\":\"true\"}}}' [root@master1 nfs-storage]# kubectl get scNAME PROVISIONER AGEnfs-storage (default) nfs-provisioner 12m 参考https://www.cnblogs.com/lixiuran/p/7117000.html https://github.com/kubernetes-incubator/external-storage/tree/master/nfs-client","categories":[{"name":"容器","slug":"容器","permalink":"http://sunshanpeng.com/categories/容器/"}],"tags":[{"name":"k8s","slug":"k8s","permalink":"http://sunshanpeng.com/tags/k8s/"}]},{"title":"使用ephemeral-storage管理容器的临时存储","slug":"使用ephemeral-storage管理容器的临时存储","date":"2018-11-28T08:26:37.000Z","updated":"2019-08-18T11:55:24.670Z","comments":true,"path":"2018/11/28/使用ephemeral-storage管理容器的临时存储/","link":"","permalink":"http://sunshanpeng.com/2018/11/28/使用ephemeral-storage管理容器的临时存储/","excerpt":"","text":"ephemeral-storage介绍Kubernetes在1.8的版本中引入了一种类似于CPU,内存的新的资源模式:ephemeral-storage,并且在1.10的版本在kubelet中默认打开了这个特性。 Alpha release target (x.y): 1.7/1.8 Beta release target (x.y): 1.10 Stable release target (x.y): 1.11 ephemeral-storage是为了管理和调度Kubernetes中运行的应用的短暂存储。 在每个Kubernetes的节点上,kubelet的根目录(默认是/var/lib/kubelet)和日志目录(/var/log)保存在节点的主分区上,这个分区同时也会被Pod的EmptyDir类型的volume、容器日志、镜像的层、容器的可写层所占用。ephemeral-storage便是对这块主分区进行管理,通过应用定义的需求(requests)和约束(limits)来调度和管理节点上的应用对主分区的消耗。 ephemeral-storage的eviction逻辑在节点上的kubelet启动的时候,kubelet会统计当前节点的主分区的可分配的磁盘资源,或者你可以覆盖节点上kubelet的配置来自定义可分配的资源。在创建Pod时会根据存储需求调度到满足存储的节点,在Pod使用超过限制的存储时会对其做驱逐的处理来保证不会耗尽节点上的磁盘空间。 如果运行时指定了别的独立的分区,比如修改了docker的镜像层和容器可写层的存储位置(默认是/var/lib/docker)所在的分区,将不再将其计入ephemeral-storage的消耗。 EmptyDir 的使用量超过了他的 SizeLimit,那么这个 pod 将会被驱逐 Container 的使用量(log,如果没有 overlay 分区,则包括 imagefs)超过了他的 limit,则这个 pod 会被驱逐 Pod 对本地临时存储总的使用量(所有 emptydir 和 container)超过了 pod 中所有container 的 limit 之和,则 pod 被驱逐 ephemeral-storage使用和内存和CPU的限制类似,存储的限制也是定义在Pod的container中 spec.containers[].resources.limits.ephemeral-storage spec.containers[].resources.requests.ephemeral-storage 示例: 12345678910111213141516apiVersion: v1kind: Podmetadata: name: teststorage labels: app: teststoragespec: containers: - name: teststorage image: nginx:1.14 command: [\"bash\", \"-c\", \"while true; do dd if=/dev/zero of=$(date '+%s').out count=1 bs=10MB; sleep 1; done\"] # 持续写入文件到容器的rootfs中 resources: limits: ephemeral-storage: 100Mi #定义存储的限制为100M requests: ephemeral-storage: 100Mi 12345[root@master1 ~]# kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODEteststorage 1/1 Running 0 7s 172.20.189.69 10.208.204.35 <none>-------------------------------------------------------------------------------------------------teststorage 0/1 Evicted 0 1m <none> 10.208.204.35 <none> 1234567891011121314151617181920212223242526272829303132333435363738394041424344[root@master1 ~]# kubectl describe pod teststorage Name: teststorageNamespace: defaultNode: 10.208.204.35/Start Time: Wed, 28 Nov 2018 13:48:37 +0800Labels: app=teststorageAnnotations: kubectl.kubernetes.io/last-applied-configuration={\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"teststorage\"},\"name\":\"teststorage\",\"namespace\":\"default\"},\"spec\":{\"contai...Status: FailedReason: EvictedMessage: Pod ephemeral local storage usage exceeds the total limit of containers 100Mi. IP: Containers: teststorage: Image: nginx:1.14 Port: <none> Host Port: <none> Command: bash -c while true; do dd if=/dev/zero of=$(date '+%s').out count=1 bs=10MB; sleep 1; done Limits: ephemeral-storage: 100Mi Requests: ephemeral-storage: 100Mi Environment: <none> Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-mqzrh (ro)Volumes: default-token-mqzrh: Type: Secret (a volume populated by a Secret) SecretName: default-token-mqzrh Optional: falseQoS Class: BestEffortNode-Selectors: <none>Tolerations: <none>Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 1m default-scheduler Successfully assigned default/teststorage to 10.208.204.35 Normal Pulled 1m kubelet, 10.208.204.35 Container image \"nginx:1.14\" already present on machine Normal Created 1m kubelet, 10.208.204.35 Created container Normal Started 1m kubelet, 10.208.204.35 Started container Warning Evicted 8s kubelet, 10.208.204.35 Pod ephemeral local storage usage exceeds the total limit of containers 100Mi. Normal Killing 8s kubelet, 10.208.204.35 Killing container with id docker://teststorage:Need to kill Pod 参考文档:https://v1-11.docs.kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/ https://github.com/kubernetes/enhancements/issues/361 https://yq.aliyun.com/articles/594066 http://www.k8smeetup.com/article/VyEncpgA7","categories":[{"name":"容器","slug":"容器","permalink":"http://sunshanpeng.com/categories/容器/"}],"tags":[{"name":"k8s","slug":"k8s","permalink":"http://sunshanpeng.com/tags/k8s/"}]},{"title":"下载谷歌镜像的几种姿势","slug":"下载谷歌镜像的几种姿势","date":"2018-11-28T08:26:37.000Z","updated":"2019-08-25T10:40:47.179Z","comments":true,"path":"2018/11/28/下载谷歌镜像的几种姿势/","link":"","permalink":"http://sunshanpeng.com/2018/11/28/下载谷歌镜像的几种姿势/","excerpt":"","text":"前言在国内,因为墙的存在所以很多国外网站不能访问,这其中就有谷歌镜像网站gcr.io,不过我们可以通过其他方式使用谷歌的镜像。 通过国内镜像站阿里云镜像站域名:registry.cn-hangzhou.aliyuncs.com/google_containers 微软镜像站域名:gcr.azk8s.cn/google_containers 中科大镜像站(拉取速度较慢)域名:gcr.mirrors.ustc.edu.cn/google_containers 使用方式替换谷歌镜像地址为国内镜像站地址,比如: k8s.gcr.io开头的k8s.gcr.io/coredns:1.1.3 12docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:1.1.3docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:1.1.3 k8s.gcr.io/coredns:1.1.3 gcr.io开头的gcr.io/google_containers/heapster-amd64:v1.5.3 12docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/heapster-amd64:v1.5.3docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/heapster-amd64:v1.5.3 gcr.io/google_containers/heapster-amd64:v1.5.3 通过配置代理前提是有一台能够科学上网的机器,并且目标机器能访问到可以科学上网的机器。 为docker服务创建systemd插件目录: 1mkdir -p /etc/systemd/system/docker.service.d 配置代理文件 1234cat >/etc/systemd/system/docker.service.d/http-proxy.conf<EOF [Service] Environment=\"HTTP_PROXY=http://proxy.example.com:80/\"EOF 刷新配置并重启Docker 12systemctl daemon-reloadsystemctl restart docker 验证配置是否加载 12systemctl show --property=Environment dockerEnvironment=HTTP_PROXY=http://proxy.example.com:80/ 如果配置已经加载但还是不能下载谷歌镜像,可以试试把HTTP_PROXY改成http_proxy,我的配置是Environment="http_proxy=http://10.208.204.147:1080/"才能使用。 HTTPS_PROXY和NO_PROXY配置类似,具体可以看官网代理配置。 通过脚本1curl -sSL https://git.io/getgcr | bash -s k8s.gcr.io/kube-apiserver:v1.14.3 把k8s.gcr.io/kube-apiserver:v1.14.3替换成要下载的目标镜像即可。 该方法本质上还是通过国内镜像站下载的。 参考http://mirror.azure.cn/help/gcr-proxy-cache.html https://blog.docker.com/2015/10/registry-proxy-cache-docker-open-source/ https://stackoverflow.com/questions/23111631/cannot-download-docker-images-behind-a-proxy","categories":[{"name":"容器","slug":"容器","permalink":"http://sunshanpeng.com/categories/容器/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://sunshanpeng.com/tags/docker/"}]},{"title":"","slug":"Service的意义","date":"2018-10-21T15:06:09.474Z","updated":"2019-06-09T06:59:54.193Z","comments":true,"path":"2018/10/21/Service的意义/","link":"","permalink":"http://sunshanpeng.com/2018/10/21/Service的意义/","excerpt":"","text":"在用 Kubernetes 之前,当我们有了容器网络之后,访问一个应用最直接的做法,就是客户端直接去访问一个 Backend Container。 这种做法最直观和容易,同时问题也是显而易见的。 当应用有多个后端容器的时候,怎么做负载均衡,会话保持怎么做,某个容器迁了之后 IP 跟着变怎么办,还有对应的健康检查怎么配,如果想用域名来做访问入口要怎么处理…… 这些其实就是 Kubernetes 的 Service 引入所要解决的问题。 Service的意义Kubernetes Pod 是有生命周期的,它们可以被创建,也可以被销毁,然而一旦被销毁生命就永远结束。 通过 ReplicationController能够动态地创建和销毁 Pod。 每个 Pod 都会获取它自己的 IP 地址,即使这些 IP 地址不总是稳定可依赖的。 这会导致一个问题:在 Kubernetes 集群中,如果一组 Pod(称为 backend)为其它 Pod (称为 frontend)提供服务,那么那些 frontend 该如何发现,并连接到这组 Pod中的哪些 backend 呢? Kubernetes Service 定义了这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。","categories":[],"tags":[]}]}