DEV Community

jhashimoto
jhashimoto

Posted on

1

Setting up a VPC Endpoint for yum with AWS CDK

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


Enter fullscreen mode Exit fullscreen mode

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.

image.png

I use an ALB as the public endpoint for the web server. I also set up VPC endpoints for SSM for remote connections.

image.png

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


Enter fullscreen mode Exit fullscreen mode


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}`
    })
  }
}


Enter fullscreen mode Exit fullscreen mode

To deploy, execute:



cdk deploy


Enter fullscreen mode Exit fullscreen mode

As a side note, the combination of CDK and GitHub Copilot offers an excellent development experience, and I highly recommend it.

dev-ssm-endpoints-with-copilot.gif

Sample code is placed here:

GitHub logo JHashimoto0518 / cdk-private-yum-sample

This is a sample of using CDK to build a private endpoint for yum on EC2 instances.






Tested on the following version:



$ cdk --version
2.81.0 (build bd920f2)


Enter fullscreen mode Exit fullscreen mode

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)

image.png



sh-4.2$ curl http://localhost/
This is a sample website.


Enter fullscreen mode Exit fullscreen mode

Next, I carry out an end-to-end test.

After CDK deployment, a test command appears in the terminal. I copy and run it.

image.png



$ curl http://CdkPr-alb8A-1UPL742M6H83S-1170769314.ap-northeast-1.elb.amazonaws.com
This is a sample website.


Enter fullscreen mode Exit fullscreen mode

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)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up