파트 3 - 간단한 EC2 인스턴스 - Awesome AWS CDK

45576 단어 awscdkdevopsec2aws
  • Introduction
  • Bootstrapping a new CDK Application

  • Structure and Setup of a CDK application
  • Structure
  • Setup

  • Writing the infrastructure code
  • Creating a new Key Pair in the AWS Console
  • Deploy
  • Accessing the instance
  • Updating the deployed stack

  • Testing the stack
  • Test requirements

  • Destroying the stack
  • Notes
  • Conclusion
  • Up next

  • 소개

    Most tutorials on AWS try to get you to deploy the world famous t2.micro instance (a small virtual server under the free usage tier on AWS) using the AWS console. You then go on to install something like Wordpress through SSH or through a user script (a script that runs when an instance is created) that you define in the AWS console.

    So that's exactly what we're going to deploy. But we're going to use the AWS CDK instead.

    This is what you'll learn in this tutorial:

    • Bootstrapping a new CDK application
    • The structure and setup of a CDK application
    • How to provision and setup an ec2 instance
    • How to setup terminal access to the instance via SSH key.
    • How to update the ec2 instance with a user script and update the deployed cdk stack
    • How to write tests for the cdk application
    • How to destroy the deployed stack

    Let's go!

    새 CDK 애플리케이션 부트스트랩

    Using your terminal, create a new directory simple-ec2 and cd into it:

    mkdir simple-ec2 && cd simple-ec2
    

    We previously setup the AWS CDK cli globally. If you haven't done that see in this series.

    Bootstrap a new cdk project template that uses Typescript:

    cdk init --language=typescript
    

    This will initialize a new cdk project for you in Typescript.

    • Run npm update to ensure you're using the latest version of the CDK.
    • If you have version conflicts between @aws-cdk/core and other @aws-cdk sub-packages, then you'll come across some weird errors. @aws-cdk/core and every other imported @aws-cdk/PACKAGE should have the same version.

    CDK 애플리케이션의 구조 및 설정

    구조

    # tree -I 'node_modules'
    .
    ├── bin
    │   └── simple-ec2.ts       # entry point
    ├── cdk.json
    ├── jest.config.js          # for tests
    ├── lib                     # where the infrastructure code you write will go
    │   └── simple-ec2-stack.ts 
    ├── package.json
    ├── package-lock.json
    ├── README.md
    ├── test                    # test folder
    │   └── simple-ec2.test.ts
    └── tsconfig.json
    
    • ./bin/simple-ec2.ts is the entry point file used by the cdk. This is where you define your stack(s).
    • The IaC that provisions the resources will be inside the lib folder and is required by ./bin/simple-ec2.ts during synth and deploy actions. I'll explain both commands later.
    • ./test/simple-ec2.test.ts contains the template code to test your CDK application

    설정

    In ./bin/simple-ec2.ts a new App() is defined and this represents a single stack.

    • We can add a description for our stack in this file.
    • This description will be visible in the Cloudformation console:
    // ./bin/simple-ec2.ts
    
    import 'source-map-support/register';
    import * as cdk from '@aws-cdk/core';
    import { SimpleEc2Stack } from '../lib/simple-ec2-stack';
    
    const app = new cdk.App();
    new SimpleEc2Stack(app, 'SimpleEc2Stack', {
       description: 'This is a simple EC2 stack'
    });
    

    Let's head over to ./lib/simple-ec2-stack.ts and see what the CDK boostrapped for us:

    // ./lib/simple-ec2-stack.ts
    
    import * as cdk from '@aws-cdk/core';
    
    export class SimpleEc2Stack extends cdk.Stack {
      constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
    
        // The code that defines your stack goes here
      }
    }
    

    As you can see, the cdk init command bootstrapped a nice template for us to get to writing our IaC fast. The base class cdk.Stack gives us the ability to create a new Cloudformation stack.

    We also need to create a .env file to keep our AWS Account number and the region we will use. In the root of the project create a file called .env and add the following. Replace the xxXxXxxXXxXxx with your AWS account number and use the region you want.

    AWS_ACCOUNT_NUMBER=xxXxXxxXXxXxx
    AWS_ACCOUNT_REGION=us-west-2
    

    인프라 코드 작성

    We want to create an ec2 instance so we will need the @aws-cdk/ec2 library. (This is the same process for any other AWS service you need to provision resources). We will also need @aws-cdk/aws-iam library to give permissions to our instance to do stuff. We also want to be able to read our .env file so lets also install dotenv package

    npm install @aws-cdk/aws-ec2 @aws-cdk/aws-iam dotenv
    

    Remember, since the CDK is written in Typescript and is typed excellently, while typing you can access intellisense and see the various properties of CDK resources e.g. in the image below I can see what properties an instance of ec2.Instance() class has.



    첫 번째 반복은 다음과 같습니다.

    import * as cdk from '@aws-cdk/core'
    import * as ec2 from '@aws-cdk/aws-ec2' // import ec2 library 
    import * as iam from '@aws-cdk/aws-iam' // import iam library for permissions
    
    require('dotenv').config()
    
    const config = {
      env: {
        account: process.env.AWS_ACCOUNT_NUMBER,
        region: process.env.AWS_REGION
      }
    }
    
    export class SimpleEc2Stack extends cdk.Stack {
      constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
        // its important to add our env config here otherwise CDK won't know our AWS account number
        super(scope, id, { ...props, env: config.env })
    
        // Get the default VPC. This is the network where your instance will be provisioned
        // All activated regions in AWS have a default vpc. 
        // You can create your own of course as well. https://aws.amazon.com/vpc/
        const defaultVpc = ec2.Vpc.fromLookup(this, 'VPC', { isDefault: true })
    
        // Lets create a role for the instance
        // You can attach permissions to a role and determine what your
        // instance can or can not do
          const role = new iam.Role(
            this,
            'simple-instance-1-role', // this is a unique id that will represent this resource in a Cloudformation template
            { assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com') }
          )
    
        // lets create a security group for our instance
        // A security group acts as a virtual firewall for your instance to control inbound and outbound traffic.
        const securityGroup = new ec2.SecurityGroup(
          this,
          'simple-instance-1-sg',
          {
            vpc: defaultVpc,
            allowAllOutbound: true, // will let your instance send outboud traffic
            securityGroupName: 'simple-instance-1-sg',
          }
        )
    
        // lets use the security group to allow inbound traffic on specific ports
        securityGroup.addIngressRule(
          ec2.Peer.anyIpv4(),
          ec2.Port.tcp(22),
          'Allows SSH access from Internet'
        )
    
        securityGroup.addIngressRule(
          ec2.Peer.anyIpv4(),
          ec2.Port.tcp(80),
          'Allows HTTP access from Internet'
        )
    
        securityGroup.addIngressRule(
          ec2.Peer.anyIpv4(),
          ec2.Port.tcp(443),
          'Allows HTTPS access from Internet'
        )
    
        // Finally lets provision our ec2 instance
        const instance = new ec2.Instance(this, 'simple-instance-1', {
          vpc: defaultVpc,
          role: role,
          securityGroup: securityGroup,
          instanceName: 'simple-instance-1',
          instanceType: ec2.InstanceType.of( // t2.micro has free tier usage in aws
            ec2.InstanceClass.T2,
            ec2.InstanceSize.MICRO
          ),
          machineImage: ec2.MachineImage.latestAmazonLinux({
            generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
          }),
    
          keyName: 'simple-instance-1-key', // we will create this in the console before we deploy
        })
    
        // cdk lets us output prperties of the resources we create after they are created
        // we want the ip address of this new instance so we can ssh into it later
        new cdk.CfnOutput(this, 'simple-instance-1-output', {
          value: instance.instancePublicIp
        })
      }
    }
    


    AWS 콘솔에서 새 키 페어 생성

    Before we try to deploy our newly created instance, we need to go to the AWS console and create a key pair that we will use to access the instance called simple-instance-1-key

    • Log into the AWS console.
    • Go to EC2 dashboard.
    • Go to Key Pairs and click create Key Pair
    • Enter key name as simple-instance-1-key and click create
    • Your new key pair will be created and your browser will automatically download a new .pem file called simple-instance-1-key.pem
    • this is the key file you'll use to gain access to your instance via SSH

  • .aws/라는 pems라는 새 디렉토리를 만듭니다.

  • mkdir ~./aws/pems/
    


  • 새로 다운로드한 파일을 이 디렉토리로 이동하고 필요한 권한을 부여합니다.

  • mv ~/Downloads/simple-instance-1-key.pem ~/.aws/pems
    
    # important step or your key file won't work
    chmod 400 ~/.aws/pems/simple-instance-1-key.pem
    


  • 이제 키 파일을 올바르게 설정했으므로 인스턴스를 배포할 수 있습니다!

  • 배포

    Remember we set up our aws profiles and crednetials in ~/.aws/config and ~/.aws/credentials back in

    I will be deploying to my default profile which is linked to my personal AWS account with the region us-west-2 .

    If you have another profile you want to use then in the commands below use that profile name instead of default .

    In your terminal:

    cdk synth --profile default
    

    This command will synthesize your stack.

    When CDK apps are executed, they produce (or “synthesize”, in CDK parlance) an AWS CloudFormation template for each stack defined in your application.

    • Essentially it will print the cloudformation template for your stack to your console.
    • It's a good way to check that there's nothing wrong with your stack before trying to deploy since cdk synth will verify the resources you are trying to provision can actually be provisioned.
    • You should something like this:
    ❯ cdk synth --profile default
    Resources:
      simpleinstance1role9EEDA67C:
        Type: AWS::IAM::Role
        Properties:
          AssumeRolePolicyDocument:
            Statement:
              - Action: sts:AssumeRole
                Effect: Allow
                Principal:
                  Service: ec2.amazonaws.com
            Version: "2012-10-17"
       ...
       ...
       ...
    

    If the entire stack prints without error then you're okay to go.

    Now we can deploy:

    cdk deploy --profile default
    
    You should get a prompt accessing for Cloudformation to allow the creation of resources that need approval. Type y and press ENTER to continue with the deployment:


    스택이 생성되는 동안 콘솔에서 Cloudformation의 출력이 표시되기 시작합니다.

    스택이 성공적으로 배포되면 다음이 표시되어야 합니다.


    스택 끝에 정의한 출력을 확인하십시오. CDK는 새로 생성된 인스턴스의 퍼블릭 IP 주소를 인쇄했습니다. 스택이 성공적으로 배포되면 이 방법을 사용하여 모든 값을 출력할 수 있습니다.

    또는 인스턴스 대시보드를 확인하여 ec2 콘솔에서 이 정보를 얻을 수 있습니다.

    인스턴스에 액세스

    Let's ssh into our newly created instance with our key file and the public ip address.

    Note: that the default user for AMAZON LINUX images is ec2-user

    ssh -i ~/.aws/pems/simple-instance-1-key.pem [email protected]
    
    You should now be able to log into your instance!


    안타깝게도 아무 것도 실행하지 않는 인스턴스가 있습니다.

    해결하자!

    사용자 스크립트 추가

    • Let's create a new file under ./lib/ directory called user_script.sh . Paste this code into that file.
    • This code will deploy Apache, Wordpress, Mysql server on this intance. In this script, the database password will be pl55w0rd .
    • It's very insecure to add passwords to scripts but in this case I'm doing this just for demonstration purposes.
    • In production you should first of all never use such a weak password and secondly, not inside such a script.
    • Rather, you should deploy the Mysql database on AWS RDS and setup credentials for that database using AWS Secrets Manager.

    Here's the setup file:

    #! /bin/bash
    # become root user
    sudo su
    
    # update dependencies
    yum -y update
    
    # we'll install 'expect' to input keystrokes/y/n/passwords
    yum -y install expect 
    
    # Install Apache
    yum -y install httpd
    
    # Start Apache
    service httpd start
    
    # Install PHP
    yum -y install php php-mysql
    # php 7 needed for latest wordpress
    amazon-linux-extras -y install php7.2 
    
    # Restart Apache
    service httpd restart
    
    # Install MySQL
    wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm
    rpm -ivh mysql-community-release-el7-5.noarch.rpm
    
    yum -y update 
    yum -y install mysql-server
    
    # Start MySQL
    service mysqld start
    
    # Create a database named blog
    mysqladmin -uroot create blog
    
    # Secure database
    # non interactive mysql_secure_installation with a little help from expect.
    
    SECURE_MYSQL=$(expect -c "
    
    set timeout 10
    spawn mysql_secure_installation
    
    expect \"Enter current password for root (enter for none):\"
    send \"\r\"
    
    expect \"Change the root password?\"
    send \"y\r\"
    expect \"New password:\"
    send \"pl55w0rd\r\"
    expect \"Re-enter new password:\"
    send \"pl55w0rd\r\"
    expect \"Remove anonymous users?\"
    send \"y\r\"
    
    expect \"Disallow root login remotely?\"
    send \"y\r\"
    
    expect \"Remove test database and access to it?\"
    send \"y\r\"
    
    expect \"Reload privilege tables now?\"
    send \"y\r\"
    
    expect eof
    ")
    
    echo "$SECURE_MYSQL"
    
    # Change directory to web root
    cd /var/www/html
    
    # Download Wordpress
    wget http://wordpress.org/latest.tar.gz
    
    # Extract Wordpress
    tar -xzvf latest.tar.gz
    
    # Rename wordpress directory to blog
    mv wordpress blog
    
    # Change directory to blog
    cd /var/www/html/blog/
    
    # Create a WordPress config file 
    mv wp-config-sample.php wp-config.php
    
    #set database details with perl find and replace
    sed -i "s/database_name_here/blog/g" /var/www/html/blog/wp-config.php
    sed -i "s/username_here/root/g" /var/www/html/blog/wp-config.php
    sed -i "s/password_here/pl55w0rd/g" /var/www/html/blog/wp-config.php
    
    # create uploads folder and set permissions
    mkdir wp-content/uploads
    chmod 777 wp-content/uploads
    
    #remove wp file
    rm -rf /var/www/html/latest.tar.gz
    

    We will need to add fs module at the top of our ./lib/simple-ec2-stack.ts file since instance.addUserData() needs to access the file system during deployment.

    import * as cdk from '@aws-cdk/core'
    import * as ec2 from '@aws-cdk/aws-ec2' // import ec2 library 
    import * as iam from '@aws-cdk/aws-iam' // import iam library for permissions
    
    // lets include fs module
    import * as fs from 'fs'
    

    Then we can the function instance.addUserData() right before our output function:

    ...
    
        // add user script to instance
        // this script runs when the instance is started 
        instance.addUserData(
          fs.readFileSync('lib/user_script.sh', 'utf8')
        )
    
        // cdk lets us output prperties of the resources we create after they are created
        // we want the ip address of this new instance so we can ssh into it later
        new cdk.CfnOutput(this, 'simple-instance-1-output', {
          value: instance.instancePublicIp
        })
    ...
    

    배포된 스택 업데이트

    Let's re-synthesize to check everything is okay:

    cdk --synth --profile default
    
    • You should now see the user script commands in the synth output
    • Let's deploy our new changes.
    • Cloudformation will only update resources that are being updated.
    • In this case, only ec2 instance is being updated.
    • Other things like roles and security groups will remain as they are since there are no changes to them in the updated stack.
    cdk --deploy --profile default
    


    참고: 탄력적 IP를 사용하지 않기 때문에 인스턴스의 퍼블릭 IP 주소가 변경되었을 가능성이 높습니다.

    출력된 IP 주소를 사용하여 Wordpress, Mysql 및 PHP가 올바르게 설치되었는지 확인합니다.
  • 브라우저에서 http:///blog
  • 로 이동합니다.
  • 그러면 다음이 표시됩니다.


  • 그러면 Wordpress 설치를 완료할 수 있습니다!
  • 스크립트
  • 에 정의된 대로 데이터베이스 자격 증명rootpl55w0rd을 기억하십시오.

    스택 테스트

    Well we know our CDK code works and can provision an ec2 instance to run our Wordpress server and database. Good.

    But how can we make sure that changes to the CDK code do not do what we don't want it to do?

    This is where tests come in.

    테스트 요구 사항

    • I do not want any instance other t2.micro to be used as my server instance type because I always want to remain under AWS free tier usage for EC2. Let's ensure that.
    • I want to ensure that my instance uses the SSH key with the name simple-instance-1-key

    To accomplish this, we change the code inside the file ./test/simple-ec2.test.ts to:

    import { expect as expectCDK, haveResourceLike } from '@aws-cdk/assert';
    import * as cdk from '@aws-cdk/core';
    import * as SimpleEc2 from '../lib/simple-ec2-stack';
    
    test('Check InstanceType and SSH KeyName', () => {
        const app = new cdk.App();
        const stack = new SimpleEc2.SimpleEc2Stack(app, 'MyTestStack');
    
        expectCDK(stack).to(
          haveResourceLike('AWS::EC2::Instance', {
            InstanceType: 't2.micro',
            KeyName: 'simple-instance-1-key'
          })
        )
    });
    

    As you can see from the test code, the test will check the generated Cloudformation template generated by the CDK.

    In our case we want to check that the instance is a t2.micro and that it uses the SSH key simple-instance-1-key . These are two crucial properties to us.

    You can read more about testing infrastructure with the CDK here here

    테스트 실행:

    npm test
    




    문제 없다!

    이제 인프라를 배포하기 전에 코드에서 테스트를 실행할 수 있어야 합니다! 환상적입니다!

    npm test && cdk deploy --profile default
    


    스택 파괴

    If you would like to destroy the infrastructure you just provisioned, it's as simple as:

    cdk destroy --profile default
    

    And Cloudformation will remove your entire stack!

    메모:

  • It's not advisable to run mysql on the same instance as your Wordpress server. You can instead use AWS managed Database service RDS Wordpress에서 사용할 데이터베이스를 배포합니다.
  • 대부분의 경우 소스 제어에 커밋되거나 해당 출력이 CI/CD 콘솔 또는 인스턴스 터미널 기록에 표시되므로 사용자 스크립트에 암호와 같은 민감한 정보를 넣지 마십시오
  • .

    결론

    The AWS CDK makes writing IaC, provisioning, deploying, updating and destroying infrastructure very painless. You can write tests to make sure you do not deploy the wrong things.

    This was a simple example and may seem quite a lot just to deploy an ec2 instance. However, as we progress through the series, you will realize how its very beneficial to complex infrastructure.

    다음

    In part 4, using the CDK, we will make our Wordpress server more more production ready. We will:

    • create AWS RDS Mysql database instead of running the database on the ec2 instance
    • provision this database in an isolated subnet to keep it secure from the public Internet
    • use AWS SSM to access our instance instead of an SSH key and gain all the benefits of IAM permissions/roles
    • deploy an Application Load Balancer
    • Create our EC2 instance with better/more advanced script
    • Place the Wordpress instance in an AutoScaling Group

    Hi I'm Emmanuel ! 저는 소프트웨어 및 DevOps에 대해 글을 씁니다.

    이 기사가 마음에 들었고 더 보고 싶다면 저를 추가하거나 저를 팔로우하세요.

    좋은 웹페이지 즐겨찾기