Dieses Mal werden wir die AWS Batch-Umgebung mit CDK implementieren. Es gibt viele Beispiele für die Implementierung in TypeScript, aber es gab nicht viele in Python, deshalb habe ich einen Artikel geschrieben.
Die Ausführungsumgebung ist wie folgt. Insbesondere werde ich nicht auf die Installation und die Grundeinstellungen von aws-cli und aws-cdk eingehen. Als Einschränkung hat aws-cdk jedoch eine sehr hohe Aktualisierungshäufigkeit für Versionen, und selbst die aktuell geschriebenen Inhalte funktionieren möglicherweise nicht.
Was besorgniserregend ist, ist der Preis. Als ich es unter den folgenden Bedingungen bewegte, wurde mir nur die EC2-Gebühr berechnet, die ungefähr 0,01 [$ / Tag] betrug. (In Batch wird die Instanz jedes Mal erstellt, nachdem die Warteschlange zum Job hinzugefügt wurde, und sie wird gelöscht, wenn der Job abgeschlossen ist.)
Führen Sie die folgenden Schritte aus, um die Stapelausführungsumgebung vorzubereiten.
Die Ordnerstruktur ist wie folgt. Die Nummer auf der rechten Seite des Dateinamens entspricht der Nummer im obigen Verfahren.
batch_example
└── src
├── docker
│ ├── __init__.py (1)
│ ├── Dockerfile (2)
│ ├── requirements.txt (2)
│ └── Makefile (3)
└── batch_environment
├── app.py (4)
├── cdk.json
└── README.md
Fahren wir nun mit der Implementierung gemäß dem obigen Verfahren fort.
Ein Beispiel für ein Skript, das in Docker ausgeführt werden soll, ist unten dargestellt.
click
dient zum Übergeben von Befehlszeilenargumenten von CMD
Wachturm
wird verwendet, um Protokolle in CloudWatch-Protokolle zu schreiben.
__init__.py
#Zur Zeitanalyse
from datetime import datetime
from logging import getLogger, INFO
#Installationsbibliothek
from boto3.session import Session
import click
import watchtower
#Ruft den Wert aus der Umgebungsvariablen ab, wenn er mit envvar angegeben wird
@click.command()
@click.option("--time")
@click.option("--s3_bucket", envvar='S3_BUCKET')
def main(time: str, s3_bucket: str):
if time:
#Analysezeit unter der Annahme der Ausführung vom CloudWatch-Ereignis
d = datetime.strptime(time, "%Y-%m-%dT%H:%M:%SZ")
#Ausführungsdatum abrufen
execute_date = d.strftime("%Y-%m-%d")
#Logger-Einstellungen
#Der Name des Loggers wird zum Namen des Log-Streams
logger_name = f"{datetime.now().strftime('%Y/%m/%d')}"
logger = getLogger(logger_name)
logger.setLevel(INFO)
#Geben Sie hier den Namen der CloudWatch-Protokollprotokollgruppe an
#Übergeben Sie die Sitzung und senden Sie das Protokoll über die IAM-Rolle
handler = watchtower.CloudWatchLogHandler(log_group="/aws/some_project", boto3_session=Session())
logger.addHandler(handler)
#Geplante Verarbeitung
#Schreiben Sie hier nur das Ausführungsdatum und die Ausführungszeit in CloudWatch-Protokolle
logger.info(f"{execute_date=}")
if __name__ == "__main__":
"""
python __init__.py
--time 2020-09-11T12:30:00Z
--s3_bucket your-bucket-here
"""
main()
Erstellen Sie als Nächstes eine Docker-Datei, die das obige Python-Skript ausführt. Ich habe es in mehreren Schritten unter Bezugnahme auf [hier] erstellt (https://future-architect.github.io/articles/20200513/).
Dockerfile
#Dies ist ein Container zum Bauen
FROM python:3.8-buster as builder
WORKDIR /opt/app
COPY requirements.txt /opt/app
RUN pip3 install -r requirements.txt
#Bereiten Sie von hier aus den Ausführungscontainer vor
FROM python:3.8-slim-buster as runner
COPY --from=builder /usr/local/lib/python3.8/site-packages /usr/local/lib/python3.8/site-packages
COPY src /opt/app/src
WORKDIR /opt/app/src
CMD ["python3", "__init__.py"]
Fügen Sie gleichzeitig die Bibliothek ein, die in der Datei require.txt verwendet werden soll.
requirements.txt
click
watchtower
Registrieren Sie die Docker-Datei nach dem Erstellen in ECR. Erstellen Sie zunächst ein Repository, indem Sie auf der ECR in der Konsole auf die Schaltfläche "Repository erstellen" klicken.
Stellen Sie den Namen des Repositorys entsprechend ein.
Wählen Sie das erstellte Repository aus und klicken Sie auf die Schaltfläche "Push-Befehl anzeigen".
Dann werden die zum Drücken erforderlichen Befehle angezeigt, also ** kopieren und ohne nachzudenken ausführen. ** ** ** Wenn Sie hier versagen, funktionieren die AWS CLI-Einstellungen meiner Meinung nach nicht ordnungsgemäß. Überprüfen Sie daher die AWS CLI-Einstellungen.
Es ist schwierig, den Befehl jedes Mal einzugeben. Erstellen Sie daher ein Makefile, das eine Kopie des obigen Befehls ist. (Der Befehl "--username AWS" in 1 scheint eine Konstante zu sein.)
Makefile
.PHONY: help
help:
@echo " == push docker image to ECR == "
@echo "type 'make build tag push' to push docker image to ECR"
@echo ""
.PHONY: login
login:
(1 Befehl)aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin {ACCOUNT_NUMBER}.dkr.ecr.ap-northeast-1.amazonaws.com
.PHONY: build
build:
(2 Befehle)docker build -t {REPOSITORY_NAME} .
.PHONY: tag
tag:
(3 Befehle)docker tag {REPOSITORY_NAME}:latest {ACCOUNT_NUMBER}.dkr.ecr.ap-northeast-1.amazonaws.com/{REPOSITORY_NAME}:latest
.PHONY: push
push:
(4 Befehle)docker push {ACCOUNT_NUMBER}.dkr.ecr.ap-northeast-1.amazonaws.com/{REPOSITORY_NAME}:latest
Mit diesem Makefile können Sie den Befehl wie folgt leicht verkürzen. Darüber hinaus glaube ich nicht, dass das obige Makefile gefährliche Informationen enthält, selbst wenn es nach außen gelangt, sodass Sie den Quellcode freigeben können.
#Melden Sie sich bei ECR an
$ make login
#Schieben Sie das neueste Bild auf ECR
$ make build tag push
Der Implementierungsinhalt von CDK basiert auf diesem Artikel, der in TypeScript geschrieben wurde. Außerdem ist es besser, "$ cdk in it" in dem Verzeichnis auszuführen, in dem app.py im Voraus implementiert ist.
Jeder Paketname ist lang ... Außerdem ist die Installationszeit ziemlich lang.
$ pip install aws-cdk-core aws-cdk-aws-stepfunctions aws-cdk-aws-stepfunctions-tasks aws-cdk-aws-events-targets aws-cdk.aws-ec2 aws-cdk.aws-batch aws-cdk.aws-ecr
Erstellen Sie zunächst eine Klasse für die diesmal zu erstellende Umgebung.
Stack_name
und stack_env
werden als Argumente der BatchEnvironment-Klasse festgelegt.
Dies entspricht dem Namen dieser Umgebung und der Ausführungsumgebung (Verifikation / Entwicklung / Produktion).
(Wenn Sie die Ausführungsumgebung wirklich trennen möchten, müssen Sie meines Erachtens auch das ECR-Repository ändern.)
app.py
from aws_cdk import (
core,
aws_ec2,
aws_batch,
aws_ecr,
aws_ecs,
aws_iam,
aws_stepfunctions as aws_sfn,
aws_stepfunctions_tasks as aws_sfn_tasks,
aws_events,
aws_events_targets,
)
class BatchEnvironment(core.Stack):
"""
Stapelumgebung und Schrittfunktionen, um sie auszuführen+Erstellen Sie eine CloudWatch-Ereignisumgebung
"""
#Der oben erstellte ECR-Repository-Name
#Ziehen Sie Bilder aus diesem Repository, wenn Sie in Batch ausgeführt werden
ECR_REPOSITORY_ARN = "arn:aws:ecr:ap-northeast-1:{ACCOUNT_NUMBER}:repository/{YOUR_REPOSITORY_NAME}"
def __init__(self, app: core.App, stack_name: str, stack_env: str):
super().__init__(scope=app, id=f"{stack_name}-{stack_env}")
#Die folgende Implementierung ist das Bild unten hier.
app.py
# def __init__(...):im
#CIDR liegt in dem Bereich, den Sie mögen
cidr = "192.168.0.0/24"
# === #
# vpc #
# === #
#VPC ist (sollte) kostenlos verfügbar, wenn Sie nur das öffentliche Subnetz verwenden
vpc = aws_ec2.Vpc(
self,
id=f"{stack_name}-{stack_env}-vpc",
cidr=cidr,
subnet_configuration=[
#Definieren Sie die Netzmaske für das öffentliche Subnetz
aws_ec2.SubnetConfiguration(
cidr_mask=28,
name=f"{stack_name}-{stack_env}-public",
subnet_type=aws_ec2.SubnetType.PUBLIC,
)
],
)
security_group = aws_ec2.SecurityGroup(
self,
id=f'security-group-for-{stack_name}-{stack_env}',
vpc=vpc,
security_group_name=f'security-group-for-{stack_name}-{stack_env}',
allow_all_outbound=True
)
batch_role = aws_iam.Role(
scope=self,
id=f"batch_role_for_{stack_name}-{stack_env}",
role_name=f"batch_role_for_{stack_name}-{stack_env}",
assumed_by=aws_iam.ServicePrincipal("batch.amazonaws.com")
)
batch_role.add_managed_policy(
aws_iam.ManagedPolicy.from_managed_policy_arn(
scope=self,
id=f"AWSBatchServiceRole-{stack_env}",
managed_policy_arn="arn:aws:iam::aws:policy/service-role/AWSBatchServiceRole"
)
)
batch_role.add_to_policy(
aws_iam.PolicyStatement(
effect=aws_iam.Effect.ALLOW,
resources=[
"arn:aws:logs:*:*:*"
],
actions=[
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogStreams"
]
)
)
#Rolle für EC2
instance_role = aws_iam.Role(
scope=self,
id=f"instance_role_for_{stack_name}-{stack_env}",
role_name=f"instance_role_for_{stack_name}-{stack_env}",
assumed_by=aws_iam.ServicePrincipal("ec2.amazonaws.com")
)
instance_role.add_managed_policy(
aws_iam.ManagedPolicy.from_managed_policy_arn(
scope=self,
id=f"AmazonEC2ContainerServiceforEC2Role-{stack_env}",
managed_policy_arn="arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
)
)
#Richtlinie hinzufügen, um auf S3 zuzugreifen
instance_role.add_to_policy(
aws_iam.PolicyStatement(
effect=aws_iam.Effect.ALLOW,
resources=["*"],
actions=["s3:*"]
)
)
#Fügen Sie eine Richtlinie für den Zugriff auf CloudWatch-Protokolle hinzu
instance_role.add_to_policy(
aws_iam.PolicyStatement(
effect=aws_iam.Effect.ALLOW,
resources=[
"arn:aws:logs:*:*:*"
],
actions=[
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogStreams"
]
)
)
#Gewähren Sie EC2 eine Rolle
instance_profile = aws_iam.CfnInstanceProfile(
scope=self,
id=f"instance_profile_for_{stack_name}-{stack_env}",
instance_profile_name=f"instance_profile_for_{stack_name}-{stack_env}",
roles=[instance_role.role_name]
)
app.py
#Fortsetzung der VPC...
# ===== #
# batch #
# ===== #
batch_compute_resources = aws_batch.ComputeResources(
vpc=vpc,
maxv_cpus=4,
minv_cpus=0,
security_groups=[security_group],
instance_role=instance_profile.attr_arn,
type=aws_batch.ComputeResourceType.SPOT
)
batch_compute_environment = aws_batch.ComputeEnvironment(
scope=self,
id=f"ProjectEnvironment-{stack_env}",
compute_environment_name=f"ProjectEnvironmentBatch-{stack_env}",
compute_resources=batch_compute_resources,
service_role=batch_role
)
job_role = aws_iam.Role(
scope=self,
id=f"job_role_{stack_name}-{stack_env}",
role_name=f"job_role_{stack_name}-{stack_env}",
assumed_by=aws_iam.ServicePrincipal("ecs-tasks.amazonaws.com")
)
job_role.add_managed_policy(
aws_iam.ManagedPolicy.from_managed_policy_arn(
scope=self,
id=f"AmazonECSTaskExecutionRolePolicy_{stack_name}-{stack_env}",
managed_policy_arn="arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
)
)
job_role.add_managed_policy(
aws_iam.ManagedPolicy.from_managed_policy_arn(
scope=self,
id=f"AmazonS3FullAccess_{stack_name}-{stack_env}",
managed_policy_arn="arn:aws:iam::aws:policy/AmazonS3FullAccess"
)
)
job_role.add_managed_policy(
aws_iam.ManagedPolicy.from_managed_policy_arn(
scope=self,
id=f"CloudWatchLogsFullAccess_{stack_name}-{stack_env}",
managed_policy_arn="arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"
)
)
batch_job_queue = aws_batch.JobQueue(
scope=self,
id=f"job_queue_for_{stack_name}-{stack_env}",
job_queue_name=f"job_queue_for_{stack_name}-{stack_env}",
compute_environments=[
aws_batch.JobQueueComputeEnvironment(
compute_environment=batch_compute_environment,
order=1
)
],
priority=1
)
#Holen Sie sich das ECR-Repository
ecr_repository = aws_ecr.Repository.from_repository_arn(
scope=self,
id=f"image_for_{stack_name}-{stack_env}",
repository_arn=self.ECR_REPOSITORY_ARN
)
#Holen Sie sich ein Bild von ECR
container_image = aws_ecs.ContainerImage.from_ecr_repository(
repository=ecr_repository
)
#Jobdefinition
#Verwenden Sie es hier im Python-Skript`S3_BUCKET`Als Umgebungsvariable
batch_job_definition = aws_batch.JobDefinition(
scope=self,
id=f"job_definition_for_{stack_env}",
job_definition_name=f"job_definition_for_{stack_env}",
container=aws_batch.JobDefinitionContainer(
image=container_image,
environment={
"S3_BUCKET": f"{YOUR_S3_BUCKET}"
},
job_role=job_role,
vcpus=1,
memory_limit_mib=1024
)
)
Von hier aus ist es nicht immer notwendig, die Batch-Umgebung zu erstellen. Dies erfolgt mithilfe von Schrittfunktionen und CloudWatch-Ereignis für die regelmäßige Ausführung.
Sie können Batch auch direkt vom CloudWatch-Ereignis aus aufrufen. Zwischen ihnen sind Schrittfunktionen angeordnet, um die Zusammenarbeit mit anderen Diensten und die Übergabe von Parametern zu vereinfachen.
Bei der Registrierung als Schritt Funktionen Schritt Überschreiben Sie den Docker CMD-Befehl (= in der Jobdefinition von Batch festgelegt) und Es nimmt das Argument "Zeit" aus dem CloudWatch-Ereignis und übergibt es an das Python-Skript.
app.py
#Fortsetzung von Batch...
# ============= #
# StepFunctions #
# ============= #
command_overrides = [
"python", "__init__.py",
"--time", "Ref::time"
]
batch_task = aws_sfn_tasks.BatchSubmitJob(
scope=self,
id=f"batch_job_{stack_env}",
job_definition=batch_job_definition,
job_name=f"batch_job_{stack_env}_today",
job_queue=batch_job_queue,
container_overrides=aws_sfn_tasks.BatchContainerOverrides(
command=command_overrides
),
payload=aws_sfn.TaskInput.from_object(
{
"time.$": "$.time"
}
)
)
#Dieses Mal gibt es nur einen Schritt, es ist also einfach, aber wenn Sie mehrere Schritte verbinden möchten
# batch_task.next(aws_sfn_tasks.JOB).next(aws_sfn_tasks.JOB)
#Sie können es mit der Kettenmethode wie übergeben.
definition = batch_task
sfn_daily_process = aws_sfn.StateMachine(
scope=self,
id=f"YourProjectSFn-{stack_env}",
definition=definition
)
# ================ #
# CloudWatch Event #
# ================ #
# Run every day at 21:30 JST
# See https://docs.aws.amazon.com/lambda/latest/dg/tutorial-scheduled-events-schedule-expressions.html
events_daily_process = aws_events.Rule(
scope=self,
id=f"DailySFnProcess-{stack_env}",
schedule=aws_events.Schedule.cron(
minute=31,
hour=12,
month='*',
day="*",
year='*'),
)
events_daily_process.add_target(aws_events_targets.SfnStateMachine(sfn_daily_process))
#Bis hierher def__init__(...):
Schreiben Sie abschließend den Prozess zum Ausführen des CDK und Sie sind fertig.
app.py
#Hier def__init__(...):
def main():
app = core.App()
BatchEnvironment(app, "your-project", "feature")
BatchEnvironment(app, "your-project", "dev")
BatchEnvironment(app, "your-project", "prod")
app.synth()
if __name__ == "__main__":
main()
Überprüfen Sie nach Abschluss des obigen Skripts mit dem folgenden Befehl, ob das CDK richtig eingestellt ist, und stellen Sie es dann bereit. Selbst wenn Sie eine Batch-Umgebung von Grund auf neu erstellen, ist diese in etwa 10 Minuten abgeschlossen.
#Bestätigung der Definition
$ cdk synth
Successfully synthesized to {path_your_project}/cdk.out
Supply a stack id (your-project-dev, your-project-feature, your-project-prod) to display its template.
#Bestätigung der bereitstellbaren Umgebung
$ cdk ls
your-project-dev
your-project-feature
your-project-prod
$ cdk deploy your-project-feature
...deploying...
Wenn die Bereitstellung abgeschlossen ist, wählen Sie die von Ihnen in der Konsole erstellten Schrittfunktionen aus und klicken Sie auf die Schaltfläche "Ausführung starten".
Setzen Sie nur das Argument der "Zeit",
{
"time": "2020-09-27T12:31:00Z"
}
Wenn es richtig funktioniert, sind Sie fertig. Überprüfen Sie außerdem die CloudWatch-Protokolle, um festzustellen, ob sie wie erwartet funktionieren.
Ich mag CDK wirklich, weil Sie die Umgebung mit Befehlen schnell erstellen und löschen können!
Anstatt über die Konsole zu erstellen, können Sie auch sehen, was für die Programmparameter erforderlich ist Auch wenn Sie den Service nicht kennen, dachte ich, es wäre schön zu wissen, welche Parameter erforderlich sind!
(Eines Tages werde ich die obige Quelle im GitHub-Repository erweitern ...!)
Recommended Posts