Note: This article is an English translation of my original article, which you can find here.
In this article, I demonstrate how to run yum
in a private subnet using a VPC endpoint, taking the example of web server configuration via user data. I will also share sample code using AWS CDK.
Background and Objective
I set up a web server at the time of instance launch using user data.
yum update -y yum install -y httpd systemctl start httpd systemctl enable httpd echo "This is a sample website." > /var/www/html/index.html
However, the yum
command fails in environments without internet access. To resolve this issue, I place a VPC endpoint to use the Amazon Linux repository hosted on S3.
By employing this method, I can set up a web server without internet access and without deploying a NAT gateway.
Update yum on my AL1 or AL2 EC2 instance without internet access | AWS re:Post
Architecture
I deploy a gateway-type VPC endpoint for S3.
I use an ALB as the public endpoint for the web server. I also set up VPC endpoints for SSM for remote connections.
How to Use CDK
First, make sure to complete the setup for CDK.
The official workshop provides a detailed procedure from setting up the development environment to deployment.
AWS CDK Intro Workshop | AWS CDK Workshop
Sample Code
I create a CDK project and edit lib/cdk-private-yum-sample-stack.ts
.
mkdir cdk-private-yum-sample cd cdk-private-yum-sample cdk init -l typescript
import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; import * as elbv2_tg from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets' import { Construct } from 'constructs'; export class CdkPrivateYumSampleStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // vpc const vpc = new ec2.Vpc(this, 'WebVpc', { vpcName: 'web-vpc', ipAddresses: ec2.IpAddresses.cidr('172.16.0.0/16'), natGateways: 0, maxAzs: 2, subnetConfiguration: [ { cidrMask: 24, name: 'Public', subnetType: ec2.SubnetType.PUBLIC }, { cidrMask: 24, name: 'Private', subnetType: ec2.SubnetType.PRIVATE_ISOLATED } ], // remove all rules from default security group // See: https://docs.aws.amazon.com/config/latest/developerguide/vpc-default-security-group-closed.html restrictDefaultSecurityGroup: true }); // add private endpoints for session manager vpc.addInterfaceEndpoint('SsmEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.SSM, }); vpc.addInterfaceEndpoint('SsmMessagesEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES, }); vpc.addInterfaceEndpoint('Ec2MessagesEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.EC2_MESSAGES, }); // add private endpoint for Amazon Linux repository on s3 vpc.addGatewayEndpoint('S3Endpoint', { service: ec2.GatewayVpcEndpointAwsService.S3, subnets: [ { subnetType: ec2.SubnetType.PRIVATE_ISOLATED } ] }); // // security groups // const albSg = new ec2.SecurityGroup(this, 'AlbSg', { vpc, allowAllOutbound: true, description: 'security group for alb' }) albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'allow http traffic from anyone') const ec2Sg = new ec2.SecurityGroup(this, 'WebEc2Sg', { vpc, allowAllOutbound: true, description: 'security group for a web server' }) ec2Sg.connections.allowFrom(albSg, ec2.Port.tcp(80), 'allow http traffic from alb') // // web servers // const userData = ec2.UserData.forLinux({ shebang: '#!/bin/bash', }) userData.addCommands( // setup httpd 'yum update -y', 'yum install -y httpd', 'systemctl start httpd', 'systemctl enable httpd', 'echo "This is a sample website." > /var/www/html/index.html', ) // launch one instance per az const targets: elbv2_tg.InstanceTarget[] = new Array(); for (const [idx, az] of vpc.availabilityZones.entries()) { targets.push( new elbv2_tg.InstanceTarget( new ec2.Instance(this, `WebEc2${idx + 1}`, { instanceName: `web-ec2-${idx + 1}`, // web-ec2-1, web-ec2-2, ... instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO), machineImage: ec2.MachineImage.latestAmazonLinux2023(), vpc, vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE_ISOLATED, }), availabilityZone: az, securityGroup: ec2Sg, blockDevices: [ { deviceName: '/dev/xvda', volume: ec2.BlockDeviceVolume.ebs(8, { encrypted: true }), }, ], userData, ssmSessionPermissions: true, propagateTagsToVolumeOnCreation: true, }) ) ); } // // alb // const alb = new elbv2.ApplicationLoadBalancer(this, 'Alb', { internetFacing: true, vpc, vpcSubnets: { subnets: vpc.publicSubnets }, securityGroup: albSg }) const listener = alb.addListener('HttpListener', { port: 80, protocol: elbv2.ApplicationProtocol.HTTP }) listener.addTargets('WebEc2Target', { targets, port: 80 }) new CfnOutput(this, 'TestCommand', { value: `curl http://${alb.loadBalancerDnsName}` }) } }
To deploy, execute:
cdk deploy
As a side note, the combination of CDK and GitHub Copilot offers an excellent development experience, and I highly recommend it.
Sample code is placed here:
JHashimoto0518 / cdk-private-yum-sample
This is a sample of using CDK to build a private endpoint for yum on EC2 instances.
cdk private yum sample
This is a sample of using CDK to build a private endpoint for yum on EC2 instances.
diagrams
yum execution
http(s) request
Articles
Japanese
Tested on the following version:
$ cdk --version 2.81.0 (build bd920f2)
Testing
First, I verify the web server's operation.
I connect via the session manager and confirm that it returns responses to HTTP requests. (Sorry, the screenshot is in Japanese)
sh-4.2$ curl http://localhost/ This is a sample website.
Next, I carry out an end-to-end test.
After CDK deployment, a test command appears in the terminal. I copy and run it.
$ curl http://CdkPr-alb8A-1UPL742M6H83S-1170769314.ap-northeast-1.elb.amazonaws.com This is a sample website.
I verify that the ALB returns the response as expected.
Conclusion
By utilizing VPC endpoints, I can run yum
in an environment without internet access. Additionally, using CDK improves the development experience and speeds up the building process.
Top comments (0)