0. はじめに
ROSA HCP の標準の Ingress として、AWS NLB
がデプロイされています。また non-HTTPのTCPを使用したサービス用の Service Type=LoadBalancer としては、AWS CLB
が個別アプリケーション毎にデプロイされます。
(わかりやすさのために TCP (non HTTP) と分けて書いてますが、HTTP も TCP上のプロトコルなので左側の方法で管理できます)
一方で、AWS の WAF を使いたいので、AWS ALB
を使いたいなどの要件も出てくる事があります。
標準の NLB の前にAWSネットワーク設計などもろもろを駆使して ALB を設置する事もできますが、もっとシンプルに、ALB
をデプロイするために、AWS謹製の AWS Load Balancer Contoler を使って ALB(Ingress)
やNLB(Service Type=LoadBalancer)
をデプロイする事ができます。
OpenShift上では AWS Load Balancer Contoler を Operator 形式でパッケージしたAWS LoadBalancer Operator
が Operator Hub
上で提供されています。
OpenShift では必要の無い機能(例えば Fargate対応の mode) などはサポートしないようですが、Red Hatのサポートが受けられるパッケージという事でこちらを試して見ます。
オフィシャルドキュメントとしては、こちら、Red Hat の Cloud Service のエキスパート・チームである Black Belt チームが提供するサンプルはこちらと2通りのドキュメントが存在しています。
後者の方が必要な情報だけになっており、別途ツールも必要がなくわかり易いので、後者の手順に解説を付け加えたり、多少変更しながら手順をなぞって行こうと思います。
1. 前提
・ROSA HCP クラスターがインストール済み
・ROSA HCP クラスターは、3AZ 構成 ( AWS ALB は Single AZ 環境にはデプロイできない制限があるため)
2.環境変数の準備
環境変数を準備します。(全行コピーして一気に実行して問題ありません)
export ROSA_CLUSTER_NAME=$(oc get infrastructure cluster -o=jsonpath="{.status.infrastructureName}" | sed 's/-[a-z0-9]\{5\}$//')
export REGION=$(oc get infrastructure cluster -o=jsonpath="{.status.platformStatus.aws.region}")
export OIDC_ENDPOINT=$(oc get authentication.config.openshift.io cluster -o jsonpath='{.spec.serviceAccountIssuer}' | sed 's|^https://||')
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export SCRATCH="/tmp/${ROSA_CLUSTER_NAME}/alb-operator"
mkdir -p ${SCRATCH}
全ての環境変数がセットされている確認します。
echo "Cluster: ${ROSA_CLUSTER_NAME}, Region: ${REGION}, OIDC Endpoint: ${OIDC_ENDPOINT}, AWS Account ID: ${AWS_ACCOUNT_ID}"
3. VPC / Subnet へのタグ付け
サブネットに必要なタグ付けを行います。Subnet の ID は半角スペース区切りで並べます。
export VPC_ID=<vpc-id>
export PUBLIC_SUBNET_IDS=<public-subnets>
export PRIVATE_SUBNET_IDS=<private-subnets>
export CLUSTER_NAME=$(oc get infrastructure cluster -o=jsonpath="{.status.infrastructureName}")
AWS コンソールで ID を確認するのが面倒な場合は、以下の AWSコマンドで VPC ID / Subnet ID を取得できます。
aws ec2 describe-subnets \
--query "Subnets[].{ID:SubnetId,Name:Tags[?Key=='Name']|[0].Value,VPC:VpcId}" \
--output table
VPCにタグ付けします。
aws ec2 create-tags --resources ${VPC_ID} --tags Key=kubernetes.io/cluster/${CLUSTER_NAME},Value=owned --region ${REGION}
Public Subnet にタグ付けします
aws ec2 create-tags \
--resources ${PUBLIC_SUBNET_IDS} \
--tags Key=kubernetes.io/role/elb,Value='' \
--region ${REGION}
Private Subnet にタグ付けします
aws ec2 create-tags \
--resources ${PRIVATE_SUBNET_IDS} \
--tags Key=kubernetes.io/role/internal-elb,Value='' \
--region ${REGION}
4. AWS Load Balancer Contoler のインストール
新しいプロジェクト aws-load-balancer-operator
を作成します。
oc new-project aws-load-balancer-operator
Operator 用の IAM Policy を作成します。既に存在していなければ、PIAM olicy を作成します。 IAM Policy の定義は AWS Load Balancer Controller
の Github Repository から引っ張ってきています。
POLICY_ARN=$(aws iam list-policies --query \
"Policies[?PolicyName=='aws-load-balancer-operator-policy'].{ARN:Arn}" \
--output text)
if [[ -z "${POLICY_ARN}" ]]; then
wget -O "${SCRATCH}/load-balancer-operator-policy.json" \
https://raw.githubusercontent.com/openshift/aws-load-balancer-operator/main/hack/operator-permission-policy.json
POLICY_ARN=$(aws --region "$REGION" --query Policy.Arn \
--output text iam create-policy \
--policy-name aws-load-balancer-operator-policy \
--policy-document "file://${SCRATCH}/load-balancer-operator-policy.json")
fi
echo $POLICY_ARN
最期の POLICY_ARN
の値が設定されている事を確認してください。
Operator の IAM Role 用の Trust Policy を作成します。
cat <<EOF > "${SCRATCH}/trust-policy.json"
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Condition": {
"StringEquals" : {
"${OIDC_ENDPOINT}:sub": ["system:serviceaccount:aws-load-balancer-operator:aws-load-balancer-operator-controller-manager", "system:serviceaccount:aws-load-balancer-operator:aws-load-balancer-controller-cluster"]
}
},
"Principal": {
"Federated": "arn:aws:iam::$AWS_ACCOUNT_ID:oidc-provider/${OIDC_ENDPOINT}"
},
"Action": "sts:AssumeRoleWithWebIdentity"
}
]
}
EOF
Operator の IAM Role を作成し、先ほど作成した IAM Policy をアタッチします。
ROLE_ARN=$(aws iam create-role --role-name "${ROSA_CLUSTER_NAME}-alb-operator" \
--assume-role-policy-document "file://${SCRATCH}/trust-policy.json" \
--query Role.Arn --output text)
echo $ROLE_ARN
aws iam attach-role-policy --role-name "${ROSA_CLUSTER_NAME}-alb-operator" \
--policy-arn $POLICY_ARN
Operator 用の Secret を作成します。
cat << EOF | oc apply -f -
apiVersion: v1
kind: Secret
metadata:
name: aws-load-balancer-operator
namespace: aws-load-balancer-operator
stringData:
credentials: |
[default]
role_arn = $ROLE_ARN
web_identity_token_file = /var/run/secrets/openshift/serviceaccount/token
EOF
Operator をインストールします。
cat << EOF | oc apply -f -
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
name: aws-load-balancer-operator
namespace: aws-load-balancer-operator
spec:
upgradeStrategy: Default
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: aws-load-balancer-operator
namespace: aws-load-balancer-operator
spec:
channel: stable-v1.0
installPlanApproval: Automatic
name: aws-load-balancer-operator
source: redhat-operators
sourceNamespace: openshift-marketplace
startingCSV: aws-load-balancer-operator.v1.0.0
EOF
ここで少し待ちます。以下のコマンドで Pod のデプロイが完了するまで待ちます。
watch oc get pods -n aws-load-balancer-operator
AWS Load Balancer Controller
をインストールします。
cat << EOF | oc apply -f -
apiVersion: networking.olm.openshift.io/v1
kind: AWSLoadBalancerController
metadata:
name: cluster
spec:
credentials:
name: aws-load-balancer-operator
EOF
以下のコマンドで状態を確認します。
oc -n aws-load-balancer-operator get pods
以下のようになれば完了です。
$ oc -n aws-load-balancer-operator get pods
NAME READY STATUS RESTARTS AGE
aws-load-balancer-controller-cluster-84b6b57d55-zmmfg 1/1 Running 0 8m
aws-load-balancer-operator-controller-manager-76bfdbd7b4-k97vd 2/2 Running 0 8m
$
5. サンプルアプリのデプロイ
ここでは、EchoServer (HTTP) のアプリをデプロイしてみます。
サンプルのアプリは GitHub の AWS LoadBalancer Controllerの Repository のものを使用します。
5.1 ALB と HTTP アプリのデプロイ
echoserver
という namespace
を作成します。
oc apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/examples/echoservice/echoserver-namespace.yaml
このサンプルアプリは、OpenShift にデプロイするには、要求する権限が大きいので、OpenShift 側の権限を緩めます。(anyuid を許可)
oc adm policy add-scc-to-user anyuid system:serviceaccount:echoserver:default
以下のファイルを apply して Deployment
と Service
を echoserver
namespace
にデプロイします。
oc apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/examples/echoservice/echoserver-deployment.yaml
oc apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/examples/echoservice/echoserver-service.yaml
oc apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/examples/echoservice/echoserver-ingress.yaml
以上で Ingress
を使ってアプリが公開されました。
デプロイの完了まで数分程度あると思うので、Service
と Ingress
の中身を確認しておきます。
Service
は NodePort
になっています。
apiVersion: v1
kind: Service
metadata:
name: echoserver
namespace: echoserver
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: NodePort
selector:
app: echoserver
OpenShift
の標準の IngressController の場合は、クラスター内の Router Pod
が NLB
からNodePort
で一旦トラフィックを受け、 HTTP Header のドメイン名を見て、適切な Application Pod
の IP にトラフィックをフォワードします。そのため、各 Application Pod
は NodePort
の Service
を使いません。
一方、AWS LoadBalancer Controller
の場合は、ALB
が 各 Application Pod
のNodePort
にトラフィックを渡します。
Ingress
は、annotatins:
や spec.ingressClassName:
が必要です。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: echoserver
namespace: echoserver
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/tags: Environment=dev,Team=test
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Exact
backend:
service:
name: echoserver
port:
number: 80
この Ingress
リソースを AWS LoadBalance Controller
で処理してもらうためには、spec.ingressClassName: alb
が必要で、annotations
も正しく設定されている必要があります。
OpenShift も Ingress
リソースを処理できるため、spec.ingressClassName
が指定されて無い場合は、OpenShift が Ingress
リソースを処理しようとします。
Ingress
がデプロイされている事を確認します。
oc -n echoserver get ingress echoserver
以下のような出力になるはすです
$ oc -n echoserver get ingress echoserver
NAME CLASS HOSTS ADDRESS PORTS AGE
echoserver alb * k8s-echoserv-echoserv-94b1c3792d-970404549.ap-northeast-1.elb.amazonaws.com 80 73m
$
アプリがデプロイされている事を確認します。
oc -n echoserver get pods
以下のような出力になるはずです。
$ oc -n echoserver get pods
NAME READY STATUS RESTARTS AGE
echoserver-658d7b6fb-rvnk4 1/1 Running 0 4m14s
$
アプリにアクセスしてみます。
INGRESS=$(oc -n echoserver get ingress echoserver \
-o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
curl -sH "Host: ${INGRESS}" "http://${INGRESS}" | grep Hostname
このアプリケーションは、送信した HTTPリクエストをそのまま Response で返してきます。
以下のような出力になるはずです。
$ curl -sH "Host: ${INGRESS}" "http://${INGRESS}" | grep Hostname
Hostname: echoserver-658d7b6fb-zxpjh
$
(ALBがきちんと立ちあがったように見えても、反応が返って来ない(空のレスポンス)の場合があります。その場合は4分程度待ってみてください)
5.2 NLB と TCPアプリ (non HTTP) をデプロイ
今度は、TCP (non HTTP) の echoserver Pod デプロイして、Service Type=LoadBalancer
を使って外部公開してみます。
アプリは私の GitHub repository のものを使います。(中身は nc コマンドを実行しているだけですが、心配な方は deploymentで確認できます。怪しくないよ)
tcp-echo
という namespace
を作成します。
oc apply -f https://raw.githubusercontent.com/yuhkih/tcp-echo/refs/heads/main/namespace.yaml
Deployment
で tcp echoserver
Pod をデプロイし。Service Typle=LoadBalancer
でアプリを公開します。
oc apply -f https://raw.githubusercontent.com/yuhkih/tcp-echo/refs/heads/main/deployment.yaml
oc apply -f https://raw.githubusercontent.com/yuhkih/tcp-echo/refs/heads/main/service-aws-lb-operator.yaml
暫くするとアクセス可能になりますが、その間に、Service Type=LoadBalcer
の中身を確認しましょう。
annotations:
の部分に注目して下さい。
apiVersion: v1
kind: Service
metadata:
name: echoserver-nlb
namespace: echoserver
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: instance
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: LoadBalancer
selector:
app: echoserver
annotations
がある事で AWS LoadBalancer Controller
が Service
リソースを処理して NLB
が作成されます。
annotations
が無い場合は、OpenShift がこのリソースを処理して、CLB
が作成されます。
以下のコマンドでデプロイを確認してみます。
oc -n tcp-echo get service tcp-echo
以下のような出力が出てくるはずです。
$ oc -n tcp-echo get service tcp-echo
NAME TYPE CLUSTER-IP EXTERNAL-IP
PORT(S) AGE
tcp-echo LoadBalancer 172.30.139.153 k8s-tcpecho-tcpecho-ab3f09303e-1ed39f83c61b86b7.elb.ap-northeast-1.amazonaws.com 9000:30610/TCP 14m
$
アプリにアクセスしてみます。
NLB=$(oc -n tcp-echo get service tcp-echo \
-o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
nc ${NLB} 9000
以下のように入力した文字列と同じ文字列が出力で返ってくるはずです。
$ nc ${NLB} 9000
aaa
aaa
Hello!
Hello!
...
(Ctrl + C で終了)
(NLBがきちんと立ちあがったように見えても、ドメインが解決できなかったり、echo が返って来ない場合があります。その場合は4分程度待ってみてください)
6. 構成のまとめ
今回、ざっとドキュメントをなめてみたり、実機で確認した限りでは、AWS Load Balancer Operator (と大元の AWS Load Balancer Controller) では、アプリ一つにつき、1つの Load Balancerがデプロイされるようです。
コストや要件の違いで、ROSA デフォルト構成で使われる ELB と、AWS Load Balancer Operator (AWS Load Balancer Controller) でデプロイされる ELBを使いわける事になると思います。
また、Default の NLB ではなく、ALB を使いたいという要件の多くは、恐らく WAF 等を使いたいという所から来ると想像しますが、指定は annotation でできるようです。
尚、上記の図にはスペースの都合で書いてませんが、AWS Load Balancer Operator を使う場合でも、ROSA HCP デフォルトでデプロイされる NLB は削除する事はできません。
7. 環境の Clean Up
echoserver アプリを削除します
oc delete project echoserver
oc delete project tcp-echo
Operator 用の AWS Role を削除します
aws iam detach-role-policy \
--role-name "${ROSA_CLUSTER_NAME}-alb-operator" \
--policy-arn $POLICY_ARN
aws iam delete-role \
--role-name "${ROSA_CLUSTER_NAME}-alb-operator"
Operator 用の IAM Policyを削除します。
aws iam delete-policy --policy-arn $POLICY_ARN
Operator を導入した namespace を削除します。
oc delete project aws-load-balancer-operator
8. 補足
AWS Console を開かずに、作成された ELB の確認を AWS CLI から行う方法として以下があります。
ALB / NLB の場合
aws elbv2 describe-load-balancers --query 'LoadBalancers[*].[LoadBalancerName,Type,DNSName, VpcId]' --output table
CLB の場合
aws elb describe-load-balancers --region ap-northeast-1 --query 'LoadBalancerDescriptions[*].[LoadBalancerName,DNSName, VPCId]' --output table