Skip to content

k8s

用oauth2-proxy和Github保护istio服务

在AWS EKS用上了istio后,部署服务很方便,但是我发现不少应用没有自带账号验证机制(比如jaeger),而很多数据信息比较敏感,那怎么办呢?Keycloak又太复杂了,这就想到了Github账号机制来管理,那要是能整合到istio里就太好了~说干就干

首先配置Github的应用App (官方文档),记好Client ID 和 Client secret,等回会用到。

配置好应用的oauth2 callback地址,比如 https://example.com/oauth2,等下istio需要配置对应的service。接着就是安装和配置oauth2-proxy(helm)

configuration:
  clientID: "xxxxx" #刚才的Github Client ID 
  clientSecret: "xxxxxxxxxxxxxxxxx" # 刚才的Github Client Secret
  ## 用这个命令生成一段随机的secret 
  ## openssl rand -base64 32 | head -c 32 | base64
  cookieSecret: "xxxxxxxxxxxxxxxxxxxxxxxx="
extraArgs:
  [
    "--provider=github", # provider 我们选github
    "--github-org=example", # 组织填入自己的组织名,还有其他验证范围可选,具体可以看文档
    "--scope=user:email", # 这个是oauth-proxy的bug……不加上会不停的重定向
    "--upstream=static://200", # 也是不加上就不停重定向的bug
    "--pass-authorization-header=true",
    "--pass-user-headers=true"
  ]

github-org这个配置可以改成你需要的验证方式,具体可以看oauth2-proxy官方文档(链接)。回到我们的istio配置上,给整个istio添加自定义的extensionProvider,让Github 的 AuthorizationPolicy能跑通。有点懵了是吧,我画了张不太准确的图帮助理解

oauth2-proxy大致的流程图

kubectl edit configmap -n istio-system istio 编辑istio的配置,最后大概长这样,注意里面的注释说明

apiVersion: v1
data:
  mesh: |-
    defaultConfig:
      discoveryAddress: istiod.istio-system.svc:11111
      proxyMetadata: {}
    enablePrometheusMerge: true
    rootNamespace: istio-system
    trustDomain: cluster.local
    # 上面的都是原来的配置,不要改,关键是下面这个
    extensionProviders:
    - name: "gh-example-oauth2" # 要记得这个名字,等会儿会用到
      envoyExtAuthzHttp:
        # 这里要指向你自己的oauth2-proxy安装的service,我这里是放在oauth2-proxy这个namespace下
        service: "gh-oauth2-proxy.oauth2-proxy.svc.cluster.local"
        port: "80" # 这个是helm安装的默认端口(kubeapp也是)
        includeRequestHeadersInCheck: ["authorization", "cookie"] # 这三个必须和我的一样
        headersToUpstreamOnAllow: ["authorization", "path", "X-Auth-Request-User", "X-Auth-Request-Email", "X-Auth-Request-Access-Token"]
        headersToDownstreamOnDeny: ["content-type", "set-cookie"]

然后再配置istion的virtual service,让服务整个跑起来,同样注意我里面的注释,官方的文档写得太文绉绉,喜欢的也可以去读一下(链接)

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: example-vs
  namespace: example-ns #应用自己的namespace
spec:
  hosts:
  - "jaeger.example.com"
  gateways:
  - example-gw # 之前安装istio时的gateway
  http:
  - match: # 这里保持和github里的一致
    - uri:
        prefix: /oauth2
    route:
    - destination:
        host: gh-oauth2-proxy.oauth2-proxy.svc.cluster.local # oauth2的安装svc地址
        port:
          number: 80
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: jaeger.app.svc.cluster.local # jaeger的svc地址
        port:
          number: 8080
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: jaeger-github-oauth2
  namespace: example-ns
spec:
  selector:
    matchLabels:
      app.kubernetes.io/component: query #重要!不要填错了,匹配不上不会生效的
      app.kubernetes.io/name: jaeger
  action: CUSTOM
  provider:
    name: "gh-example-oauth2" # 跟istio配置里的extensionProviders保持一致
  rules:
  - to:
    - operation:
        # 注意是精确匹配的!!要加通配符才能前缀匹配
        paths: [ "/*"]      

kubectl apply之后,重启istio,访问你的应用,看到这个就成功啦~

参考: https://medium.com/@lucario/istio-external-oidc-authentication-with-oauth2-proxy-5de7cd00ef04

k8s配置istio并自动全站https

AWS的k8s(Kubernetes)需要安装一个istio来方便管理servicemesh、动态调整流量、附加https等

官网的安装说明已经很不错了,我个人喜欢用helm部署,以下是helm 3.6以上部署方式

helm repo add istio https://istio-release.storage.googleapis.com/charts
kubectl create namespace istio-system
helm install istiod istio/istiod -n istio-system --wait

# 这里开始就和官网不一样了,直接安装gateway到 istio-system里,这样方便整体删除
helm install istio-ingressgateway istio/gateway -n istio-system --wait

这时,AWS会自动分配一个ELB给你

istio自动添加ELB

到这里istio安装就完成了,接下来是cert-manager,我喜欢用kubeapp 进行管理,就可以一键安装cert-manager(个人偏好换了个namespace)

安装完毕后,开始配置cert-manager, kubectl apply 走起~

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod-cluster #注意这个名字
  namespace: istio-system
spec:
  acme:
    email: [email protected]
    server: https://acme-v02.api.letsencrypt.org/directory # 正式环境的地址,stage的可以看
    privateKeySecretRef:
      name: letsencrypt-prod-cluster
    solvers:
    - http01:
        ingress:
          class: istio

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-cert
  namespace: istio-system
  annotations:
    cert-manager.io/issue-temporary-certificate: "true" # 防止配置错误一直取不到证书
spec:
  secretName: example-cert #最好跟name一致
  isCA: false
  usages:
    - server auth
    - client auth
  issuerRef:
    name: letsencrypt-prod-cluster #要和前面的ClusterIssuer里的名字一样
    kind: ClusterIssuer
    group: cert-manager.io
  dnsNames:
    - example.com # 这里换成你自己的域名

现在来配置istio对应的站点, kubectl apply 继续走起~

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: example-gw
  namespace: default
spec:
  selector:
    istio: ingressgateway #这里就是helm安装的时候那个app名字,去掉istio-
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - example.com
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      httpsRedirect: false #防止配置错误乱跳
      mode: SIMPLE
      credentialName: example-cert #跟刚才的Certificate里的一样
    hosts:
    - example.com

---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: example
  namespace: default
spec:
  hosts:
  - "example.com"
  gateways:
  - example-gw # 跟前面的Gateway名字一致
  http:
  - name: main-site
    route:
    - destination:
        host: site.default.cluster.local # K8S集群里的名字,当然可以看官方按app和version配置,不过这次简单点
        port:
          number: 8080

接下来配置对应的DNS记录,我这里用了Route53,注意可以直接用alias指向ELB,因为ELB的地址会换IP,所以不要轻易用A记录直接指向。

AWS配置ELB alias

好了~这下配置完成了,但是还是连不上怎么办?

debug最好用的是istioctl dashboard kiali ,可以很直观的看到哪里配置错误,按照里面的提示修改就行。

官方教程 https://istio.io/latest/docs/tasks/observability/kiali/

ESXi安装k8s集群

最近ESXi 虚拟机安装完毕,开始折腾k8s集群。

k8s 集群拓扑

我先安装了Debian 11(bullseye),但是里面的软件比如containerd都很老了,只能跑1.4.3没办法设定指定镜像,换成Debian 12和Ubuntu 22.04都有网络没办法互通的问题……怎么改iptables都不行,只好用Fedora 36。这下才终于成功了,下面记录一下我遇到的坑。

首先对某墙只想唱:“听我说,谢谢你”,大家安装过程可以用阿里云的镜像。官方文档https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/ 其实已经非常全面和详细了,不要漏了任何一步,我就漏了加载overlay 模块导致的集群起不来的尴尬……

给路由器配置了自动DHCP,这样局域网里可以直接用fed-k8s-master这种域名访问了。

Fedora登入后第一步就是设置机器名称

hostnamectl set-hostname fed-k8s-master

然后是系统配置,比如必要的内核模块,sysctl一些东西,fedora还有个问题,就是要关掉防火墙

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# sysctl params required by setup, params persist across reboots
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# Apply sysctl params without reboot
sudo sysctl --system
sudo systemctl stop firewalld && sudo systemctl disable firewalld
sudo dnf remove zram-generator-defaults

# Set SELinux in permissive mode (effectively disabling it)
sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

接着配置k8s fedora源,记得把里面的proxy换成你科学上网用的(用aliyun的也问题不大,就是我不太喜欢……)

cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
proxy=<你的代理地址>
EOF

sudo yum install -y kubelet kubeadm kubectl containerd iproute-tc --disableexcludes=kubernetes
sudo systemctl enable --now kubelet containerd

接下来就是配置containerd,这里有3个坑:

  • 一个是cgroup driver要配置成systemd
  • runtime 的type 得手动配置
  • 还有就是坑爹的sandbox镜像源(可以理解成占位)

最后使用的配置文件就是:/etc/containerd/config.toml

version = 2

[plugins]
  [plugins."io.containerd.grpc.v1.cri"]
    sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.6"
    [plugins."io.containerd.grpc.v1.cri".cni]
      bin_dir = "/usr/libexec/cni/"
      conf_dir = "/etc/cni/net.d"
  [plugins."io.containerd.internal.v1.opt"]
    path = "/var/lib/containerd/opt"
  [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
    runtime_type = "io.containerd.runc.v2"
    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
      SystemdCgroup = true

开始初始化master control-plane(控制面主机),10.0.0.39 改成你自己的主机ip

kubeadm init --apiserver-advertise-address 10.0.0.39 --image-repository registry.aliyuncs.com/google_containers

执行成功会返回一个token

kubeadm join 10.0.0.39:6443 --token xlstub.uvzof5s4mz504ev1 \
--discovery-token-ca-cert-hash sha256:9cc767e5d1e2f2707d4e1f5a1270c569aeca3a49185aa591a9d2142f8d352198

先拷下来,然后不管他,因为我们还需要CNI(container network interface),简单来说就是容器间互相访问的网络, 我这里选了flannel,下载flanneld二进制,并应用配置

mkdir -p /opt/bin && wget https://github.com/flannel-io/flannel/releases/download/v0.19.0/flanneld-amd64 -O /opt/bin/flanneld
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

这里也有个坑:不管是coredns还是flanneld都一直起不来。其实就是k8s的一个bug……效果是

[root@fed-k8s-master ~]# kubectl get pods --all-namespaces
NAMESPACE      NAME                                     READY   STATUS              RESTARTS        AGE
kube-flannel   kube-flannel-ds-95sv7                    0/1     CrashLoopBackOff    7 (3m52s ago)   15m
kube-flannel   kube-flannel-ds-qjjcn                    0/1     CrashLoopBackOff    7 (3m36s ago)   15m
kube-system    coredns-74586cf9b6-fkd5x                 0/1     ContainerCreating   0               79m
kube-system    coredns-74586cf9b6-ktvk7                 0/1     ContainerCreating   0               79m
kube-system    etcd-fed-k8s-master                      1/1     Running             0               79m
kube-system    kube-apiserver-fed-k8s-master            1/1     Running             0               79m
kube-system    kube-controller-manager-fed-k8s-master   1/1     Running             0               79m
kube-system    kube-proxy-cl2l6                         1/1     Running             0               79m
kube-system    kube-proxy-llln9                         1/1     Running             0               30m
kube-system    kube-scheduler-fed-k8s-master            1/1     Running             0               79m

要是crictl logs 看为啥起不来,就会发现:

I0721 03:54:55.082767       1 match.go:206] Determining IP address of default interface
I0721 03:54:55.083292       1 match.go:259] Using interface with name ens192 and address 10.0.0.39
I0721 03:54:55.083350       1 match.go:281] Defaulting external address to interface address (10.0.0.39)
I0721 03:54:55.083485       1 vxlan.go:138] VXLAN config: VNI=1 Port=0 GBP=false Learning=false DirectRouting=false
E0721 03:54:55.084170       1 main.go:330] Error registering network: failed to acquire lease: node "fed-k8s-master" pod
 cidr not assigned
I0721 03:54:55.084272       1 main.go:447] Stopping shutdownHandler...
W0721 03:54:55.084438       1 reflector.go:436] github.com/flannel-io/flannel/subnet/kube/kube.go:403: watch of *v1.Node
 ended with: an error on the server ("unable to decode an event from the watch stream: context canceled") has prevented
the request from succeeding

这是个k8s的bug !,需要手动调整/etc/kubernetes/manifests/kube-controller-manager.yaml 这个文件,加上这两个选项……

--allocate-node-cidrs=true
--cluster-cidr=10.244.0.0/16

重启kubelet,master就配置好了。

在另一台一样配置好的fedora机器上,执行刚才的join命令,这样,一个简单的集群就配置好了