应用错误跟踪系统:对软件系统运行过程中产生的错误日志进行收集从而实现监控告警。

虽然软件错误❌是不可避免的,但是可以降低错误数。

提高对错误的治理能力能让错误带来的损失降到最低 ​👍🏻 。

错误日志监控在最顶层的业务层监控,有他将会帮助你打造更好的软件!

我们需要业务场景下自己发现Bug的速度快于用户报告Bug的速度,毕竟让用户报告已经晚了。

典型的这套系统的架构:错误日志(前端、后端) => 传输(HTTP)=>错误跟踪平台(收集、展示、分析、告警)。

本文不讨论哪款软件来解决这个问题,只讲Sentry这个轮子,SASS版本和私有部署版体验几乎一致

img

本文主要讲了

应用错误需要监控的场景案例

Sentry有哪些功能

Sentry如何在k8s中私有化部署

本文由 www.iamle.com 流水理鱼 原创,wx公众号搜索 流水理鱼 或 liushuiliyu

SEO 关键字

Sentry私有化部署

Sentry helm部署

Sentry kubernets部署 k8s部署

1. 需要应用错误监控的场景案例

  • 多年的老web项目需要https适配,因为老项目大量写死了http://所以文件修改数巨大,即便测试很仔细也不能保证完全没问题
  • 运营发现落地页转化异常了,有访问没表单提交,找到技术,技术发现是某个JS报错,导致表单无法提交,造成推广费用的损失
  • 上线了一个功能,由于环境差异,只在生产环境才触发,用户报告了才去查问题
  • 应用错误日志虽然通过ELK采集了,但是缺乏及时分析和告警
  • 特定的环境才产生错误,要解决问题的先去构建环境并复现错误,因为不清楚发生错误时的具体参数

2. Sentry有哪些功能

Sentry is cross-platform application monitoring, with a focus on error reporting.

Sentry跨平台应用监控,专注错误报告。

Sentry英文直译中文叫“哨兵”。

Sentry提供了一个应用程序监视平台,可以帮助您实时识别问题。

  • 提供WEB UI
  • 提供SASS版和私有部署2种方式
  • 开源,授权协议为BSL,只要你不拿他来做SASS服务卖钱,自用可以免费商用
  • 提供几乎所有主流开发语言和框架的SDK
  • 提供完整的错误详情
  • 支持自动上报错误和手动上报错误
  • 支持WEB前端、后端、APP
  • 支持多项目管理
  • 支持账号权限管理
  • 提供统一错误的聚合分析

  • 今日头条等很多大公司都在用

img

3. 在Kubernets中部署Sentry

目前网上大部分都是讲DockerCompose的部署方式

其实Helm Hub上可以找到Sentry的helm charts包(⑤),那么直接用Helm部署Sentry是最快速方便的

3.1 Sentry部署资源要求

下面给出一个部署资源情况参考,这基本也就是最低资源消耗情况了

  • Kubernets存储已经支持了动态PVC

  • Kubernets已经支持Ingress

  • 准备一个子域名 (例如, sentry.iamle.com)

  • Helm部署Sentry会部署sentry-corn、sentry-web()、sentry-worker、Redis、PostgreSQL

  • 空负载资源详细sentry-corn(220MB/0.01Core)、sentry-web(850MB/0.012Core)、sentry-worker(2048MB/0.04Core)、Redis(132+179MB/0.04+0.054Core)、PostgreSQL(506MB/0.03Core)

  • 空负载整体资源占用情况,内存:3935MB、CPU核数:0.2 Cores、存储PVC:34G

image-20200419182509260

3.2 Helm 部署 Sentry

helm为helm3

使用国内加速的charts(微软azure)

helm repo add stable http://mirror.azure.cn/kubernetes/charts
helm repo add incubator http://mirror.azure.cn/kubernetes/charts-incubator
helm repo update
helm search repo sentry
#NAME                              CHART VERSION   APP VERSION DESCRIPTION
#stable/sentry                     4.2.0           9.1.2       Sentry is a cross-platform crash reporting and ...
#已经可以在stable repo 找到 sentry
## helm安装sentry
kubectl create namespace sentry
helm install sentry stable/sentry \
-n sentry \
--set persistence.enabled=true,user.email=i@iamle.com,user.password=i@iamle.com \
--set ingress.enabled=true,ingress.hostname=sentry.iamle.com,service.type=ClusterIP \
--set email.host=smtp.yourhost.com,email.port=25 \
--set email.user=user,email.password=password,email.use_tls=false \
--wait
#参数一看就懂,更多安装参数看文末的参考⑤
#第一次安装需要耗时10分钟以上,等等等,db-init-job初始化数据库的时候花费了太多时间
#在安装完成之前,访问sentry.iamle.com会出现服务器内部错误

#出现下面的内容就表示部署好了
#NAME: sentry
#LAST DEPLOYED: Sun Apr 19 21:01:26 2020
#NAMESPACE: sentry
#STATUS: deployed
#REVISION: 1
#TEST SUITE: None
#NOTES:
#1. Get the application URL by running these commands:
#  export POD_NAME=$(kubectl get pods --namespace sentry -l "app=sentry,role=web" -o jsonpath="{.items[0].metadata.name}")
#  echo "Visit http://127.0.0.1:8080 to use your application"
#  kubectl port-forward --namespace sentry $POD_NAME 8080:9000
#
#2. Log in with
#
#  USER: i@iamle.com
#  Get login password with
#    kubectl get secret --namespace sentry sentry -o jsonpath="{.data.user-password}" | base64 --decode

#查看登陆密码,也就是user.password设置的值
#kubectl get secret --namespace default sentry -o jsonpath="{.data.user-password}" | base64 --decode

#删除sentry
#helm uninstall sentry -n sentry

ingress根据自己实际环境做一些微调,笔者的环境下ssl证书使用 cert-manager 自动管理,备注annotations就自动配置ssl

# sentry ingress示例
kubectl apply -f - <<EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: sentry
  namespace: sentry
  annotations:
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  rules:
  - host: sentry.iamle.com
    http:
      paths:
      - path: /
        backend:
          serviceName: sentry
          servicePort: 9000
  tls:
  - hosts:
    - sentry.iamle.com
    secretName: sentry-cert
EOF

浏览器访问

https://sentry.iamle.com 使用安装时设置的账号密码登陆即可

3.3 Sentry新增一个项目

理论上是需要给每个项目都在Sentry中创建一个对应的项目

但是实际上这么干会比较麻烦,笔者建议对访问量不大,比如后台类的相同类型的新建一个项目即可

发现混在一起已经不好区分了,再去拆开也不迟

对于本身访问量巨大的“前端”项目,建议在Sentry管理后台的一对一配置

Projects 》 Add new Project 选择一个JavaScript类型

image-20200419211855759

image-20200419212037861

⚠️ 官方提供的SDK是国外的CDN,实际使用的时候需要把https://browser.sentry-cdn.com/5.5.0/bundle.min.js下载后放在自己的OSS+CDN上

SDK和初始化代码一般放入全站头部当中,当页面有js错误的时候已经会自动上报了

我们用chrome的console手动上报一个消息测试下sentry是否工作正常

Sentry.captureMessage("流水理鱼 www.iamle.com")

image-20200419212444197

image-20200419212641447

3.4 Helm安装sentry常见问题及解决

  • 解决数据库不能初始化的问题

    如果安装过程数据库不能初始化,可以手动初始化

kubectl exec -it -n sentry $(kubectl get pods  -n sentry  |grep sentry-web |awk '{print $1}') bash
sentry upgrade

  • 手动创建一个新的管理员账号
kubectl exec -it -n sentry $(kubectl get pods  -n sentry  |grep sentry-web |awk '{print $1}') bash
sentry createuser

4. 总结

本文看起来洋洋洒洒一大篇,实际上15分钟就能部署好sentry🎉

Sentry不仅仅支持WEB前端,也支持桌面、APP、后端,全平台💯

注意如果要升级sentry,先备份数据先,之前的数据会被清空 ⚠️

如果遇到Sentry的部署、使用等问题可以在博客 www.iamle.com 中找到我的wx,加群讨论📣

5. 参考

1. 导言

绝大多数业务场景都是需要知道客户端IP的
在k8s中运行的业务项目,如何获取到客户端真实IP?
本文总结了通行的2种方式
要答案的直接看方式一、方式二和总结
SEO 关键字
nginx ingress客户端真实ip
kubernets获取客户端真实ip
rke获取客户端真实ip
rancher获取客户端真实ip
本文由 www.iamle.com 流水理鱼 原创,wx公众号同名

1.1 流量链路介绍

7层转发链路 Client(客户端) > Nginx > K8s Ingress(Nginx ingress)
4层转发链路 Client(客户端) > 公有云LB > K8s Ingress(Nginx ingress)
ps: 实际业务会串联更多层级的转发。WAF、CDN、Api Gateway一般是http 7层转发,LB一般是4层tcp转发

1.2 准备whoami探针

whomai是一个go编写的调试探针工具,回显http头信息
在k8s中部署一个containous/whoami用来作为探针,配置好ingress公网和访问,这样客户端web访问可以看到基本的http头信息,方便调试

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: whoami
  namespace: default
  labels:
    app: whoami
spec:
  replicas: 1
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
      - image: containous/whoami
        imagePullPolicy: Always
        name: whoami
        ports:
        - containerPort: 80
          name: 80tcp02
          protocol: TCP
      dnsPolicy: ClusterFirst
      restartPolicy: Always
EOF 

ps:ingress自行增加

客户端web访问,回显http头示例

Hostname: whoami-65b8cc4b-6vwns
IP: 127.0.0.1
IP: 10.42.2.12
RemoteAddr: 10.42.1.0:47850
GET / HTTP/1.1
Host: whoami.iamle.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6,la;q=0.5
Cookie: _ga=GA1.2.30707523.1570429261;
Dnt: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
X-Forwarded-For: 8.8.8.8, 10.0.0.1
X-Forwarded-Host: whoami.iamle.com
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Original-Forwarded-For: 8.8.8.8
X-Original-Uri: /
X-Real-Ip: 8.8.8.8
X-Request-Id: 3852c9780589ffba4c1f9f2785691d5f
X-Scheme: https

2. 两种方式 7层http头X-Forwarded-For透传 和 4层Proxy Protocol透传

获得客户端真实IP有针对7层和针对4层两种方式

2.1 7层http头X-Forwarded-For透传介绍

http工作在网络第7层,http中有个X-Forwarded-For字段

大部分CDN、WAF、LB用X-Forwarded-For字段来存客户端IP,也有用X-Real-Ip字段,cloudflare、百度云加速还扩展了CF-Connecting-IP字段
标准数据为

X-Forwareded-For:Client,proxy1,proxy2,proxy3……

第一个ip是客户端ip,后面的proxy为路过一层就加一层的ip
这里的proxy可以是WAF、CDN、LB、Api Gateway等

2.2 4层Proxy Protocol透传

tcp工作在网络第4层,Proxy Protocol就是在tcp中增加一个小的报头,用来存储额外的信息

代理协议即 Proxy Protocol,是haproxy的作者Willy Tarreau于2010年开发和设计的一个Internet协议,通过为tcp添加一个很小的头信息,来方便的传递客户端信息(协议栈、源IP、目的IP、源端口、目的端口等),在网络情况复杂又需要获取客户IP时非常有用。
其本质是在三次握手结束后由代理在连接中插入了一个携带了原始连接四元组信息的数据包。

目前 proxy protocol有两个版本,v1仅支持human-readable报头格式(ASCIII码),v2需同时支持human-readable和二进制格式,即需要兼容v1格式
proxy protocol的接收端必须在接收到完整有效的 proxy protocol 头部后才能开始处理连接数据。因此对于服务器的同一个监听端口,不存在兼容带proxy protocol包的连接和不带proxy protocol包的连接。如果服务器接收到的第一个数据包不符合proxy protocol的格式,那么服务器会直接终止连接。

Proxy protocol是比较新的协议,但目前已经有很多软件支持,如haproxy、nginx、apache、squid、mysql等等,要使用proxy protocol需要两个角色sender和receiver,sender在与receiver之间建立连接后,会先发送一个带有客户信息的tcp header,因为更改了tcp协议头,需receiver也支持proxy protocol,否则不能识别tcp包头,导致无法成功建立连接。
nginx是从1.5.12起开始支持的

3. 方式一 X-Forwarded-For配置

适用于7层http转发

3.1 NGINX Ingress Controller X-Forwarded-For配置

查看NGINX Ingress Controller的ConfigMaps配置文档,可以找到以下配置项
use-forwarded-headers
如果为true,NGINX会将传入的 X-Forwarded-* 头传递给upstreams。当NGINX位于另一个正在设置这些标头的 L7 proxy / load balancer 之后时,请使用此选项。
如果为false,NGINX会忽略传入的 X-Forwarded-* 头,用它看到的请求信息填充它们。如果NGINX直接暴露在互联网上,或者它在基于 L3/packet-based load balancer 后面,并且不改变数据包中的源IP,请使用此选项。
ps: NGINX Ingress Controller直接暴露互联网也就是Edge模式不能开启为true,否则会有伪造ip的安全问题。也就是k8s有公网ip,直接让客户端访问,本配置不要设为true!

forwarded-for-header
设置标头字段以标识客户端的原始IP地址。 默认: X-Forwarded-For
ps:如果 NGINX Ingress Controller 在CDN,WAF,LB等后面,设置从头的哪个字段获取IP,默认是X-Forwarded-For
这个配置应该和use-forwarded-headers配合使用

compute-full-forwarded-for
将远程地址附加到 X-Forwarded-For 标头,而不是替换它。 启用此选项后,upstreams应用程序将根据其自己的受信任代理列表提取客户端IP

修改configmap nginx-configuration配置

kubectl -n ingress-nginx edit cm nginx-configuration

在apiVersion: v1下,kind: ConfigMap上加入

data:
  compute-full-forwarded-for: "true"
  forwarded-for-header: "X-Forwarded-For"
  use-forwarded-headers: "true"

或者直接apply附加配置

kubectl apply -f - <<EOF
apiVersion: v1
data:
  compute-full-forwarded-for: "true"
  forwarded-for-header: X-Forwarded-For
  use-forwarded-headers: "true"
kind: ConfigMap
metadata:
  labels:
    app: ingress-nginx
  name: nginx-configuration
  namespace: ingress-nginx
EOF

ps:如果nginx-configuration不在namespace ingress-nginx中就在namespace kube-system中找

3.2 Nginx作为边缘节点(Edge)配置

作为Edge需要重写remote_addr,保证了客户端IP不会被伪造
必须:X-Forwarded-For 重写为 $remote_addr
非必须扩展:X-Real-IP 重写为 $remote_addr

upstream wwek-k8s {
    server 8.8.8.8:443;
    server 8.8.8.7:443;
    server 8.8.8.6:443;
}

map $http_upgrade $connection_upgrade {
    default Upgrade;
    ''      close;
}
server {
    if ($http_x_forwarded_proto = '') {
        set $http_x_forwarded_proto  $scheme;
   }
location / {

        proxy_set_header Host              $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port  $server_port;
        #proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-For   $remote_addr;
        proxy_set_header X-Real-IP         $remote_addr;

        proxy_pass          https://wwek-k8s;
        proxy_http_version  1.1;
        proxy_set_header    Upgrade $http_upgrade;
        proxy_set_header    Connection $connection_upgrade;
        proxy_read_timeout 900s;
        proxy_buffering off;
    }
}

3.3 X-Forwarded-For是否可以伪造

客户端是否能伪造IP,取决于边缘节点(Edge)是如何处理X-Forwarded-For字段的。
客户端直接连接的首个proxy节点都叫做边缘节点(Edge),不管是网关、CDN、LB等只要这一层是直接接入客户端访问的,那么他就是一个边缘节点。

不重写-不安全的边缘节点(Edge)
边缘节点如果是透传http头中的X-Forwarded-For字段,那么这个就是不安全的,客户端可以在http中实现包含X-Forwarded-For字段值,这个值又被透传了。

#不安全
X-Forwareded-For:Client(Edge不重写,只透传),proxy1,proxy2,proxy3……

重写-安全的边缘节点(Edge)
边缘节点(Edge)如果重写remote_addr到X-Forwarded-For,那么这就是安全的。边缘节点(Edge)获取的remote_addr就是客户端的真实IP

#安全
X-Forwareded-For:Client(Edge获取的remote_addr),proxy1,proxy2,proxy3……

4. 方式二 Proxy Protocol 配置实例

适用于4层tcp转发
公有云的负载均衡LB一般都支持Proxy Protocol

查看NGINX Ingress Controller的ConfigMaps配置文档,可以找到如何配置Proxy Protocol
use-proxy-protocol
启用或禁用roxy Protocol,以接收通过代理服务器和负载均衡器(例如HAProxy和Amazon Elastic Load Balancer(ELB))传递的客户端连接(真实IP地址)信息。

NGINX Ingress Controller 作为receiver角色 Proxy Protocol配置

kubectl -n ingress-nginx edit cm nginx-configuration

在apiVersion: v1下,kind: ConfigMap上加入

data:
  use-proxy-protocol: "true"

或者直接apply附加配置

kubectl apply -f - <<EOF
apiVersion: v1
data:
  use-proxy-protocol: "true"
kind: ConfigMap
metadata:
  labels:
    app: ingress-nginx
  name: nginx-configuration
  namespace: ingress-nginx
EOF

ps: 注意需要上一层LB支持Proxy Protocol,才能这么配置,否则会导致无法链接

5. 总结

7层http头X-Forwarded-For透传
链路proxy有透传X-Forwarded-For
访问链路上多层proxy,任意一个节点不支持Proxy Protocol

4层协议Proxy Protocol透传
上下游可控都支持Proxy Protocol协议
链路proxy中丢失了http头
https反向代理http(某些情况下由于Keep-alive导致不是每次请求都传递x-forword-for

应该用那种方式?
7层用X-Forwarded-For,4层用Proxy Protocol
如果链路的边缘节点(Edge)X-Forwarded-For字段是安全的,建议用X-Forwarded-For
如果链路proxy全路径都支持Proxy Protocol,那么建议用Proxy Protocol
如果有4层tcp业务应用,那么获取客户端IP就的用Proxy Protocol
总之搞清楚了这2种方式的原理按照场景选择

5. 参考

1.场景

容器化+K8s编排已经是现在进行时
把网站的多个项目设计为云原生(Cloud Native)或老项改造为云原生可以获得诸多能力
例如无云绑定、弹性、部署环境一致性、微服务、DevOps、持续交付
同时下一代微服务框架 服务网格(ServiceMesh) 也能无痛接入

博主现有项目后端开发语言为 PHP、Golang
Golang做一些基础公共服务(短信、消息、搜索等)
这些公共服务化的项目已经容器化
PHP的项目做应用逻辑层,会调用Golang写的一些公共基础服务
PHP项目中直接通过服务名调用服务

需求: PHP项目A 依赖 短信、消息、搜索这3个服务,开发人员无需在本机启动依赖的服务,通过服务直接名透明的调用开发环境dev下的服务,开发人员只需要关注PHP项目A的开发。

☆本文的方案完成了开发人员开发机透明的直接访问K8s服务,从而满足了本需求☆

需要开发机透明访的直接问Kubernetes群集内的服务本文讲的方案都适用

开发机直接访问Kubernetes群集内的服务 示意图

2.基本信息和完成的目标

2.1基本信息

开发办公内网 192.168.1.0/24
开发机2 192.168.1.2

运行K8s群集的Node网络 10.2.0.0/16
Node1 10.2.1.4
Node2 10.2.1.8
K8s 群集网络 10.3.0.0/16

部署deployment whoami服务用于测试
命名空间 default (这里可以用命名空间来区分环境例如dev为开发环境)
镜像 wwek/whoami
服务名 whoami

Pod ip
10.3.0.8在node1 10.2.1.4
10.3.0.70在node2 10.2.1.8

2.3完成的目标

开发机2 192.168.1.2 可以直接访问 whoami服务
也就是可以直接 curl http://whomai 就可以访问服务
本目标即完成需求

3.网络互通1 开发办公内网 <==> 公有云VPC(私有云内网)基础互通

开发办公内网 和 公有云VPC、私有云内网 网络互通

和公有云互通的方案
公有云VPN
专线(SDWAN)

私有云互通就不多讲了,很多公司内网的K8s开发测试群集和开发网络本身就是互通的

各家网络情况各有各的不同,相同的是这些有用的Tips
无论是在公有云VPC、私有云、K8s群集非常关键的一点,子网网段不要冲突不要冲突、子网网段不要冲突、子网网段不要冲突
做基础互通的时候善用公有云的VPC和路由配置功能
甚至你可以不用买公有云自带的VPN网关服务,直接配合VPC路由表用一台VM充当路由器网关、VPN网关
开发测试环境下用zerotier来打通各个内网性价比极高

最终要完成是 开发办公内网 和 公有云VPC(私有云内网) 基础互通

4.网络互通2 开发办公内网 <==> K8s群集内部Pod和Service网络

☆☆☆ K8s Node本身是直接可以访问K8s群集内部Pod网络的!☆☆☆
在Node1上用ping/curl测试 whoami服务 分布在2个Node的2个Pod

可以看到,whoami的2个pod ip都能ping通,用curl测试也能访问到

通过Edge Node互通K8s群集和开发办公之间的网络
那么用Node1作为 开发办公内网 和 K8s群集内部网络的“连接”点我们把这个Node1节点叫做 边缘节点(Edge Node)
边缘节点(Edge Node)可以在运行K8s群集中Node中随便选一个
这里选择Node1,他的网卡信息如下
eth0 vm网卡 ip 10.2.1.4
cbr0 K8s群集创建的网卡 ip 10.3.0.1

可以有2种方式
方式1 在 Edge Node eth0 上启用NAT 这样其他的子网的访问在K8s群集中看到的IP是 10.2.1.4
方式2 K8s群集子网和开发办公内网完全对等互通(公有云VPC路由表、开发办公网络路由表配合做)

完成后
开发办公网络中 在开发机2 192.168.1.2 ping/curl K8s群集中的pod ip

可ping/curl 属于whoami的2个Pod ip

㊗️网络搞通了,那么再解决DNS解析的问题就可以了

5.打通K8s群集中的DNS (开发办公内网的DNS,设置K8s中KubeDns为上游DNS)

在Edge Node1上可以直接访问到KubeDns

kubectl get svc -n kube-system
#kube-dns ip 10.3.254.107

那么在Edge Node1上面装一个DNS Server做个中间转发(使用CoreDNS)
开发网络中的电脑无法直接使用kube-dns,非Edge Node解析结果为空
所以需要在Edge Node1上转一个 DNS Server 做一个Proxy
CoreDNS的安装使用参考我的另外一篇文章
使用CoreDNS作为你的内网DNS服务器
可用CoreDNS配置文件参考
/etc/coredns/Corefile

# kubernetes设置
cluster.local:53 {
  # kube-dns
  forward . 10.3.254.107:53
  log
  errors
  #debug
}

# 默认设置
.:53 {
  # 先走本机的hosts
  # https://coredns.io/plugins/hosts/
  hosts {
    # 自定义sms.service search.service 的解析
    # 因为解析的域名少我们这里直接用hosts插件即可完成需求
    # 如果有大量自定义域名解析那么建议用file插件使用 符合RFC 1035规范的DNS解析配置文件
    #10.6.6.2 servicename
    # ttl
    ttl 60
    # 重载hosts配置
    reload 1m
    # 继续执行
    fallthrough
  }

  # file enables serving zone data from an RFC 1035-style master file.
  # https://coredns.io/plugins/file/
  # file service.signed service
  # 最后所有的都转发到系统配置的上游dns服务器去解析
  forward . /etc/resolv.conf

  # 缓存时间ttl  s
  #cache 6
  # 自动重新加载配置文件的间隔时间
  reload 6s
  # 输出日志
  log
  # 输出错误
  errors
  #debug
}

启动CoreDNS

dig @10.2.1.4 whoami.default.svc.cluster.local
# 测试下确保可以解析

在 开发机2
配置DNS为 Edge Node1 IP 10.2.1.4
配置搜索域为 default.svc.cluster.local

这个2个配置可以在开发办公网络中的DHCP服务器上统一配置

-w564

来测试下DNS解析 curl访问

6.关键实现总结

  • 网络互通1 开发办公内网 <==> 公有云VPC(私有云内网)基础互通
  • 网络互通2 开发办公内网 <==> K8s群集内部Pod和Service网络(通过Edge Node)
  • 打通K8s群集中的DNS (开发办公内网的DNS,设置K8s中KubeDns为上游DNS)
  • 开发机DNS配置 Edge Node DNS 和 搜索域设置default.svc.cluster.local

7.QA

问1:这也太复杂了吧,有没有简单点的?
答:
解决的目标不同

如果只是单纯的能访问k8s中的服务有以下的方式
访问K8s中的服务还有这些方式
telepresence (快速,本地开发面向k8s的微服务)
K8s中装个openvpn 拨入群集内网络
K8s自带的服务暴露方式 NodePort Ingress

这些方法在一个应用有多个服务依赖的时候无法做到让所有开发人员透明的直接通过服务名调用

为了做到对多个开发人员透明,所有人都不需要安装项目依赖的服务,只需要运行项目本身,本项目透明的调用依赖的服务。
所以才有了本文的“复杂”方案

问2:这样直通了暴露K8s群集后岂不是不安全?
答:
是的,但是可以解决,我是这么解决的
K8s群集分为线上和线下实现了隔离
线上为准生产、生产,线下为开发、测试
k8s中可以用命名空间(namespace)来做环境的区分
dev、testing、staging、prod

问题3:开发机中DNS用K8s的DNS作为上游后网站的CDN乱解析了!
答:
开发办公网络和公有云的网络运营商和地理位置都不同,
也就是如果网络出口不一样这会导致CDN解析的IP是错的

需要在开发办公网络也部署一个DNS Server成为二级DNS
开发办公网络 开发机设置DNS为这个二级DNS
cluster.local转发到 Edge Node DNS上
其他的走本地默认的DNS
同样采用CoreDNS,配置文件参考

cluster.local:53 {
  # Edge Node DNS
  forward . 10.2.1.4:53
  log
  errors
  #debug
}
.:53 {
   ....
}

私有云或者自己在开发办公网络部署的K8s群集,因为是同一个网络出口那么网站的DNS解析应该不会有问题

前言

组织的容器支持docker-compose部署
组织的容器支持kubernets部署
以php框架thinkphp为示例,演示php项目的kubernets部署
多容器方式(3容器)分别为:appphp(php代码)、openresty(nginx webserver),php-fpm(php的运行环境)
dockerfile 和 yaml文件
docker iamges仓库

PHP项目在Docker中如何部署运行?

PHP应用的运行方式

PHP应用的运行方式一般有
Apache mod_php 模式、Nginx(FastCgi)+PHP-FPM模式、Swoole常驻内存Daemon模式

Docker单容器

Apache mod_php 模式和Swoole常驻内存Daemon模式本身就是单程序,那么在Docker中入口运行程序直接为应用程序即可

Nginx(FastCgi)+PHP-FPM模式需要nginx和php-fpm 2个程序
这样Docker中就需要运行多个程序,需要有进程守护类软件来运行多个程序,那么可以参考如何在一个Docker中运行多个程序进程
推荐使用s6-overlay

Docker Hub中PHP官方镜像包已经包括Apache mod_php 模式的镜像包,Kubernets官方PHP项目实例GuestBook中就是采用这种模式的镜像包

Docker多容器配合

Docker官方倡导容器单一职责,也就是一个容器只运行一个程序
那么Nginx(FastCgi)+PHP-FPM模式就需要2个容器配合编排工作,再加上如果把PHP代码再独立成一个Docker镜像,那么就是3容器配合编排工作
所以下面就是以PHP项目采用多个Docker镜像的方式再Kubernets平台部署的范例

kubernets(k8s)部署运行

思路说明
容器共有3个,① appphp(wwek/k8s-php-thinkphp-hello:app-v1)php代码容器、② php-fpm(wwek/k8s-php-thinkphp-hello:php-fpm-7.2) 、③ openresty(wwek/k8s-php-thinkphp-hello:openresty)
apphp、php-fpm、openresty三个容器都在一个Pod(8s-php-thinkphp-hello)中
appphp容器作为initContainers初始化容器使用
挂载了一个叫做wwwroot的volume,类型为emptyDir,临时卷
initContainers初始化容器的时候会把php代码复制到wwwroot(volume)中
php-fpm和openresty都挂载wwwroot(volume)
openresty的upstream php 使用域名php-fpm
使用hostAliases把域名php-fpm解析为127.0.0.1(在同一个Pod中网络是共享的,所以用127.0.0.1)

具体的yaml文件

# Service
apiVersion: v1
kind: Service
metadata:
  name: k8s-php-thinkphp-hello
  labels:
    app: k8s-php-thinkphp-hello
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app: k8s-php-thinkphp-hello
---
# deployment
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: k8s-php-thinkphp-hello
  labels:
    app: k8s-php-thinkphp-hello
spec:
  replicas: 1
  selector:
    matchLabels:
      app: k8s-php-thinkphp-hello
  template:
    metadata:
      labels:
        app: k8s-php-thinkphp-hello
    spec:
    #做一个emptyDir类型,名为wwwroot的volume 用于多个容器共享同一个挂载
      volumes:
      - name: wwwroot
        emptyDir: {}
      # 私有docker 镜像仓库
      # imagePullSecrets:
      # - name: registrykey-qlcoud-1
      # 自定义设置POD的hosts
      hostAliases:
      - ip: "127.0.0.1"
        hostnames:
        - "php-fpm"
      initContainers:
       #php程序本身
        - name: appphp
          image: "wwek/k8s-php-thinkphp-hello:app-v1"
          imagePullPolicy: Always
          # 复制php程序文件到wwwroot volume
          command: ["sh", "-c", "cp -r /var/www/k8s-php-thinkphp-hello /appdata"]
          volumeMounts:
          - mountPath: /appdata
            name: wwwroot
      containers:
       #php-fpm php运行环境
        - name: php-fpm
          image: "wwek/k8s-php-thinkphp-hello:php-fpm-7.2"
          imagePullPolicy: Always
          volumeMounts:
          - mountPath: /var/www
            name: wwwroot
      #openrsty webserver
        - name: openresty
          image: "wwek/k8s-php-thinkphp-hello:openresty"
          imagePullPolicy: Always
          volumeMounts:
          - mountPath: /var/www
            name: wwwroot
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          # livenessProbe:
          #   httpGet:
          #     path: /
          #     port: http
          # readinessProbe:
          #   httpGet:
          #     path: /
          #     port: http
          resources:
            {}
---
# Ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: k8s-php-thinkphp-hello
  labels:
    app: k8s-php-thinkphp-hello
spec:
  rules:
    - host: k8sphpthinkphp.com
      http:
        paths:
          - path: /
            backend:
              serviceName: k8s-php-thinkphp-hello
              servicePort: http
kubectl apply -f k8s-php-thinkphp-hello.yml
kubectl get pods |grep k8s-php-thinkphp-hello
kubectl get service |grep k8s-php-thinkphp-hello
kubectl get ingress |grep k8s-php-thinkphp-hello

把 k8sphpthinkphp.com hosts解析到Ingress 的ip
然后浏览器

访问 http://k8sphpthinkphp.com/ 可以看到thinkphp的欢迎页面

访问 http://k8sphpthinkphp.com/phpinfo.php 可以看到phpinfo信息

如果没有ingress请自行修改service的type为 NodePort 使用节点的ip和端口访问

docker-compose部署运行

一套打包好的业务PHP Docker业务镜像也可以用docker-compose部署,这样方便了本地的开发调试使用

version: '2'

services:

### Applications Code Container #############################

    appphp:
      image: wwek/k8s-php-thinkphp-hello:app-v1
      build:
        context: ./appphp
        dockerfile: "Dockerfile"

### PHP-FPM Container #######################################

    php-fpm:
      image: wwek/k8s-php-thinkphp-hello:php-fpm-7.2
      build:
        context: ./php-fpm
        dockerfile: "Dockerfile"
      volumes_from:
        - appphp
      environment:
        - RUN_MODE=prd
        - MYSQL_HOSTNAME= \
        - MYSQL_USERNAME= \
        - MYSQL_PASSWORD= 
      depends_on:
        - appphp
      expose:
        - "9000"
      networks:
        - backend

### OPENRESTY Server Container ##################################

    openresty:
      image: wwek/k8s-php-thinkphp-hello:openresty
      build:
        context: ./openresty
        dockerfile: "Dockerfile"
      volumes_from:
        - appphp
      ports:
        - "80:80"
        - "443:443"
      depends_on:
        - php-fpm
      networks:
        - frontend
        - backend

### Networks Setup ############################################

networks:
  frontend:
    driver: "bridge"
  backend:
    driver: "bridge"

### Volumes Setup #############################################

volumes:
  redis:
    driver: "local"

直接up方式,直接pull已经build好的docker images

docker-compose up -d

本地build方式

docker-compose up -d --build

浏览器 访问 http://127.0.0.1 访问 http://127.0.0.1/phpinfo.php

代码

所有配置范例代码已经托管到github
k8s-php-thinkphp-hello

PHP在服务化微服务中遇到的问题

项目大了后服务化是必然的!
想象下几十个PHP项目,里面有大多数项目有同样的功能需求,我们可以用复制代码解决
但是对代码维护来说简直是噩梦,一次调整,调整多个项目,简直爽歪歪
先谈服务化拆分,再谈用微服务落地。 做好服务化是目的,用微服务落地是实现方式
那么微服务落地需要一大堆的服务治理能力来支撑,服务注册发现,断路器,网关,RPC等等
微服务中服务注册发现,总不可能手动管理各种服务,那么得有服务注册发现这个东西
由于apache php ,PHP-FPM php不是常驻内存的方式运行,导致了在服务注册发现等方便不能做,服务注册发现又是基础
正因为这个特性让PHP在虚拟主机年代大放异彩,也是因为本身特性导致PHP在服务化,微服务领域落地困难,不过就没解决办法了? 办法是有,但是小公司玩不起来。下面看看市面上的解决方案。

目前用PHP做微服务的解决方案

要用PHP做微服务必须要搞定微服务的服务注册发现问题

Agent模式

微博采用了这样的方式,在跑PHP-FPM的机器上跑了一个Agent(这个和后面会讲到的Service Mesh的 Sidecar模式很像)
通过Agent去完成服务注册发现,调用方通过Agent去调用。Agent作为一个包裹层。
Agent其实是后面在Service Mesh节提到的Sidecar的雏形。

以Swoole为基础的常驻内存damon模式

Swoole是PHP的一个扩展
使 PHP 开发人员可以编写高性能的异步并发 TCP、UDP、Unix Socket、HTTP,WebSocket 服务。Swoole 可以广泛应用于互联网、移动通信、企业软件、云计算、网络游戏、物联网(IOT)、车联网、智能家居等领域。
基于Swoole开发的php程序是直接常驻内存damon的方式运行的
所以这样就可以方便的做服务注册和发现了
基于Swoole体系的开源PHP微服务框架有
Swoft
PHP-MSF
GroupCo
SwooleDistributed
Tencent/Tars

综述PHP微服务落地

综上述
方式1 以微博为代表的Agent代理模式(php-fpm模式运行的php程序因为运行机制问题,导致只有Agent的模式才能做服务注册发现,极少公司有这个技术支撑)
方式2 以Swoole为基石的常驻内存damon模式
生产可用问题,没有广泛的使用经验,目前有部分有强力技术支持的公司在运用,如果要用于自己的环境需要有技术团队去完成这个落地,需要开发一些配套的管控基础设施
毕竟没有服务治理能力贸然上微服务就是自己搞死自己的事

未来PHP做微服务的解决方案

Java体系以 Spring Cloud、Dubbo为代表的微服务框架得到广泛应用的时候无疑是对java编程语言的助推加持
现有的微服务框架不是就完美了,任何事物都是具备两面性的,现有的框架面临SDK更新,业务代码侵入,编程语言绑定(要做非常多的SDK)等诸多问题

Linkerd横空出世,提出了一个叫做Service Mesh的东西中文翻译叫做服务网格
随后以Google Ibm联合起来紧跟着发起了Istio项目
Service Mesh是一个非常新的概念,在2017年才提出,被誉为下一代微服务框架
试图解决现在的微服务框架的诸多问题,最终实现包含现有框架的所有功能,而且还能实现业务代码零侵入

Service Mesh使用了Sidecar模式接管了网络,所以才能实现业务代码零侵入
正因为Service Mesh的这样的架构设计,所以可以真正的实现编程语言无关性,不同服务可以使用不同的编程语言,不需要为不同的语言每个都去维护SDK

那么用PHP做的服务,不论是apache php ,PHP-FPM php, 还是Swoole damon都可以,可以做到真正的语言无关,应用程序本身是不关心微服务那一堆事情的

目前Service Mesh还存在的问题
体系还是非常早期的阶段,以Istio为例,目前大部分特性都是处在Alpha阶段(https://istio.io/about/feature-stages.html)
性能损失问题,据网上看到的测试Istio目前的版本有40%的性能损失(我觉得这个不是重点,只看字面40%,想象下实际业务场景呢? 解决的问题的收益远大于问题,况且后期肯定有优化的空间)
可能需要K8s才能更好的落地,以Istio为例虽然不强制依赖k8s,但是在k8s运行Istio才是最佳选择,使用k8s也是需要学习成本的(我觉得用k8s不是问题,中小公司可以用云平台直接提供的k8s能力,大公司也不用担心没人运维k8s的问题)
Service Mesh到规模落地时间可能还有2年左右的时间

业务代码开发为什么要关注服务治理的东西,微服务的东西? 这就是抽象成一层框架干的事情

作为phper未来还有机会么

诚然PHP在微服务时代被沦为了“前端”语言,写PHP的在大公司沦为“套模板”的。
在Java Spring Cloud 全家桶面前,phper是看着人家的工具库牛逼。
phper是否没机会了呢,php是否没机会了呢
仅仅是从语言角度讲,首先phper当然有机会,php也当然有机会,phper不要局限在php语言本身,js框架vue,Golang都是可以学习的目标
再次市面上还有大量的web站点只需要php就可以快速简单的达成,根本不需要服务化,什么微服务,老夫拿起php连上redis、mysql就是干,单体应用分分钟出活
phper不用担心没出路,php在web领域的优势、市场需求都会验证这一点

需要2年时间,Service Mesh在中大型公司一定会落地,小公司也会在云平台上找到落地的可能,php一样可以干微服务“后端”干的事!