Skip to content

Commit 2913b2c

Browse files
Merge pull request openshift#7652 from mtulio/edge-aws-wavelength-zone-byovpc
SPLAT-1218: AWS BYO VPC support for Wavelength Zones
2 parents e2504ab + d655486 commit 2913b2c

File tree

6 files changed

+230
-28
lines changed

6 files changed

+230
-28
lines changed

docs/user/aws/install_edge-zones.md

Lines changed: 217 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ This document is split into the following sections:
1010
- [Install a cluster into existing VPC with Local Zone subnets](#ipi-localzones-existing-vpc) (4.13+)
1111
- [Extend worker nodes to AWS Local Zones in existing clusters [Day 2]](#day2-localzones)
1212
- [Wavelength Zones](#wavelength-zones)
13-
- [Install a cluster extending nodes to the Wavelength Zone [new VPC]](#ipi-wavelength) (4.15+)
13+
- [Install a cluster extending nodes to the Wavelength Zone [new VPC]](#ipi-wavelength-auto)
14+
- [Install a cluster on AWS in existing VPC with subnets in Wavelength Zone](#ipi-wavelength-byovpc)
1415
- [Use Cases](#use-cases)
1516

1617
## Prerequisites for edge zones
@@ -472,12 +473,12 @@ The default EBS for Local Zone locations is `gp2`, different than the default wo
472473

473474
The preferred list of instance types follows the same order of worker pools, depending
474475
on the availability of the location, one of those instances will be chosen*:
475-
> Note: This list can be updated over the time
476+
> Note: This list can be updated over time
476477
- `m6i.xlarge`
477478
- `m5.xlarge`
478479
- `c5d.2xlarge`
479480

480-
The `edge` compute pool will also create new labels to help developers to
481+
The `edge` compute pool will also create new labels to help developers
481482
deploy their applications onto those locations. The new labels introduced are:
482483
- `node-role.kubernetes.io/edge=''`
483484
- `zone_type=local-zone`
@@ -610,15 +611,15 @@ aws cloudformation delete-stack \
610611

611612
## Extend worker nodes to AWS Local Zones in existing clusters [Day 2] <a name="#day2-localzones"></a>
612613

613-
To create worker nodes in AWS Local Zones in existing clusters it is required those steps:
614+
The following steps are required to create worker nodes in AWS Local Zones:
614615

615616
- Make sure the overlay network MTU is set correctly to support the AWS Local Zone limitations
616617
- Create subnets in AWS Local Zones, and dependencies (subnet association)
617618
- Create MachineSet to deploy compute nodes in Local Zone subnets
618619

619620
When the cluster is installed using the edge compute pool, the MTU for the overlay network is automatically adjusted depending on the network plugin used.
620621

621-
When the cluster was already installed without the edge compute pool, without Local Zone support, the required dependencies must be satisfied. The steps below cover both scenarios.
622+
When the cluster was already installed without the edge compute pool, and without Local Zone support, the required dependencies must be satisfied. The steps below cover both scenarios.
622623

623624
### Adjust the MTU of the overlay network
624625

@@ -628,8 +629,6 @@ The [KCS](https://access.redhat.com/solutions/6996487) covers the required step
628629

629630
***Example changing the default MTU (9001) to the maximum allowed for network plugin OVN-Kubernetes***:
630631

631-
> TODO: need to check if can omit the `spec.migration.mtu.machine` on the process.
632-
633632
```bash
634633
635634
$ CLUSTER_MTU_CUR=$(oc get network.config.openshift.io/cluster --output=jsonpath={.status.clusterNetworkMTU})
@@ -726,7 +725,7 @@ aws ec2 modify-availability-zone-group \
726725
--opt-in-status opted-in
727726
```
728727

729-
The request will be processed in background, it could take a few minutes. Check if the field `OptInStatus` has the value `opted-in` before proceeding:
728+
The request will be processed in the background, it could take a few minutes. Check if the field `OptInStatus` has the value `opted-in` before proceeding:
730729

731730
```bash
732731
aws --region us-east-1 ec2 describe-availability-zones \
@@ -735,31 +734,31 @@ aws --region us-east-1 ec2 describe-availability-zones \
735734
--query "AvailabilityZones[].OptInStatus"
736735
```
737736

738-
## Install a cluster extending nodes to the Wavelength Zone [new VPC] <a name="#ipi-wavelength"></a>
737+
## Install a cluster extending nodes to the Wavelength Zone [new VPC] <a name="#ipi-wavelength-auto"></a>
739738

740739
### Prerequisites
741740

742741
#### Additional AWS Permissions
743742

744-
IAM Permissions when installer fully automate the Wavelength zones creation, and deletion.
743+
IAM Permissions when the installer fully automates the creation and deletion of subnets in Wavelength zones.
745744

746745
- [Opt-int permissions](#pre-iam-opt-in)
747746

748747
- Permissions to create and delete the Carrier Gateway:
749748

750749
```json
751750
{
752-
"Version": "2012-10-17",
753-
"Statement": [
754-
{
755-
"Effect": "Allow",
756-
"Action": [
757-
"ec2:DeleteCarrierGateway",
758-
"ec2:CreateCarrierGateway"
759-
],
760-
"Resource": "*"
761-
}
762-
]
751+
"Version": "2012-10-17",
752+
"Statement": [
753+
{
754+
"Effect": "Allow",
755+
"Action": [
756+
"ec2:DeleteCarrierGateway",
757+
"ec2:CreateCarrierGateway"
758+
],
759+
"Resource": "*"
760+
}
761+
]
763762
}
764763
```
765764

@@ -806,6 +805,203 @@ EOF
806805
./openshift-install destroy cluster --dir ${$INSTALL_DIR}
807806
```
808807

808+
## Install a cluster on AWS in existing VPC with subnets in Wavelength Zone <a name="#ipi-wavelength-byovpc"></a>
809+
810+
This section describes how to create the CloudFormation stack to provision VPC and subnets in Wavelength Zones, and then install an OpenShift cluster into an existing network.
811+
812+
### Prerequisites
813+
814+
- [Opt-into AWS Wavelength Zone](#opt-into-aws-wavelength-zone)
815+
816+
### Create the Network Stack (VPC and subnets)
817+
818+
Steps:
819+
820+
- Export the general variables for the cluster, and adapt them according to your environment:
821+
822+
```sh
823+
export CLUSTER_REGION=us-east-1
824+
export CLUSTER_NAME=wlz-byovpc
825+
export PULL_SECRET_FILE=${HOME}/path/to/pull-secret.json
826+
export BASE_DOMAIN=example.com
827+
export SSH_PUB_KEY_FILE=$HOME/.ssh/id_rsa.pub
828+
829+
export CIDR_VPC="10.0.0.0/16"
830+
831+
# Set the Wavelength Zone to create subnets
832+
export ZONE_NAME="us-east-1-wl1-nyc-wlz-1"
833+
export SUBNET_CIDR_PUB="10.0.128.0/24"
834+
export SUBNET_CIDR_PVT="10.0.129.0/24"
835+
```
836+
837+
- Export the CloudFormation template path (assuming you are in the root of the installer repository):
838+
839+
```sh
840+
TEMPLATE_NAME_VPC="upi/aws/cloudformation/01_vpc.yaml"
841+
TEMPLATE_NAME_CARRIER_GW="upi/aws/cloudformation/01_vpc_01_carrier_gateway.yaml"
842+
TEMPLATE_NAME_SUBNET="upi/aws/cloudformation/01_vpc_99_subnet.yaml"
843+
```
844+
845+
- Create the CloudFormation stack for VPC:
846+
847+
```sh
848+
export STACK_VPC=${CLUSTER_NAME}-vpc
849+
aws cloudformation create-stack \
850+
--region ${CLUSTER_REGION} \
851+
--stack-name ${STACK_VPC} \
852+
--template-body file://$TEMPLATE_NAME_VPC \
853+
--parameters \
854+
ParameterKey=VpcCidr,ParameterValue="${CIDR_VPC}" \
855+
ParameterKey=AvailabilityZoneCount,ParameterValue=3 \
856+
ParameterKey=SubnetBits,ParameterValue=12
857+
858+
aws --region $CLUSTER_REGION cloudformation wait stack-create-complete --stack-name ${STACK_VPC}
859+
aws --region $CLUSTER_REGION cloudformation describe-stacks --stack-name ${STACK_VPC}
860+
861+
export VPC_ID=$(aws --region $CLUSTER_REGION cloudformation describe-stacks \
862+
--stack-name ${STACK_VPC} \
863+
| jq -r '.Stacks[0].Outputs[] | select(.OutputKey=="VpcId").OutputValue' )
864+
```
865+
866+
- Create the Carrier Gateway:
867+
868+
```sh
869+
export STACK_CAGW=${CLUSTER_NAME}-cagw
870+
aws cloudformation create-stack \
871+
--region ${CLUSTER_REGION} \
872+
--stack-name ${STACK_CAGW} \
873+
--template-body file://$TEMPLATE_NAME_CARRIER_GW \
874+
--parameters \
875+
ParameterKey=VpcId,ParameterValue="${VPC_ID}" \
876+
ParameterKey=ClusterName,ParameterValue="${CLUSTER_NAME}"
877+
878+
aws --region $CLUSTER_REGION cloudformation wait stack-create-complete --stack-name ${STACK_CAGW}
879+
aws --region $CLUSTER_REGION cloudformation describe-stacks --stack-name ${STACK_CAGW}
880+
```
881+
882+
- Extract the variables to create the subnets
883+
884+
```sh
885+
export ZONE_SUFFIX=$(echo ${ZONE_NAME/${CLUSTER_REGION}-/})
886+
887+
export ROUTE_TABLE_PUB=$(aws --region $CLUSTER_REGION cloudformation describe-stacks \
888+
--stack-name ${STACK_CAGW} \
889+
| jq -r '.Stacks[0].Outputs[] | select(.OutputKey=="PublicRouteTableId").OutputValue' )
890+
891+
export ROUTE_TABLE_PVT=$(aws --region $CLUSTER_REGION cloudformation describe-stacks \
892+
--stack-name ${STACK_VPC} \
893+
| jq -r '.Stacks[0].Outputs[]
894+
| select(.OutputKey=="PrivateRouteTableIds").OutputValue
895+
| split(",")[0] | split("=")[1]' \
896+
)
897+
898+
# Review the variables (optional)
899+
cat <<EOF
900+
CLUSTER_REGION=$CLUSTER_REGION
901+
VPC_ID=$VPC_ID
902+
AZ_NAME=$AZ_NAME
903+
AZ_SUFFIX=$AZ_SUFFIX
904+
ZONE_GROUP_NAME=$ZONE_GROUP_NAME
905+
ROUTE_TABLE_PUB=$ROUTE_TABLE_PUB
906+
ROUTE_TABLE_PVT=$ROUTE_TABLE_PVT
907+
SUBNET_CIDR_PUB=$SUBNET_CIDR_PUB
908+
SUBNET_CIDR_PVT=$SUBNET_CIDR_PVT
909+
EOF
910+
```
911+
912+
- Create the CloudFormation stack to provision the public and private subnets:
913+
914+
```sh
915+
export STACK_SUBNET=${CLUSTER_NAME}-subnets-${AZ_SUFFIX}
916+
aws cloudformation create-stack \
917+
--region ${CLUSTER_REGION} \
918+
--stack-name ${STACK_SUBNET} \
919+
--template-body file://$TEMPLATE_NAME_SUBNET \
920+
--parameters \
921+
ParameterKey=VpcId,ParameterValue="${VPC_ID}" \
922+
ParameterKey=ClusterName,ParameterValue="${CLUSTER_NAME}" \
923+
ParameterKey=ZoneName,ParameterValue="${AZ_NAME}" \
924+
ParameterKey=PublicRouteTableId,ParameterValue="${ROUTE_TABLE_PUB}" \
925+
ParameterKey=PublicSubnetCidr,ParameterValue="${SUBNET_CIDR_PUB}" \
926+
ParameterKey=PrivateRouteTableId,ParameterValue="${ROUTE_TABLE_PVT}" \
927+
ParameterKey=PrivateSubnetCidr,ParameterValue="${SUBNET_CIDR_PVT}"
928+
929+
aws --region $CLUSTER_REGION cloudformation wait stack-create-complete --stack-name ${STACK_SUBNET}
930+
aws --region $CLUSTER_REGION cloudformation describe-stacks --stack-name ${STACK_SUBNET}
931+
```
932+
933+
### Create the cluster
934+
935+
- Extract the subnets to be used in the install-config.yaml:
936+
937+
```sh
938+
# Regular Availability Zones (public and private) from VPC CloudFormation Stack
939+
mapfile -t SUBNETS < <(aws --region $CLUSTER_REGION cloudformation describe-stacks --stack-name "${STACK_VPC}" --query "Stacks[0].Outputs[?OutputKey=='PrivateSubnetIds'].OutputValue" --output text | tr ',' '\n')
940+
941+
mapfile -t -O "${#SUBNETS[@]}" SUBNETS < <(aws --region $CLUSTER_REGION cloudformation describe-stacks --stack-name "${STACK_VPC}" --query "Stacks[0].Outputs[?OutputKey=='PublicSubnetIds'].OutputValue" --output text | tr ',' '\n')
942+
943+
# Private subnet for Wavelenth Zones from subnets CloudFormation Stack
944+
mapfile -t -O "${#SUBNETS[@]}" SUBNETS < <(aws --region $CLUSTER_REGION cloudformation describe-stacks --stack-name "${STACK_SUBNET}" --query "Stacks[0].Outputs[?OutputKey=='PrivateSubnetIds'].OutputValue" --output text | tr ',' '\n')
945+
```
946+
947+
- Create install-config.yaml:
948+
949+
```sh
950+
cat <<EOF > ./install-config.yaml
951+
apiVersion: v1
952+
publish: External
953+
baseDomain: ${BASE_DOMAIN}
954+
metadata:
955+
name: "${CLUSTER_NAME}"
956+
platform:
957+
aws:
958+
region: ${CLUSTER_REGION}
959+
subnets:
960+
$(for SB in ${SUBNETS[*]}; do echo " - $SB"; done)
961+
pullSecret: '$(cat ${PULL_SECRET_FILE} | awk -v ORS= -v OFS= '{$1=$1}1')'
962+
sshKey: |
963+
$(cat ${SSH_PUB_KEY_FILE})
964+
EOF
965+
```
966+
967+
- Create the cluster:
968+
969+
```sh
970+
./openshift-install create cluster
971+
```
972+
973+
### Destroy the cluster and network dependencies
974+
975+
- Destroy the cluster:
976+
977+
```sh
978+
./openshift-install destroy cluster
979+
```
980+
981+
- Destroy the subnet stack:
982+
983+
```sh
984+
aws cloudformation delete-stack \
985+
--region ${AWS_REGION} \
986+
--stack-name ${STACK_SUBNET}
987+
```
988+
989+
- Destroy the Carrier Gateway stack:
990+
991+
```sh
992+
aws cloudformation delete-stack \
993+
--region ${AWS_REGION} \
994+
--stack-name ${STACK_CAGW}
995+
```
996+
997+
- Destroy the VPC Stack:
998+
999+
```sh
1000+
aws cloudformation delete-stack \
1001+
--region ${AWS_REGION} \
1002+
--stack-name ${STACK_VPC}
1003+
```
1004+
8091005
___
8101006
___
8111007

pkg/asset/installconfig/aws/availabilityzones.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func edgeZones(ctx context.Context, session *session.Session, region string) ([]
103103
return nil, fmt.Errorf("unable to retrieve Local Zone names: %w", err)
104104
}
105105

106-
wavelengthZones, err := filterZonesByType(ctx, session, region, typesaws.WavelengtyZoneType)
106+
wavelengthZones, err := filterZonesByType(ctx, session, region, typesaws.WavelengthZoneType)
107107
if err != nil {
108108
return nil, fmt.Errorf("unable to retrieve Wavelength Zone names: %w", err)
109109
}

pkg/asset/installconfig/aws/subnet.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ func subnets(ctx context.Context, session *session.Session, region string, ids [
164164
}
165165

166166
// AWS Local Zones are grouped as Edge subnets
167-
if meta.Zone.Type == typesaws.LocalZoneType {
167+
if meta.Zone.Type == typesaws.LocalZoneType ||
168+
meta.Zone.Type == typesaws.WavelengthZoneType {
168169
subnetGroups.Edge[id] = meta
169170
continue
170171
}
@@ -228,6 +229,9 @@ func isSubnetPublic(rt []*ec2.RouteTable, subnetID string) (bool, error) {
228229
if strings.HasPrefix(aws.StringValue(route.GatewayId), "igw") {
229230
return true, nil
230231
}
232+
if strings.HasPrefix(aws.StringValue(route.CarrierGatewayId), "cagw") {
233+
return true, nil
234+
}
231235
}
232236

233237
return false, nil

pkg/asset/installconfig/aws/validation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ func validateZoneLocal(ctx context.Context, meta *Metadata, fldPath *field.Path,
423423
for _, zone := range zones {
424424
if aws.StringValue(zone.ZoneName) == zoneName {
425425
switch aws.StringValue(zone.ZoneType) {
426-
case awstypes.LocalZoneType, awstypes.WavelengtyZoneType:
426+
case awstypes.LocalZoneType, awstypes.WavelengthZoneType:
427427
default:
428428
return field.Invalid(fldPath, zoneName, fmt.Sprintf("only zone type local-zone or wavelength-zone are valid in the edge machine pool: %s", aws.StringValue(zone.ZoneType)))
429429
}

pkg/tfvars/aws/aws.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ func TFVars(sources TFVarsSources) ([]byte, error) {
112112
if _, ok := sources.AvailabilityZones[zoneName]; !ok {
113113
return nil, errors.New(fmt.Sprintf("unable to find the zone when generating terraform vars: %s", zoneName))
114114
}
115-
if sources.AvailabilityZones[zoneName].Type == typesaws.LocalZoneType || sources.AvailabilityZones[zoneName].Type == typesaws.WavelengtyZoneType {
115+
if sources.AvailabilityZones[zoneName].Type == typesaws.LocalZoneType ||
116+
sources.AvailabilityZones[zoneName].Type == typesaws.WavelengthZoneType {
116117
edgeLocalZoneMap[zoneName] = exists
117118
continue
118119
}

pkg/types/aws/availabilityzones.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package aws
33
const (
44
// AvailabilityZoneType is the type of regular zone placed on the region.
55
AvailabilityZoneType = "availability-zone"
6-
// LocalZoneType is the type of Local zone placed on the metropolitan areas.
6+
// LocalZoneType is the type of AWS Local Zones placed on the metropolitan area.
77
LocalZoneType = "local-zone"
8-
// WavelengtyZoneType is the type of Wavelength zone placed in the Carrier infrastructure closer to areas.
9-
WavelengtyZoneType = "wavelength-zone"
8+
// WavelengthZoneType is the type of AWS Wavelength Zones placed on the telecommunications
9+
// providers’ data centers at the edge of the 5G network.
10+
WavelengthZoneType = "wavelength-zone"
1011
// ZoneOptInStatusOptedIn is the opt-in status of the zone.
1112
// For Availability Zones, this parameter always has the value of opt-in-not-required.
1213
// For Local Zones and Wavelength Zones, this parameter is the opt-in status.

0 commit comments

Comments
 (0)