IRSA

EKS 的 IRSA(IAM Roles for Service Accounts)是一种机制,允许在 EKS集群中运行的 Kubernetes Pods 安全地访问 AWS 服务,而不需要管理和分发长期的 AWS 凭证(如访问密钥)。通过 IRSA可以将 AWS IAM 角色与 Kubernetes 的 Service Account 关联,从而为 Pod 提供临时的 AWS 凭证。

IRSA 的作用

IRSA 解决了在 Kubernetes 环境中安全地向 Pod 提供 AWS 服务访问权限的问题。传统上,应用程序通过配置文件、环境变量等获取 AWS 凭证,这种方法存在安全风险。而 IRSA 提供了一种基于 IAM 角色和临时凭证的安全机制,使 Pod 可以在执行特定操作时自动获取到所需的权限。

IRSA 的工作原理

IRSA 的核心原理是将 Kubernetes Service Account 和 AWS IAM Role 关联在一起,允许 Pod 使用该 IAM Role 来获取临时的 AWS 凭证,进而访问特定的 AWS 服务。

主要步骤如下:

  • 创建 Kubernetes Service Account:在 Kubernetes 中为应用程序创建一个 Service Account
  • 创建 IAM 角色:在 AWS IAM 中创建一个 IAM 角色,角色需要具有适当的策略权限,允许访问指定的 AWS 服务。
  • 指定信任策略:在 IAM 角色中,指定该角色的信任实体为 eks.amazonaws.com,并使用 OIDC 提供者来确定具体的 Service Account。
  • 配置映射关系:通过 OpenID Connect (OIDC) 身份提供者,将 Kubernetes Service Account 映射到 IAM 角色,使得 Pod 可以以该角色的身份进行操作。
  • 应用程序自动获取临时凭证:当关联了该 Service Account 的 Pod 启动时,它会通过 OIDC 提供者获取与 IAM 角色相关联的临时凭证,然后使用这些凭证来访问 AWS 服务。

IRSA 机制的主要优势

  • 最小权限原则:可以为不同的 Service Account 配置不同的 IAM Role,确保 Pod 只拥有其执行任务所需的最低权限。
  • 无长期凭证:与直接使用静态 AWS 凭证不同,IRSA 使用临时的 AWS 凭证,减少了凭证泄露的风险。
  • 自动化凭证轮换:使用 IRSA 时,IAM 会自动处理临时凭证的创建、续订和过期,简化了凭证管理。
  • 简化 AWS 服务访问:通过 IRSA,Pod 可以直接使用 AWS SDK 与 AWS 服务交互,无需管理复杂的凭证配置。

IRSA 的架构示意图

[Kubernetes Service Account] ----> [OIDC Provider (EKS)] ----> [IAM Role] ----> [Temporary AWS Credentials]
                                \
                                 `--> [Kubernetes Pod]

Pod 使用的 Service Account 通过 OIDC 提供者向 IAM 请求角色的临时凭证,然后 Pod 使用这些凭证访问 AWS 资源。

IRSA示例

创建Golang应用:

main.go:

package main

import (
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
)

func main() {
	// Create a new session using the default credentials and region from environment variables or AWS config file
	sess, err := session.NewSession(&aws.Config{
		Region: aws.String("us-west-2"), // specify your region
	})

	if err != nil {
		log.Fatalf("Unable to create session: %v", err)
	}

	// Create a new S3 service client
	svc := s3.New(sess)

	// Call the ListBuckets API
	result, err := svc.ListBuckets(nil)
	if err != nil {
		log.Fatalf("Unable to list buckets: %v", err)
	}

	// Print the names of the buckets
	fmt.Println("Buckets:")
	for _, bucket := range result.Buckets {
		fmt.Printf("* %s created on %s\n", aws.StringValue(bucket.Name), bucket.CreationDate)
	}
}

go.mod:

module describe-s3

go 1.15

require (
	github.com/aws/aws-sdk-go v1.53.12 // indirect
	github.com/pkg/errors v0.9.1 // indirect
)

打包成ECR镜像

Dockerfile:

# Use the official Golang image as a build stage
FROM golang:1.15-alpine AS builder

# Set the Current Working Directory inside the container
WORKDIR /app

# Copy go mod and sum files
COPY go.mod go.sum ./

# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
RUN go mod download

# Copy the source from the current directory to the Working Directory inside the container
COPY . .

# Build the Go app
RUN go build -o main .

# Start a new stage from scratch
FROM alpine:latest

# Set the Current Working Directory inside the container
WORKDIR /root/

# Copy the Pre-built binary file from the previous stage
COPY --from=builder /app/main .

# Command to run the executable
CMD ["./main"]

上传到ECR:

docker build -t my-go-app .
docker tag my-go-app:latest aws_account_id.dkr.ecr.us-west-2.amazonaws.com/my-go-app:latest

aws ecr create-repository --repository-name my-go-app --region us-west-2
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin aws_account_id.dkr.ecr.us-west-2.amazonaws.com
docker push aws_account_id.dkr.ecr.us-west-2.amazonaws.com/my-go-app:latest

创建IRSA:

eksctl create iamserviceaccount \
  --cluster=eks-cilium \
  --namespace=default \
  --name=s3-irsa \
  --attach-policy-arn=arn:aws:iam::aws:policy/AmazonS3FullAccess \
  --override-existing-serviceaccounts \
  --approve

如果查看创建出来的Role的trust relationship, 会发现格式如下,在Condition字段里限制了具体的service account:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<account_id>:oidc-provider/<eks_cluster_oidc>"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "<eks_cluster_oidc>:sub": "system:serviceaccount:<namespace>:<service_account>"
        }
      }
    }
  ]
}

image-20241003061619948

部署deployment

S3.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: s3-access-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: s3-access
  template:
    metadata:
      labels:
        app: s3-access
    spec:
      serviceAccountName: s3-irsa
      containers:
      - name: s3-access-container
        image: 145197526627.dkr.ecr.us-west-2.amazonaws.com/my-go-app:latest
        ports:
        - containerPort: 8080
        env:
        - name: AWS_REGION
          value: "us-west-2"  # Replace with your region

kubectl apply -f s3.yaml

查看pod日志:

image-20240530113800309

image-20240530113851305

Java版本

项目结构有两个文件,一个pom.xml,另一个是App.java文件。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>list-s3-v2</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>s3</artifactId>
            <version>2.21.36</version> <!-- Replace with the latest version -->
        </dependency>
    </dependencies>

</project>

App.java:


import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.ListBucketsResponse;
import software.amazon.awssdk.services.s3.model.S3Exception;

public class ListS3 {

    public static void main(String[] args) {
        // Set your AWS region
        Region region = Region.US_EAST_1; // Change to your desired region

        // Create an S3 client
        S3Client s3 = S3Client.builder()
                .region(region)
                .build();

        try {
            // List S3 buckets
            ListBucketsResponse response = s3.listBuckets();

            // Print the bucket names
            System.out.println("S3 Buckets:");
            response.buckets().forEach(bucket -> System.out.println(bucket.name()));

        } catch (S3Exception e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
}

创建deployment

cat >my-deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      serviceAccountName: s3-irsa
      containers:
      - name: my-app
        image: maven

        command: ["sleep", "100000000"]
EOF

kubectl apply -f my-deployment.yaml

这里直接使用了maven镜像,里面安装了最新版本的java,并集成了maven工具。

部署好pod后,登录到上面:

kubectl exec -it xxxx bash

然后参考https://msk.kpingfan.com/09.kafka-java-development/02.cloud9-development/ 的内容进行maven构建,注意最后在执行打包好的文件时:

mvn exec:java -Dexec.mainClass="com.kpingfan.kafka.App"
要改成
mvn exec:java -Dexec.mainClass="App"