تست کردن گردش کار تابع مرحله به صورت محلی

اگر مدتی است که دنبال میکنید، میدانید که من چقدر از طرفداران سرورهای بدون سرور هستم. و حتی به طور خاص تر، من چقدر توابع Step را دوست دارم. اگر مشکل نیاز به یک هماهنگ کننده گردش کار بسیار در دسترس را دارید، نمی توانید کاری بهتر از انتخاب آن به عنوان ابزار انتخابی خود انجام دهید. با این حال، من نیز بدون عذرخواهی از طرفداران توسعه محلی هستم. و این جایی است که من احساس می کنم که توابع مرحله کمی سقوط می کند. بنابراین من را در این حماسه دنبال کنید تا بتوانید گردش کار Step Function را به صورت محلی آزمایش کنید.
گردش کار
این کوچولویی است که قرار است مدتی را صرف کار کردن با آن کنیم
هدف از این راهنما ایجاد یک گردش کار بسیار قوی نیست، بلکه نشان دادن نحوه اجرای برخی از تستهای یکپارچهسازی بر روی دستگاه است.
نکته اصلی این ماشین حالت این است که باری را ارائه می دهد که یا به “موفقیت” یا “شکست” منجر می شود. باز هم، بسیار ساده است.
از اینجا، بیایید در مورد نحوه آزمایش گردشهای کاری Step Function به صورت محلی بپردازیم.
راه اندازی ماشین دولتی
این نمونه با استفاده از CDK به عنوان سازنده زیرساخت ساخته شده است. اگر با CDK آشنایی ندارید، یک شروع از اینجا وجود دارد.
ابتدا اجازه دهید ماشین حالت را با استفاده از CDK با TypeScript ایجاد کنیم.
// code above omitted
const flow = this.buildStateMachine(scope);
this._stateMachine = new stepfunctions.StateMachine(this, "StateMachine", {
stateMachineName: "SimpleStateMachine",
definition: flow,
stateMachineType: stepfunctions.StateMachineType.EXPRESS,
timeout: Duration.seconds(30),
logs: {
level: LogLevel.ALL,
destination: logGroup,
includeExecutionData: true,
},
});
// code above omitted
buildStateMachine = (scope: Construct): stepfunctions.IChainable => {
const succeed = new Succeed(scope, "Succeed");
const failure = new Fail(scope, "Fail");
return (
new Choice(this, "Success or Failure")
// Look at the "status" field
.when(Condition.stringEquals("$.path", "Succeed"), succeed)
.when(Condition.stringEquals("$.path", "Fail"), failure)
.otherwise(failure)
);
};
استقرار توابع مرحله به صورت محلی
توسعه ابر محلی گاهی اوقات می تواند کمی مشکل باشد. من در گذشته از Localstack برای بسیاری از سرویسها استفاده کردهام، اما وقتی تصویری را که به طور رسمی توسط AWS پشتیبانی میشود برای توابع Step پیدا کردم، هیجانزده شدم که آن را امتحان کنم. برای مرجع، در اینجا پیوند به آن تصویر Docker است.
یکی از چیزهای خوبی که من پیدا کردم این است که (تاکنون) تمام ویژگیهای برابری را دارد که من برای بکارگیری، آزمایش و حتی اجرای محلی نیاز دارم. به یاد داشته باشید، این «سرویس وب آمازون» است، درخواستهای این سرویسها فقط «GET, PUT, POST» هستند و شما میتوانید به صورت محلی با آنها تعامل داشته باشید.
با توجه به اینکه یک تصویر Docker است، من یک فایل Docker Compose ساده برای راهاندازی کانتینر و یک کانتینر جانبی برای Localstack ساختهام. دلیل Localstack این است که با Step Functions محلی، می توانید نقاط پایانی را برای چیزهایی که می خواهید “مسخره” کنید مانند Lambda، SQS، SNS و غیره مشخص کنید. من هیچ کدام از این کارها را برای این نمونه انجام نمی دهم، اما در صورتی که بخواهید آن را تمدید کنید، وجود دارد.
بیایید نگاهی به فایل نوشتن بیاندازیم
version: "3.4"
services:
localstack:
container_name: sf_localstack
image: localstack/localstack:latest
environment:
- AWS_DEFAULT_REGION=us-west-2
- HOSTNAME_EXTERNAL=localhost
- SERVICES=sqs # which services to start
- DEBUG=0
ports:
- 4566:4566
step-functions:
container_name: step-functions
image: amazon/aws-stepfunctions-local
depends_on:
- localstack
environment:
- AWS_DEFAULT_REGION=us-west-2 # this is used when resources are created
- AWS_ACCESS_KEY_ID=12345 # just to fill in the blanks
- AWS_SECRET_ACCESS_KEY=12345 # just to fill in the blanks
- SQS_ENDPOINT=host.docker.internal:4566 # connected to Localstack
ports:
- 8083:8083
اصول اولیه نوشتن فایل ها. Localstack را راه اندازی می کند و سپس کانتینر Step Functions را انجام می دهد.
استقرار در کانتینر محلی
حالا اینجا جایی است که مالش نهفته است. هنگام استقرار تا AWS از خروجی CDK برای ما مراقبت می شود. آن CloudFormation که تولید می شود بدون مشکل اجرا می شود. با این حال، من فقط می خواهم بخشی از CloudFormation مستقر شود.
قبلاً ابزار دیگری را دیده بودم که چنین کاری را انجام می داد، اما من اصلاً از نحوه بازگشت خروجی آن خوشم نیامد و به جای وصله کردن و صدور یک PR تصمیم گرفتم خودم را بسازم. این قسمت 1 بود که این فرآیند فکری را آغاز کرد. اگر می خواهید کتابخانه را ببینید، در آن مقاله یا اینجا آمده است
روش کار این است که تعریف را از State Machine در CloudFormation (JSON) استخراج می کند و سپس آرایه ای از ماشین ها را خروجی می دهد. خروجی به شکل زیر است:
[{"identifier":"SimpleStateMachine3C32178E","definition":"{\"StartAt\":\"Success or Failure\",\"States\":{\"Success or Failure\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.path\",\"StringEquals\":\"Succeed\",\"Next\":\"Succeed\"},{\"Variable\":\"$.path\",\"StringEquals\":\"Fail\",\"Next\":\"Fail\"}],\"Default\":\"Fail\"},\"Fail\":{\"Type\":\"Fail\"},\"Succeed\":{\"Type\":\"Succeed\"}},\"TimeoutSeconds\":30}"}]
پس با آن چه کنیم؟ ساده است، آن را به داخل ظرف توابع Step فشار دهید تا ماشین حالت ایجاد شود.
aws stepfunctions --endpoint-url http://localhost:8083 create-state-machine --definition <the body> --name <the id> --role-arn "arn:aws:iam::012345678901:role/DummyRole" --type "EXPRESS"
پس از استقرار، یک لیست سریع اجرا کنید:
# AWS CLI Command
aws stepfunctions --endpoint-url http://localhost:8083 list-state-machines
# the output
{
"stateMachines": [
{
"stateMachineArn": "arn:aws:states:us-west-2:123456789012:stateMachine:SimpleStateMachine3C32178E",
"name": "SimpleStateMachine3C32178E",
"type": "EXPRESS",
"creationDate": "2023-04-14T13:56:45.260000-05:00"
}
]
}
با حرکت درست، اکنون می توانیم آزمایش را شروع کنیم
تست کردن گردش کار تابع مرحله به صورت محلی
اکنون که این State Machine را راهاندازی کردهایم، چگونه آن را آزمایش کنیم؟ راه های زیادی.
- پستچی
- حلقه
- است
- jUnit
- …
برای این مثال، من به شما نشان میدهم که چگونه Jest را برای آزمایش این موارد تنظیم کنید. من جست را به چند دلیل دوست دارم
- من می توانم از TypeScript استفاده کنم که همان چیزی است که برای کد CDK استفاده می کنم.
- AWS SDK v3 برای جاوا اسکریپت جامد است
برای جداسازی تستهای State Machine خود، یک مجموعه آزمایشی مانند این ایجاد میکنم.
import { SFNClient, StartSyncExecutionCommand } from "@aws-sdk/client-sfn";
describe("SF Integration Tests", () => {
const client = new SFNClient({
region: "us-west-2",
endpoint: "http://localhost:8083",
disableHostPrefix: true,
});
});
و سپس می توانم یک آزمایش با باری که می خواهم این را دوست داشته باشم، اجرا کنم
it("Should Succeed Success Path", async () => {
const startCommand = new StartSyncExecutionCommand({
stateMachineArn:
"arn:aws:states:us-west-2:123456789012:stateMachine:SimpleStateMachine3C32178E",
input: '{"path": "Succeed"}',
});
const startOutput = await client.send(startCommand);
expect(startOutput.status).toBe("SUCCEEDED");
});
بنابراین به نحوه راه اندازی State Machine توجه کنید. اطلاعات زیادی در مورد عدم امکان اجرای StartSync با کانتینر محلی در اینترنت خواهید دید. این دیگر درست نیست. اولین مشکلی که خواهید دید این است که از آن پشتیبانی نمی کند. این کار را انجام می دهد. مورد دوم این است که StartSync اضافه می شود sync-
به میزبان برای نقطه پایانی شما. و هنگام تنظیم نقطه پایانی خود برای اجراهای محلی، اضافه می شود sync-http://localhost
. این یک نقطه پایان معتبر نیست.
پس از کمی جستجو در مخزن GitHub و خواندن اینکه این پشتیبانی در Java SDK است، گزینه مورد نظر را پیدا کردم. در تنظیمات مشتری، مطمئن شوید که تنظیم کرده اید disableHostPrevix: true
const client = new SFNClient({
region: "us-west-2",
endpoint: "http://localhost:8083",
disableHostPrefix: true, // <----- HERE
});
اجرای مجموعه به این شکل خواهد بود
بنابراین هنگام آزمایش گردش کار تابع Step به صورت محلی، می توانم از همان ابزاری که به آن عادت کرده ام استفاده کنم.
- داکر
- CDK و TypeScript
- چارچوب و ابزار تست من را بیاورید
- AWS SDK که به صورت محلی و در فضای ابری کار می کند
همه اش را بگذار کنار هم
آزمایش گردشهای کاری تابع مرحله به صورت محلی شگفتانگیز است و بررسیهای زیادی را به گردش کار توسعه محلی من اضافه میکند. و مطمئناً این چیز خوبی است. اما اگر بخواهم یک قدم فراتر بروم چه؟ اگر بخواهم این را در خط لوله CI/CD خود برای توسعه زیرساختم معرفی کنم، چه میشود؟
بیایید همین کار را بکنیم! بیایید L3 Construct به نام CDK Pipelines را به ترکیب اضافه کنیم. اگر آشنا نیستید، در اینجا یک آغازگر سریع وجود دارد.
کد خط لوله به این شکل است.
export class PipelineStack extends Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
const pipeline = new CodePipeline(this, "Pipeline", {
pipelineName: "SamplePipeline",
dockerEnabledForSynth: true,
synth: new ShellStep("Synth", {
input: CodePipelineSource.gitHub(
"benbpyle/cdk-step-functions-local-testing",
"main",
{
authentication: SecretValue.secretsManager(
"sf-sample",
{
jsonField: "github",
}
),
}
),
commands: [
"npm i",
"npm i cdk-asl-definition-extractor -g",
"make test-start-local",
],
}),
synthCodeBuildDefaults: {
buildEnvironment: {
buildImage: LinuxBuildImage.STANDARD_6_0,
environmentVariables: {
DOCKERHUB_USERNAME: {
type: BuildEnvironmentVariableType.SECRETS_MANAGER,
value: "dockerhub:username",
},
DOCKERHUB_PASSWORD: {
type: BuildEnvironmentVariableType.SECRETS_MANAGER,
value: "dockerhub:password",
},
},
},
partialBuildSpec: BuildSpec.fromObject({
phases: {
install: {
"runtime-versions": {
nodejs: "16",
},
commands: [
"docker login --username $DOCKERHUB_USERNAME --password $DOCKERHUB_PASSWORD",
],
},
},
}),
},
});
pipeline.addStage(new PipelineStage(this, "PipelineStage"));
}
}
چند نکته قابل ذکر است. ابتدا، مرحله ترکیبی از GitHub با کلید API شخصی واکشی شده از AWS Secrets Manager است.
input: CodePipelineSource.gitHub(
"benbpyle/cdk-step-functions-local-testing",
"main",
{
// the IAM policy gets added by default
authentication: SecretValue.secretsManager(
"sf-sample",
{
jsonField: "github",
}
),
}
),
دوم، من وابستگی را برای بسته NPM خود که در بالا ذکر شد اضافه می کنم و سپس آن را اجرا می کنم Make
فرمان
commands: [
"npm i",
"npm i cdk-asl-definition-extractor -g",
"make test-start-local",
],
سوم، Make
دستور فقط مراحلی را که در بالا توضیح دادم اجرا می کند
test-start-local:
npx cdk synth --quiet # build
docker-compose up -d --quiet-pull # run the containers
sleep 10 # pause to let localstack startup
node scripts/index.js # a runner for posting the state machine into the local container
npm run test-sf # executes the Jest tests
make test-end-local # teardown
دنبال کردن مراحل به اندازه کافی آسان است، اما مستندسازی آنها عبارتند از:
- synth را اجرا کنید و خروجی CloudFormation را بسازید
- کانتینرهای محلی Localstack و Step Functions را بیاورید
- مکث جزئی … این برای Localstack است
- یک اسکریپت در POST در تعریف ماشین حالت اجرا کنید
- تست هایی را که در بالا نگاه کردیم را اجرا کنید
- فروپاشی را خراب کنید
برای بالا بردن این موضوع به محیط AWS خود، اجرا کنید cdk deploy
(البته بعد از اینکه بوت استرپ کردید) و می روید. باید در نهایت مانند شکل زیر باشد
فرو رفتن عمیق تر در مرحله ساخت، گزارش هایی را به دست می دهد که نشان می دهد آزمایشات شما اجرا شده است.
علاوه بر این، من میخواستم بتوانم برخی از محدودیتهای کشش Docker را دور بزنم و از پشتیبانی Node 16 استفاده کنم، بنابراین این تغییرات را در CodeBuild Definition انجام دادم. توجه داشته باشید که من دوباره از SecretsManager برای بیرون کشیدن اطلاعات کاربری حساس Docker خود مانند رمز دسترسی شخصی GitHub استفاده می کنم. چنین سرویس باحالی
synthCodeBuildDefaults: {
buildEnvironment: {
buildImage: LinuxBuildImage.STANDARD_6_0,
environmentVariables: {
DOCKERHUB_USERNAME: {
type: BuildEnvironmentVariableType.SECRETS_MANAGER,
value: "dockerhub:username",
},
DOCKERHUB_PASSWORD: {
type: BuildEnvironmentVariableType.SECRETS_MANAGER,
value: "dockerhub:password",
},
},
},
partialBuildSpec: BuildSpec.fromObject({
phases: {
install: {
"runtime-versions": {
nodejs: "16",
},
commands: [
"docker login --username $DOCKERHUB_USERNAME --password $DOCKERHUB_PASSWORD",
],
},
},
}),
},
تأیید ماشین دولتی مستقر شد
آخرین قطعه از این حکیم این است که تأیید کند که CodePipeline ما ماشین State ما را به درستی بیرون زده است.
و آنجاست! با گردش کار
ما به دایره کامل رسیده ایم!
بسته بندی
اگر تمام این سفر را دنبال کردهاید، در اینجا مخزن GitHub به عنوان پاداش شما است که میتوانید آن را چنگال، بکشید یا هر چیز دیگری با این کد بازی کنید.
من امیدوارم که بتوانید چگونه تست کردن گردش های کاری Step Function به صورت محلی نه تنها امکان پذیر است بلکه می تواند در پلتفرم CI/CD شما نیز گنجانده شود. و با استفاده از ابزاری که با آن راحتتر هستید، میتوانید در اجرای چیزی شبیه به این کارایی به دست آورید. من مطمئناً می دانم که توانایی آزمایش محلی برای من شخصاً از نظر کارایی و تجربه توسعه دهنده تفاوت زیادی ایجاد کرده است.
امیدوارم از خواندن لذت برده باشید و این مطلب مفید بوده باشد!
آزمایش گردش کار تابع مرحله به صورت محلی لازم نیست سخت باشد. با استفاده از CDK، Docker و یک تصویر محلی، می توانید با دنبال کردن مقاله این کار را انجام دهید