Индустрия разработки программного обеспечения меняется очень быстро. После того, как термин DevOps стал очень популярным, одного написания кода уже недостаточно. Вы должны максимально автоматизировать процесс разработки программного обеспечения. В этом посте я опишу, как определить нашу инфраструктуру, просто написав код.
«Инфраструктура как код — это процесс управления компьютерными центрами обработки данных и их предоставления с помощью машиночитаемых файлов определений, а не физической конфигурации оборудования или интерактивных инструментов настройки».[1]
Зачем нам нужна инфраструктура как код?
- После переноса контейнерного мира инфраструктуру можно чаще менять или удалять/создавать.
- Кроме того, отслеживать настройки инфраструктуры становится очень сложно из-за множества различных настроек. Таким образом, сохранение этих настроек внутри кода помогает понять огромную сложность.
В этом посте я буду использовать CloudFormation для определения инфраструктуры.
«AWS CloudFormation предоставляет общий язык для моделирования и предоставления ресурсов AWS и сторонних приложений в вашей облачной среде. AWS CloudFormation позволяет использовать языки программирования или простой текстовый файл для автоматического и безопасного моделирования и предоставления всех ресурсов, необходимых для ваших приложений во всех регионах и учетных записях. Это дает вам единый источник достоверной информации для ваших ресурсов AWS и сторонних ресурсов».[2]
Я нарисовал архитектурную схему ниже, чтобы продемонстрировать финал.
Давайте определим компоненты, которые существуют внутри диаграммы.
Регион: это отдельные географические области, которые AWS использует для размещения своей инфраструктуры. Они распределены по всему миру, чтобы клиенты могли выбрать ближайший к ним регион, чтобы разместить там свою облачную инфраструктуру.[3]
Зона доступности: логический строительный блок, из которого состоит регион AWS. В настоящее время существует 69 AZ, которые являются изолированными местами — центрами обработки данных — в пределах региона.[3]
VPC: позволяет запускать ресурсы AWS в определенную вами виртуальную сеть. Эта виртуальная сеть очень похожа на традиционную сеть, которую вы бы использовали в собственном центре обработки данных, с преимуществами использования масштабируемой инфраструктуры AWS[4].
Подсеть: это «часть сети», другими словами, часть всей зоны доступности. Каждая подсеть должна полностью находиться в пределах одной зоны доступности и не может охватывать зоны.[5]
Для простоты я создам 1 VPC, 2 публичные подсети, 2 частные подсети. Подсети стоят разных зон доступности.
PS: развертывание команд кода
$sam package --template-file template.yaml --s3-bucket BUCKETNAME --output-template-file packaged.yaml $sam deploy --template-file FILEPATH/packaged.yaml --stack-name test --capabilities CAPABILITY_IAM
Прежде всего, давайте определим параметры, как показано ниже, чтобы использовать их позже.
Parameters: EnvironmentName: Description: An environment name that is prefixed to resource names Type: String Default: Test VpcCIDR: Description: Please enter the IP range (CIDR notation) for this VPC Type: String Default: 10.0.0.0/16 PublicSubnet1CIDR: Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone Type: String Default: 10.0.1.0/24 PublicSubnet2CIDR: Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone Type: String Default: 10.0.2.0/24 PrivateSubnet1CIDR: Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone Type: String Default: 10.0.3.0/24 PrivateSubnet2CIDR: Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone Type: String Default: 10.0.4.0/24
PS: 10.0.0.0/16: каждое число представляет собой 8 цифр, а первые 16 цифр (первые две цифры) остаются прежними. Остальные цифры можно изменить. Таким образом, можно назначить 256 * 256 IP.
СОЗДАТЬ VPC
VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCIDR EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Ref EnvironmentName
Давайте подключим этот VPC к интернету. Интернет-шлюз используется для доступа в Интернет из VPC.
InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Ref EnvironmentName InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC
СОЗДАНИЕ ПОДСЕТИ
PublicSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 0, !GetAZs '' ] CidrBlock: !Ref PublicSubnet1CIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${EnvironmentName} Public Subnet (AZ1) PublicSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 1, !GetAZs '' ] CidrBlock: !Ref PublicSubnet2CIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${EnvironmentName} Public Subnet (AZ2) PrivateSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 0, !GetAZs '' ] CidrBlock: !Ref PrivateSubnet1CIDR MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub ${EnvironmentName} Private Subnet (AZ1) PrivateSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 1, !GetAZs '' ] CidrBlock: !Ref PrivateSubnet2CIDR MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub ${EnvironmentName} Private Subnet (AZ2)
PublicSubnet1 стоит в первой зоне доступности.
PublicSubnet2 стоит на втором месте.
PrivateSubnet1 стоит в первой зоне доступности.
PrivateSubnet2 стоит на втором месте.
Публичные подсети должны быть подключены к Интернету. Чтобы справиться с этим, нам нужна таблица маршрутизации.
PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${EnvironmentName} Public Routes DefaultPublicRoute: Type: AWS::EC2::Route DependsOn: InternetGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet1 PublicSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet2
Любой запрос со связанной общедоступной подсетью направляется на интернет-шлюз.
PS: Если вы хотите сделать то же самое для частных подсетей, вам следует использовать NAT Gateway.
СОЗДАНИЕ ЭКЗЕМПЛЯРА ELASTICACHE С КОДОМ
ServerlessSecurityGroup: DependsOn: VPC Type: AWS::EC2::SecurityGroup Properties: GroupDescription: SecurityGroup for Serverless Functions VpcId: Ref: VPC ServerlessStorageSecurityGroup: DependsOn: VPC Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Ingress for Redis Cluster VpcId: Ref: VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: '6379' ToPort: '6379' SourceSecurityGroupId: Ref: ServerlessSecurityGroup ServerlessCacheSubnetGroup: Type: AWS::ElastiCache::SubnetGroup Properties: Description: "Cache Subnet Group" SubnetIds: - Ref: PrivateSubnet1 ElasticCacheCluster: DependsOn: ServerlessStorageSecurityGroup Type: AWS::ElastiCache::CacheCluster Properties: AutoMinorVersionUpgrade: true Engine: redis CacheNodeType: cache.t2.micro NumCacheNodes: 1 VpcSecurityGroupIds: - "Fn::GetAtt": ServerlessStorageSecurityGroup.GroupId CacheSubnetGroupName: Ref: ServerlessCacheSubnetGroup
Из соображений безопасности я поместил экземпляр Elasticache в частную подсеть. Поэтому нам нужно создать группу безопасности, чтобы порт 6379 мог подключаться к Redis.
ПОДКЛЮЧИТЬ REDIS ИЗ ФУНКЦИИ LAMBDA
CacheClientFunction: Type: AWS::Serverless::Function Properties: Tracing: Active CodeUri: bin/cacheClient Handler: cacheClient Runtime: go1.x Role: !GetAtt RootRole.Arn VpcConfig: SecurityGroupIds: - Ref: ServerlessSecurityGroup SubnetIds: - Ref: PublicSubnet1 Environment: Variables: redis_url: !GetAtt ElasticCacheCluster.RedisEndpoint.Address redis_port: !GetAtt ElasticCacheCluster.RedisEndpoint.Port
Лямбда-функция стоит в общедоступной подсети. Мы можем определить redis_url и redis_port в соответствии с созданным ElasticCache по нашему определению. При написании кода мы можем использовать эти переменные среды для подключения Redis.
!!!Важно!!!
Лямбда-функция требует определенной роли и политики. В противном случае при создании стека будет получена ошибка. Давайте создадим роль и политику с помощью кода.
SampleManagedPolicy: Type: AWS::IAM::ManagedPolicy Properties: PolicyDocument: Version: '2012-10-17' Statement: - Sid: AllowAllUsersToListAccounts Effect: Allow Action: - ec2:CreateNetworkInterface - ec2:DescribeNetworkInterfaces - ec2:DeleteNetworkInterface - xray:PutTraceSegments Resource: "*" RootRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / ManagedPolicyArns: - !Ref SampleManagedPolicy
Указанная выше политика будет создана с минимальным доступом.
Код клиента кеша:
package main import ( "context" "fmt" "github.com/aws/aws-lambda-go/lambda" "github.com/go-redis/redis" "os" ) func HandleRequest(ctx context.Context) (string, error) { redisUrl := os.Getenv("redis_url") redisPort := os.Getenv("redis_port") client := redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%s", redisUrl, redisPort), Password: "", // no password set DB: 0, // use default DB }) client.Set("1", "1", 0) return client.Get("1").String(), nil } func main() { lambda.Start(HandleRequest) }
СОЗДАНИЕ ЭКЗЕМПЛЯРА БАЗЫ ДАННЫХ С КОДОМ
ServerlessDBSecurityGroup: DependsOn: VPC Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Ingress for Redis Cluster VpcId: Ref: VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: '5432' ToPort: '5432' SourceSecurityGroupId: Ref: ServerlessSecurityGroup ServerlessDBSubnetGroup: DependsOn: ServerlessDBSecurityGroup Type: AWS::RDS::DBSubnetGroup Properties: DBSubnetGroupDescription: "DB Subnet Group" SubnetIds: - Ref: PrivateSubnet1 - Ref: PrivateSubnet2 PostgresqlInstance: DependsOn: VPC Type: AWS::RDS::DBInstance Properties: AllocatedStorage: 30 DBInstanceClass: db.t2.micro DBName: postgres Engine: postgres MasterUsername: CacheClient MasterUserPassword: ChangeIt2 DBSubnetGroupName: !Ref ServerlessDBSubnetGroup VPCSecurityGroups: - "Fn::GetAtt": ServerlessDBSecurityGroup.GroupId DbClientFunction: Type: AWS::Serverless::Function Properties: Tracing: Active CodeUri: bin/dbClient Handler: dbClient Runtime: go1.x Role: !GetAtt RootRole.Arn VpcConfig: SecurityGroupIds: - Ref: ServerlessSecurityGroup SubnetIds: - Ref: PublicSubnet1 Environment: Variables: db_url: !GetAtt PostgresqlInstance.Endpoint.Address db_port: !GetAtt PostgresqlInstance.Endpoint.Port
Создание БД очень похоже на кеш. Важным моментом является то, что ваш инстанс БД должен находиться как минимум в двух зонах доступности.
Код клиента БД:
package main import ( "context" "fmt" "github.com/aws/aws-lambda-go/lambda" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/postgres" "os" ) type MyEvent struct { Name string `json:"name"` } func HandleRequest(ctx context.Context, name MyEvent) (string, error) { dbUrl := os.Getenv("db_url") dbURI := fmt.Sprintf("host=%s user=CacheClient dbname=postgres sslmode=disable password=ChangeIt2", dbUrl) fmt.Println(dbURI) db, err := gorm.Open("postgres", dbURI) if err != nil { return "err", err } db.AutoMigrate(&Entity{}) var ent = &Entity{} ent.Text = name.Name db.Save(&ent) return fmt.Sprint(&ent.ID), nil } func main() { lambda.Start(HandleRequest) } package main import "github.com/jinzhu/gorm" type Entity struct { gorm.Model Text string }
Все сделано. Теперь мы создали сеть, экземпляр кэша, экземпляр БД с кодом. Мы связываем их вывод с нашими бессерверными функциями. Клиенты кеша и БД использовали созданную архитектуру по коду.
Полная версия template.yaml:
https://github.com/yunuskilicdev/infrastructureascode
Цитаты:
1-) https://stackify.com/what-is-infrastructure-as-code-how-it-works-best-practices-tutorials/
2-) https://aws.amazon.com/cloudformation/
4-) https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html