Что это за вызов?

Cloud Resume Challenge — это многоэтапный проект резюме, который помогает развить и продемонстрировать навыки, необходимые для продолжения карьеры инженера облачных вычислений AWS. Проект был опубликован Форрест Бразил.



Диаграмма топологии


Как устроены спринты?

Я присоединился к августовскому спринту. Спринт длится 4 недели.
Каждую неделю мы создаем мини-проект, который сам по себе ценен.



Задачи и прогресс



1. Сертификация

Для выполнения этой задачи требуется сертификат AWS Cloud Practitioner.
Для получения сертификата я использовал только бесплатные материалы и даже получил 50% скидку на плату за экзамен (обычно 100 долларов США).


2. HTML

Резюме должно быть документом HTML, стилизованным с использованием CSS. Это не обязательно должен быть оригинальный шедевр, подойдет любой HTML-шаблон резюме. Вы можете найти мой здесь для вдохновения.


3. CSS

То же самое относится и к CSS. Вы можете написать свой собственный или использовать попутный ветер или что-то подобное.


4. Статический веб-сайт

Веб-сайт HTML/CSS должен быть развернут в корзине AWS S3. Я сделал это через шаблон CloudFormation. Код ниже:

 MyWebsite:
    Type: AWS::S3::Bucket
    Properties:
      AccessControl: PublicRead
      WebsiteConfiguration:
        IndexDocument: index.html
      BucketName: cloud-cv-website
Войти в полноэкранный режим

Выйти из полноэкранного режима


5. HTTPS

Чтобы иметь возможность использовать HTTPS вместо HTTP, был необходим дистрибутив CloudFront. Я сделал это через CloudFormation следующим образом:

 MyDistribution:
    Type: "AWS::CloudFront::Distribution"
    Properties:
      DistributionConfig:
        # ViewerCertificate:
        #     AcmCertificateArn: !Ref MyCertificate
        #     SslSupportMethod: sni-only
        # Aliases:
        #   www.gabor-havasi.me
        #   gabor-havasi.me
        DefaultCacheBehavior:
          ViewerProtocolPolicy: allow-all
          TargetOriginId: cloud-cv-website.s3-website-eu-west-1.amazonaws.com
          DefaultTTL: 0
          MinTTL: 0
          MaxTTL: 0
          ForwardedValues:
            QueryString: false
        Origins:
          - DomainName: cloud-cv-website.s3-website-eu-west-1.amazonaws.com
            Id: cloud-cv-website.s3-website-eu-west-1.amazonaws.com
            CustomOriginConfig:
              OriginProtocolPolicy: match-viewer
        Enabled: "true"
        DefaultRootObject: index.html
Войти в полноэкранный режим

Выйти из полноэкранного режима


6. DNS

Я использовал AWS Route 53, чтобы указать собственное доменное имя (купленное на namecheap.com) на дистрибутив CloudFront, который был настроен на предыдущем шаге. Я использовал следующий код для добавления записей A:

 myRoute53:
    Type: "AWS::Route53::RecordSetGroup"
    Properties:
      HostedZoneId: Z0787651UFL9JUUWJ3WQ
      RecordSets:
        - Name: gabor-havasi.me
          Type: A
          AliasTarget:
            HostedZoneId: Z2FDTNDATAQYW2
            DNSName: !GetAtt MyDistribution.DomainName

  MyRoute53www:
    Type: "AWS::Route53::RecordSetGroup"
    Properties:
      HostedZoneId: Z0787651UFL9JUUWJ3WQ
      RecordSets:
        - Name: www.gabor-havasi.me
          Type: A
          AliasTarget:
            HostedZoneId: Z2FDTNDATAQYW2
            DNSName: !GetAtt MyDistribution.DomainName
Войти в полноэкранный режим

Выйти из полноэкранного режима


7. Джаваскрипт

Веб-страница с резюме должна включать счетчик посетителей, показывающий, сколько людей заходили на сайт. Этот скрипт должен быть написан на JavaScript.
Чтобы счетчик заработал, нам нужно добавить несколько дополнительных строк на наш HTML-сайт и написать код JavaScript:

Сам скрипт:

const counter = document.querySelector(".counter-number");
async function updateCounter() {
  let response = await fetch(
    "
  );
  let data = await response.json();
  counter.innerHTML = `You are the ${data}. visitor to my Cloud Resume Challenge site`;
}

updateCounter();
Войти в полноэкранный режим

Выйти из полноэкранного режима

Добавление HTML:

<div class="counter-number">Couldn't read the counter</div>
Войти в полноэкранный режим

Выйти из полноэкранного режима


8. База данных

Счетчик посетителей должен будет получить и обновить свой счет в базе данных где-то. Я использовал DynamoDB для этой задачи, так как это было самое удобное решение. Код из шаблона CloudFormation для создания таблицы DynamoDB:

DynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: gabor-havasi-cv
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: "ID"
          AttributeType: "S"
      KeySchema:
        - AttributeName: "ID"
          KeyType: "HASH"
Войти в полноэкранный режим

Выйти из полноэкранного режима


9. API

Мы не должны напрямую взаимодействовать с DynamoDB из нашего кода Javascript. Вместо этого следует создать REAT API, который принимает запросы от вашего веб-приложения и обменивается данными с базой данных.
Я создал единственный метод GET и присвоил ему Lambda. Я мог бы иметь отдельные методы GET и POST для чтения счетчика и обновления его с помощью двух отдельных функций Lambda, но я выбираю монолитный подход, так как в нашем случае он был проще.


10. Питон

Сценарий Lambda для извлечения счетчика из базы данных и обновления числа должен быть написан на Python. Сначала я сделал это на Node.js, но позже переписал на Python. Финальный скрипт ниже:

import json
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('gabor-havasi-cv')
def lambda_handler(event, context):
    response = table.get_item(Key={
            'id':'1'
    })
    record_count = response['Item']['counter']
    record_count = str(int(record_count) + 1)
    print(record_count)
    response = table.put_item(Item={
            'id':'1',
            'counter': record_count
    })

    return {

    'statusCode': 200,
        'headers': {
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
        },
        'body': record_count}
Войти в полноэкранный режим

Выйти из полноэкранного режима

И развернуть его через CloudFormation:

Lambdafunction:
    Type: AWS::Serverless::Function
    Properties:
      Policies:
        - DynamoDBCrudPolicy:
            TableName: gabor-havasi-cv
      CodeUri: get-count/
      Handler: app.lambda_handler
      Runtime: python3.9
      Architectures:
        - x86_64
      Events:
        getCount:
          Type: Api
          Properties:
            Path: /
            Method: get
Войти в полноэкранный режим

Выйти из полноэкранного режима


11. Тесты

Я добавил тест, написанный на Node.js, для проверки API. Этот тест будет добавлен в действие GitHub на шаге 14. Во время теста он дважды вызывает Lambda, а затем проверяет, больше ли возвращаемое значение из второго вызова на 1, чем возвращаемое значение из первого вызова.

import { expect } from "@jest/globals";
import fetch from "node-fetch";

async function testCounter() {
  let response = await fetch(
    "
  );
  let data = await response.json();
  return data;
}

describe("Testing the API", () => {
  it("API test", async () => {
    const firstResponse = await testCounter();
    console.log(firstResponse);
    const secondResponse = await testCounter();
    console.log(secondResponse);

    const expected = 1;
    expect(secondResponse - firstResponse).toEqual(expected);
  });
});
Войти в полноэкранный режим

Выйти из полноэкранного режима


12. Инфраструктура как код

Я использовал шаблон AWS Serverless Application Model (SAM) и развернул различные сервисы с помощью интерфейса командной строки AWS SAM. Фрагменты кода можно найти под каждым шагом, где это применимо.


13. Контроль версий

Я использовал Git в качестве системы управления версиями и размещал код на GitHub с самого начала этого проекта.


14. CI/CD (внутренняя часть)

На этом шаге нам нужно настроить GitHub Actions таким образом, чтобы, когда мы отправляем обновление в наш шаблон модели бессерверного приложения или код Lambda, тест запускался. Если тест пройден успешно, приложение SAM должно быть упаковано и развернуто на AWS.
ВАЖНО! Не передавайте учетные данные AWS в систему управления версиями! Ключ доступа и секретный ключ необходимо сохранить как «СЕКРЕТЫ».
Для этого я использовал следующие фрагменты шаблона рабочего процесса:

Чтобы запустить тест:

  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [16.x]
        # See supported Node.js release schedule at 

    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"
      - run: npm install node-fetch
      - run: npm run build --if-present
      - run: npm run test
Войти в полноэкранный режим

Выйти из полноэкранного режима

Чтобы развернуть на AWS, если тест пройден:

  deploy-to-AWS:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v3
      - uses: aws-actions/setup-sam@v2
      - uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: eu-west-1
      - run: sam build
      - run: sam deploy --no-confirm-changeset --no-fail-on-empty-changeset
Войти в полноэкранный режим

Выйти из полноэкранного режима


15. CI/CD (внешний интерфейс)

Создайте действия GitHub, чтобы при отправке нового кода веб-сайта корзина S3 автоматически обновлялась. Кэш CloudFront не вызвал здесь никаких проблем, так как я установил TTL равным 0 на шаге 5.
я использовал jakejarvis, s3-синхронизация-действие код для синхронизации с корзиной S3.
Фрагмент шаблона GitHub Action для достижения этой цели:

deploy-site:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - uses: jakejarvis/s3-sync-action@master
      with:
        args: --delete
      env:
        AWS_S3_BUCKET: cloud-cv-website
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        SOURCE_DIR: CV
Войти в полноэкранный режим

Выйти из полноэкранного режима


16. Сообщение в блоге

Последний шаг — написать блог об опыте и о том, что я узнал во время работы над проектом. Вы читаете этот блог прямо сейчас.


16+1. Что дальше?

Я планирую повторить эту задачу, используя Terraform.