背景

目前已经有许多AWS EKS的客户通过使用ALB Ingress Controller来实现南北向七层流量的导入,在项目实施过程中碰到的一个比较集中的问题就是如何使用同一个Application Load Balancer(ALB)来实现对入口流量按照多个路由规则匹配来转发到EKS集群中运行的多个服务或者Pod内,避免因为多服务或者微服务架构中产生的大量服务对应生成几十个甚至数百个ALB的情况,进而避免增加运维管理负担以及资源使用上不必要的成本开销。

本文分为上下两篇,分别介绍在EKS上发布多个服务以及单个服务的不同方法,同时提供具体的操作步骤供大家动手实验。

上篇通过具体的示例来演示在同一个ALB Ingress Controller(V1) Ingress对象上通过扩展ALB Rule增加不同的URl路径映射来实现这个需求。同时随着AWS Load Balancer Controller在2020年10月份的发布,通过新的Annotation: alb.ingress.kubernetes.io/group.name注释来支持在不同的Ingress对象上共享同一个ALB,本文同样通过具体的示例介绍如何使用这个新功能。文中的实验会给读者一个相对直观的认识,从而理解和掌握在AWS EKS平台上灵活使用Ingress来实现HTTP(S)流量导入。通过同时介绍ALB Ingress Controller(V1)和AWS Load Balancer Controller(V2)Ingress的不同定义方法来实现绑定到同一个ALB的实现来对比两个版本的功能,进而更好的区别V1和V2中的异同点,避免在概念理解和项目实施过程中产生混淆。

下篇介绍在AWS Load Balancer Controller(V2)中发布的其他几项重要功能,包括对四层流量由Network Load Balancer转发下的IP模式支持,以及通过引入Target Group Binding CRD来实现将EKS中的服务灵活地绑定到已有Target Group上的新功能。

原理

下图介绍了AWS Load Balancer Controller创建的AWS组件,以及用户端流量经过AWS Load Balancer Controller(图中为alb-ingress-controller组件)创建的ALB,然后通过URL路径匹配将不同路径的HTTP(S)请求转发到在K8S节点上Pod的过程,(mode instance)和(mode ip)分别代表了AWS Load Balancer Controller所支持的实例和IP两种模式,本文中的演示使用的均为IP模式,即将流量直接发送到Pod所绑定的ENI的IP。

操作步骤

前提:当前使用的用户具有Administrator Access权限

准备Cloud9实验环境

在AWS管理控制台中选择Cloud9服务,然后创建一个名称为eksworkshop的环境,将Cost-saving setting选项设置为After four hours,其他配置保持默认。创建完毕后关闭Welcome和底部的工作区页面,然后在主工作区中打开一个新的Terminal。

在IAM服务中,创建一个名称为eksworkshop-admin的角色,确认AWS service和EC2被选中,点击下一步,确认AdministratorAccess策略被选中,点击下一步,跳过Tag选项,点击下一步在Review页面中输入eksworkshop-admin作为新角色的名称,点击创建角色完成创建。

在EC2服务中查看刚刚创建的Cloud9环境对应的EC2实例,选中该实例,然后在菜单选择:Actions / Security / Modify IAM Role,在IAM Role的下拉列表中选择eksworkshop-admin的角色,点击保存。

返回刚刚创建好的Cloud9环境,点击页面右上角的齿轮,打开首选项设置页面,然后选择AWS SETTINGS,关闭AWS managed temporary credentials单选框,最后关闭首选项设置页面。

在打开的Terminal中运行以下命令确认临时的秘钥凭证已经被删除干净,并验证在返回结果 ARN 中包含eksworkshop-admin。

rm -vf ${HOME}/.aws/credentials
aws sts get-caller-identity

运行下列脚本安装实验所需的Kubernetes 工具:eksctl,kubectl,helm,jq,aws cli

# create a folder for the scripts
mkdir ~/environment/scripts
# tools script
cat > ~/environment/scripts/install-tools <<-"EOF"
#!/bin/bash -ex
sudo yum install -y jq gettext bash-completion
sudo curl --silent --location "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/linux_64bit/session-manager-plugin.rpm" -o "session-manager-plugin.rpm"
sudo yum install -y session-manager-plugin.rpm
# install kubectl
sudo curl --silent --location -o /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/v1.18.10/bin/linux/amd64/kubectl
sudo chmod +x /usr/local/bin/kubectl
echo 'source <(kubectl completion bash)' >>~/.bashrc
source ~/.bashrc
# install eksctl
curl --silent --location "https://github.com/weaveworks/eksctl/releases/download/latest_release/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
sudo mv -v /tmp/eksctl /usr/local/bin
if ! [ -x "$(command -v jq)" ] || ! [ -x "$(command -v envsubst)" ] || ! [ -x "$(command -v kubectl)" ] || ! [ -x "$(command -v eksctl)" ] || ! [ -x "$(command -v ssm-cli)" ]; then
  echo 'ERROR: tools not installed.' >&2
  exit 1
fi
#pip install awscli --upgrade --user
# install aws cli v2
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
. ~/.bash_profile
# install helm 3
curl -sSL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
helm version --short
helm repo add stable https://charts.helm.sh/stable
helm completion bash >> ~/.bash_completion
. /etc/profile.d/bash_completion.sh
. ~/.bash_completion
source <(helm completion bash)
helm repo update
EOF
chmod +x ~/environment/scripts/install-tools
~/environment/scripts/install-tools

创建EKS集群(版本:1.18)

配置创建集群需要的环境变量

export ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
export AWS_REGION=$(curl -s 169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.region')
export EKS_CLUSTER_NAME=eksworkshop-albc
export EKS_NODEGROUP_NAME=ung-1
export EKS_MANAGED_NODEGROUP_NAME=mng-1
export AWS_DEFAULT_REGION=$AWS_REGION
echo "export ACCOUNT_ID=${ACCOUNT_ID}" >> ~/.bash_profile
echo "export AWS_REGION=${AWS_REGION}" >> ~/.bash_profile
echo "export AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}" >> ~/.bashrc
echo "export EKS_CLUSTER_NAME=${EKS_CLUSTER_NAME}" >> ~/.bashrc
echo "export EKS_NODEGROUP_NAME=${EKS_NODEGROUP_NAME}" >> ~/.bashrc
aws configure set default.region ${AWS_REGION}
aws configure get default.region

运行下列命令创建EKS集群配置模板文件 eks-cluster.yml.template

cat > ~/environment/scripts/eks-cluster.yml.template <<-"EOF"
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: $EKS_CLUSTER_NAME
  region: $AWS_REGION
  version: "1.18"
nodeGroups:
  - name: $EKS_NODEGROUP_NAME
    minSize: 1
    maxSize: 3
    desiredCapacity: 1
managedNodeGroups:
  - name: $EKS_MANAGED_NODEGROUP_NAME
    minSize: 1
    maxSize: 3
    desiredCapacity: 1
    labels: {role: worker}
    tags:
      nodegroup-role: worker
    iam:
      withAddonPolicies:
        externalDNS: true
        certManager: true
        albIngress: true
        autoScaler: true
EOF

利用 eksctl 工具来创建 EKS 集群,运行下列命令创建一个 EKS 1.18 的集群,同时会创建一个新的 VPC,并且在该VPC中创建一个1节点的托管节点组(Managed Node Group)和一个1节点的非托管节点组(Unmanaged Node Group),整个过程大概需要 15分钟左右。

envsubst < ~/environment/scripts/eks-cluster.yml.template > ~/environment/scripts/eks-cluster.yml
eksctl create cluster -f ~/environment/scripts/eks-cluster.yml

运行下列命令测试 EKS 集群是否正常工作

kubectl get nodes --show-labels

如果我们看到 2 个 m5.large 的EC2实例,说明 EKS 集群正确启动,并且相应的权限也是正确的。可以在管理控制台的EKS服务的集群列表中查看刚刚创建好的集群节点组(托管)、网络和其他配置。

同时,因为eksctl工具的底层实现是依赖CloudFormation服务的,所以可以在CloudFormation服务的管理界面查看为了创建集群而新建的3个CloudFormaiton模板:集群控制平面Stack、托管节点组Stack、非托管节点组Stack。

安装AWS Load Balancer Controller V2

因为AWS Load Balancer Controller对于版本在1.1.3以上的ALB Ingress Controller的Ingress定义能够完全兼容,所以本实验将仅安装AWS Load Balancer Controller V2而不会安装V1的ALB Ingress Controller,并在AWS Load Balancer Controller(V2)的环境中完成V1中Ingress定义的演示,有需求的读者可以使用同样的步骤在V1的环境中自行完成实验。

首先,为EKS集群创建IAM OIDC provider

eksctl utils associate-iam-oidc-provider --cluster=$EKS_CLUSTER_NAME --approve

接下来,下载AWS Load Balancer Controller需要的IAM Policy文件

curl -o iam-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/main/docs/install/iam_policy.json

基于Policy文件创建新的IAM Policy,并记录生成Policy的ARN

aws iam create-policy \
    --policy-name AWSLoadBalancerControllerIAMPolicy \
    --policy-document file://iam-policy.json
export POLICY_ARN=$(aws iam list-policies | jq '.Policies | .[] | select(.PolicyName=="ALBIngressControllerIAMPolicy").Arn')

使用上面生产的ARN创建AWS Load Balancer Controller需要的IAM角色和ServiceAccount

eksctl create iamserviceaccount \
--cluster=$EKS_CLUSTER_NAME \
--namespace=kube-system \
--name=aws-load-balancer-controller \
--attach-policy-arn=$POLICY_ARN \
--approve

安装cert-manager

kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.0.2/cert-manager.yaml

运行下列命令下载、替换your-cluster-name,并应用AWS Load Balancer Controller的yaml定义

curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/main/docs/install/v2_0_0_full.yaml
sed -i 's/your-cluster-name/'"$EKS_CLUSTER_NAME"'/' v2_0_0_full.yaml
kubectl apply -f v2_0_0_full.yaml

使用下列命令查验load balancer controller服务安装是否成功

kubectl  get svc  aws-load-balancer-webhook-service -n kube-system

部署示例服务nginx-1与nginx-2和相应的Ingress

部署nginx-1

运行下列命令生成样例程序nginx-1的K8S部署、服务及Ingress对象的定义,注意的是下面Ingress Annotation配置中的alb.ingress.kubernetes.io/target-type设置为ip,也就是使用了mode ip模式,流量经由ALB直接跳转到K8S内部的Pod上,如果这个值为instance,流量会经过节点上的临时端口,根据kube-proxy的设置转发到Pod上。

cat <<EoF > ~/environment/nginx-1.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-1
  labels:
    app: nginx-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-1
  template:
    metadata:
      labels:
        app: nginx-1
    spec:
      containers:
      - name: nginx-1
        image: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: "nginx-1"
spec:
  selector:
    app: nginx-1
  type: ClusterIP
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: "nginx-1"
  namespace: "default"
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
  labels:
    app: nginx
spec:
  rules:
    - http:
        paths:
          - path: /*
            backend:
              serviceName: "nginx-1"
              servicePort: 80
EoF

部署nginx-1服务及Ingress对象

kubectl apply -f ~/environment/nginx-1.yaml

查看nginx-1服务及生成的Ingress对象

kubectl get svc nginx-1
kubectl get ing nginx-1

如图1所示,以上述命令输出的Ingress:nginx-1的ADDRESS为过滤条件在EC2服务控制台查找对应的ALB实例,查看基本信息,待该ALB的状态由provisioning转变为active后,访问上面命令输出的地址(即该ALB的DNS地址)信息,打开浏览器或者使用curl命令访问该地址,验证http请求可以成功返回。

部署nginx-2

将上一节中出现的所有nginx-1改为nginx-2,应用所有步骤,发现系统会创建一个全新的ALB,访问生成的外部地址验证正常工作。

cat <<EoF > ~/environment/nginx-2.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-2
  labels:
    app: nginx-2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-2
  template:
    metadata:
      labels:
        app: nginx-2
    spec:
      containers:
      - name: nginx-2
        image: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: "nginx-2"
spec:
  selector:
    app: nginx-2
  type: ClusterIP
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: "nginx-2"
  namespace: "default"
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
  labels:
    app: nginx
spec:
  rules:
    - http:
        paths:
          - path: /*
            backend:
              serviceName: "nginx-2"
              servicePort: 80
EoF

部署nginx-2服务及Ingress对象

kubectl apply -f ~/environment/nginx-2.yaml

查看nginx-1与nginx-2对应的Ingress对象

kubectl get ing

相应的可以在EC2服务的Load Balancer子项的Load Balancer列表中以每个Ingress对象对应的地址信息为过滤条件查看为每个Ingress对象创建的ALB

到目前为止,我们创建的每个Ingress对象都分别对应独立K8S服务和独立的ALB,接下来我们将分别利用AWS Load Balancer Controller V1和V2使用单个Ingress与单个ALB来实现映射多个K8S服务的实验。

使用ALB Ingress Controller(V1)Ingress定义实现发布多个K8S服务

执行下列命令在已经创建好的nginx-1的pod中创建新路径v1和index页面

# 生成新的nginx 1服务路径v1
export mypod=$(kubectl get pods  -o jsonpath='{.items[0].metadata.name}')
kubectl exec -it ${mypod} -- bash
mkdir -p /usr/share/nginx/html/v1 && echo "welcome to Nginx v1" > /usr/share/nginx/html/v1/index.html
exit

执行下列命令在已经创建好的nginx-2的pod中创建新路径v2和index页面

# 生成新的nginx 2服务路径v2
export mypod=$(kubectl get pods  -o jsonpath='{.items[1].metadata.name}')
kubectl exec -it ${mypod} -- bash
mkdir -p /usr/share/nginx/html/v2 && echo "welcome to Nginx v2" > /usr/share/nginx/html/v2/index.html
exit

创建一个新的ALB Ingress对象,将nginx-1的v1目录和nginx-2的v2目录对应到不同的HTTP路径上

cat <<EoF > ~/environment/multi-nginx-path-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: "multi-nginx-path"
  namespace: "default"
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
  labels:
    app: nginx
spec:
  rules:
    - http:
        paths:
          - path: /v1/*
            backend:
              serviceName: "nginx-1"
              servicePort: 80
          - path: /v2/*
            backend:
              serviceName: "nginx-2"
              servicePort: 80
EoF
kubectl apply -f ~/environment/multi-nginx-path-ingress.yaml

运行下列命令查看如下图所示的新创建好的multi-nginx-path Ingress对象,同上也可以在Load Balancer列表中查看对应multi-nginx-path的ALB

kubectl get ing multi-nginx-path

运行下面的命令得到新的ALB Ingress对应的外部DNS地址(同样需要等待ALB创建完成)

export loadbalancer=$(kubectl get ing multi-nginx-path -o jsonpath='{.status.loadBalancer.ingress[*].hostname}')

等待ALB创建完成状态变为active后,分别访问下面两个URL来验证使用同一个Ingress对象定义来实现映射到两个K8S服务:nginx-1和nginx-2的实验结果,可以看到不同的HTTP路径请求会被转发到URL映射的后台服务并分别返回:welcome to Nginx v1与welcome to Nginx v2

curl $loadbalancer/v1/
curl $loadbalancer/v2/

这就是如何在ALB Ingress Controller(V1)中使用同一个Ingress定义生成一个ALB来实现发布多个K8S服务的例子,接下来我们看一下在V2中的实现方法。

使用AWS Load Balancer Controller(V2)Ingress定义实现发布多个K8S服务

AWS Load Balancer Controller(V2)通过Annotation: alb.ingress.kubernetes.io/group.name来支持在不同的Ingress对象上共享同一个ALB来实现多个K8S服务发布。

运行下列命令将alb.ingress.kubernetes.io/group.name: "share-alb"的注释分别添加到Ingress对象share-1和share-2当中,后端的K8S服务依然沿用上文创建好的nginx-1与nginx-2服务

cat <<EoF > ~/environment/share-1.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: "share-1"
  namespace: "default"
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/group.name: "share-alb"
  labels:
    app: nginx
spec:
  rules:
    - http:
        paths:
          - path: /v1/*
            backend:
              serviceName: "nginx-1"
              servicePort: 80
EoF
kubectl apply -f ~/environment/share-1.yaml
cat <<EoF > ~/environment/share-2.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: "share-2"
  namespace: "default"
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/group.name: "share-alb"
  labels:
    app: nginx
spec:
  rules:
    - http:
        paths:
          - path: /v2/*
            backend:
              serviceName: "nginx-2"
              servicePort: 80
EoF
kubectl apply -f ~/environment/share-2.yaml

运行下列命令查验Ingress对象share-1与share-2对应的ALB DNS名称,会得到如下图所示的结果:DNS Address是完全相同的,也就是说后台服务nginx-1与nginx-2虽然分别对应了一个Ingress对象share-1与share-2,但这两个服务和Ingress共享着一个ALB,也可以在ALB列表验证同样的实验结果

kubectl get ing share-1
kubectl get ing share-2

删除上文中创建的K8S对象

按照与上述创建各种资源相反的顺序删除创建的各种K8S对象

kubectl delete -f ~/environment/share-2.yaml
kubectl delete -f ~/environment/share-1.yaml
kubectl delete -f ~/environment/multi-nginx-path-ingress.yaml 
kubectl delete -f ~/environment/nginx-2.yaml
kubectl delete -f ~/environment/nginx-1.yaml

删除EKS集群和Cloud9环境(如需进行本文下篇中的实验内容请跳过此步)

eksctl delete nodegroup --cluster $EKS_CLUSTER_NAME --name $EKS_NODEGROUP_NAME
eksctl delete nodegroup --cluster $EKS_CLUSTER_NAME --name $EKS_MANAGED_NODEGROUP_NAME
eksctl delete cluster --name $EKS_CLUSTER_NAME

最后,在AWS控制台的Cloud9服务的环境列表中删除eksworksohp演示环境。

兼容性

如上面示例中演示,AWS Load Balancer Controller对于版本在1.1.3以上的ALB Ingress Controller的Ingress定义能够完全兼容。

从AWSALBIngressController(v1) 到 AWSLoadBalancerController(v2) 的升级可以参考 https://kubernetes-sigs.github.io/aws-load-balancer-controller/guide/upgrade/migratev1v2/

总结

在EKS平台上无论您是使用ALB Ingress Controller(V1)还是使用最新发布的AWS Load Balancer Controller(V2)都能够很轻松的实现在单个Application Load Balancer上发布多个K8S容器服务。这两个版本的Ingress Controller除了以上演示的功能之外,还支持在Ingress定义中启用健康检查、开启ALB访问日志、启用证书实现HTTPS通讯等诸多功能,有兴趣的读者可以参考AWS Load Balancer Controller在线文档中相应的Annotation章节中的介绍。

参考资料

本篇作者

田大鹏

AWS解决方案架构师,负责帮助客户进行上云架构的设计和咨询。在电商及互联网行业有丰富的咨询和架构设计经验。加入 AWS 前曾于全球领先的存储和虚拟化企业,担任研发主管工程师及研发经理多种职位,负责在线存储及备份系统的多个子系统的高并发、高可用系统架构设计,应用微服务化等敏捷项目。