<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Peter Butkovic on Medium]]></title>
        <description><![CDATA[Stories by Peter Butkovic on Medium]]></description>
        <link>https://medium.com/@butkovic?source=rss-74fc69b20a6f------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*JdWtwDmk55TCOnxC3El6rA.jpeg</url>
            <title>Stories by Peter Butkovic on Medium</title>
            <link>https://medium.com/@butkovic?source=rss-74fc69b20a6f------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 05 Nov 2026 18:47:49 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@butkovic/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Devless: Turning GitHub Issues into Pull Requests with Amazon Bedrock]]></title>
            <link>https://medium.com/@butkovic/devless-turning-github-issues-into-pull-requests-with-amazon-bedrock-02958ddc6ba7?source=rss-74fc69b20a6f------2</link>
            <guid isPermaLink="false">https://medium.com/p/02958ddc6ba7</guid>
            <category><![CDATA[aider]]></category>
            <category><![CDATA[amazon-bedrock]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[agentic]]></category>
            <dc:creator><![CDATA[Peter Butkovic]]></dc:creator>
            <pubDate>Tue, 20 Oct 2026 16:02:44 GMT</pubDate>
            <atom:updated>2026-10-20T16:06:52.328Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Architecture, implementation trade-offs and operational boundaries of a self-hosted AI coding agent on AWS.</em></p><h3>Objective and scope</h3><p>My objective was to evaluate an issue-driven development workflow in which team members can request changes through GitHub without operating a coding agent directly.</p><h3>Desired workflow</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1022/1*u6ldkcsO_TjLm7cHw8iAdQ.png" /></figure><p>The agent should generate code, open a review-ready pull request and address review comments. Human review and repository checks remain the approval boundary; requesting a change must not imply permission to merge it.</p><h3>Scope and design constraints</h3><p>Hosted coding agents already support this interaction model. I built this proof of concept to evaluate self-hosted orchestration on AWS with Amazon Bedrock, rather than to establish a replacement for hosted products.</p><p>The design priorities were asynchronous processing of long-running coding tasks, configurable models and repository checks, reviewable infrastructure, and reduced idle worker compute. Self-hosting provides control over those choices but also makes infrastructure, permissions and failure recovery the operator’s responsibility.</p><p>The result is <strong>Devless</strong>, open source at <a href="https://github.com/typekpb/devless">github.com/typekpb/devless</a>, with a demo target repository at <a href="https://github.com/typekpb/devless-demo">github.com/typekpb/devless-demo</a>.</p><blockquote><em>Devless is a proof of concept, not a production service.</em></blockquote><h3>Solution options and rationale</h3><h4>Devless</h4><ul><li><strong>Setup:</strong> AWS bootstrap, GitHub App and Terraform.</li><li><strong>Cost:</strong> AWS infrastructure and model usage.</li><li><strong>LLM choice:</strong> Configurable through the coding backend.</li><li><strong>Customization:</strong> Own orchestrator, prompts and tools.</li><li><strong>Data boundary:</strong> AWS processing plus GitHub and configured tools.</li><li><strong>Operations:</strong> Operator-managed.</li></ul><h4>GitHub Copilot coding agent</h4><ul><li><strong>Setup:</strong> GitHub configuration and repository access.</li><li><strong>Cost:</strong> Current GitHub plan and usage pricing.</li><li><strong>LLM choice:</strong> Supported GitHub models and settings.</li><li><strong>Customization:</strong> Product-provided controls.</li><li><strong>Data boundary:</strong> GitHub service and configured integrations.</li><li><strong>Operations:</strong> Provider-managed.</li></ul><p>A hosted agent is the simpler starting point when avoiding worker infrastructure is the priority. Self-hosting was appropriate for this experiment because control over AWS deployment, model integration and orchestration was the subject of the evaluation, not because operating an agent is inherently preferable.</p><h3>Architectural trade-offs across AWS Well-Architected pillars</h3><p><strong>Operational Excellence — </strong><a href="https://docs.aws.amazon.com/wellarchitected/latest/operational-excellence-pillar/ops_dev_integ_version_control.html"><strong>OPS05-BP01: Use version control</strong></a><strong>.</strong> Terraform keeps infrastructure changes reviewable alongside application code and prompts, while issue comments and PRs keep work visible in the existing review workflow. Self-hosting still means maintaining images, dependencies and prompts; version control does not remove that work.</p><p><strong>Reliability — </strong><a href="https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/rel_prevent_interaction_failure_loosely_coupled_system.html"><strong>REL04-BP02: Implement loosely coupled dependencies</strong></a><strong>.</strong> SQS separates webhook acceptance from slow model calls, and a dead-letter queue provides a failure path. GitHub does not have to wait for code generation, but retries can repeat work: queueing alone does not guarantee exactly-once code changes or PR creation.</p><p><strong>Cost Optimization — </strong><a href="https://docs.aws.amazon.com/wellarchitected/latest/cost-optimization-pillar/cost_manage_demand_resources_dynamic.html"><strong>COST09-BP03: Supply resources dynamically</strong></a><strong>.</strong> Scaling Fargate to zero removes worker compute charges while no tasks run, at the cost of startup delay. Model retries, the scale-in waiting period and other infrastructure still contribute to the bill.</p><p><strong>Security — </strong><a href="https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/sec_identities_unique.html"><strong>SEC02-BP02: Use temporary credentials</strong></a><strong>.</strong> GitHub App tokens and IAM roles avoid distributing long-lived user credentials, following <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html">IAM guidance on temporary credentials for workloads</a>. The GitHub App private key and webhook secret still need protection and rotation. Temporary credentials do not make generated code safe; repository access and merge controls remain important boundaries.</p><h3>Solution architecture and event flow</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hdeCTP5q61CuR7HO5VVbYg.png" /></figure><p>The architecture separates webhook ingestion from code generation through an asynchronous queue:</p><ol><li><strong>Amazon API Gateway</strong> exposes POST /webhook for GitHub events: issues, issue comments, PR reviews and PR review comments.</li><li><strong>AWS Lambda</strong> (webhook_handler) validates the GitHub HMAC signature, ignores bot senders (so the agent never triggers itself), and puts a task on a queue.</li><li><strong>Amazon SQS</strong> buffers the tasks. The current redrive policy sets maxReceiveCount = 1. Unprocessed messages can move to a <strong>dead-letter queue</strong> after that receive allowance. A second Lambda (dlq_processor) then posts a failure comment on the issue, so failures are visible to users.</li><li><strong>Amazon ECS on AWS Fargate</strong> runs the orchestrator container, with the image stored in <strong>Amazon ECR</strong>. <strong>Amazon CloudWatch</strong> alarms on ApproximateNumberOfMessagesVisible scale the state-machine service from 0 to 1 and back, with a waiting period before scale-in.</li><li><strong>Amazon Bedrock</strong> provides the models: the demo config uses Claude Sonnet for Aider; the separate Strands factory defaults to Haiku. The ECS task role is granted bedrock:InvokeModel, so there is no Bedrock API key to manage.</li><li><strong>AWS Systems Manager Parameter Store</strong> holds the secrets, and <strong>Amazon S3</strong> stores issue context between runs.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_DY-h9E6Dw8_UK3nhDwWwQ.png" /></figure><h3>Webhook ingestion and validation</h3><p>The webhook Lambda validates the signature, routes the event and enqueues it. The complete validation function from <a href="https://github.com/typekpb/devless/blob/7e2611df1ce8488a3b9f1f3a26a2e29b117516a1/lambda/webhook_handler/handler.py#L23-L29">lambda/webhook_handler/handler.py</a> uses Python&#39;s standard-library hashlib and hmac imports:</p><pre>def validate_signature(payload: bytes, secret: str, header: str) -&gt; bool:<br>    if not header or not header.startswith(&quot;sha256=&quot;):<br>        return False<br>    expected = &quot;sha256=&quot; + hmac.new(<br>        secret.encode(), payload, hashlib.sha256<br>    ).hexdigest()<br>    return hmac.compare_digest(expected, header)</pre><p>This follows <a href="https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries">GitHub’s webhook validation guidance</a>: compute HMAC-SHA256 over the unmodified payload and compare in constant time. Validate before parsing or acting on the delivery.</p><h3>Message delivery and failure handling</h3><p>For accepted events, the handler returns 202 Accepted only after SQS accepts the message, without waiting on the LLM. SQS buffers work and separates the two components, but the retry budget is a configuration choice. The complete task-queue resource in <a href="https://github.com/typekpb/devless/blob/7e2611df1ce8488a3b9f1f3a26a2e29b117516a1/terraform/modules/sqs/main.tf#L5-L13">terraform/modules/sqs/main.tf</a> makes that choice explicit:</p><pre>resource &quot;aws_sqs_queue&quot; &quot;main&quot; {<br>  name                       = &quot;${var.project_name}-tasks&quot;<br>  visibility_timeout_seconds = 1200<br><br>  redrive_policy = jsonencode({<br>    deadLetterTargetArn = aws_sqs_queue.dlq.arn<br>    maxReceiveCount     = 1<br>  })<br>}</pre><p>That is a PoC setting, not my production recommendation. <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html">AWS recommends allowing sufficient retries</a>, and the 20-minute visibility timeout must cover processing or be extended with a heartbeat, as described in the <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html">SQS visibility-timeout guidance</a>. The current state-machine consumer does not extend it. Duplicate-safe operations and a tested redrive procedure are still needed.</p><h3>Worker capacity and scale-in behavior</h3><p>In <a href="https://github.com/typekpb/devless/blob/7e2611df1ce8488a3b9f1f3a26a2e29b117516a1/terraform/modules/ecs/main.tf#L94-L170">terraform/modules/ecs/main.tf</a>, the state-machine service scales between zero and one worker; scale-in waits for 30 one-minute periods with no visible messages. A received message becomes invisible while it is still being processed, so an empty <em>visible</em> queue is not proof that a worker is idle. Long jobs need in-flight-aware shutdown protection. These scaling policies apply to the state-machine service; the Strands service starts disabled and must be enabled separately.</p><h3>Implementation decisions</h3><h3>Code generation and validation</h3><p>I reused <a href="https://aider.chat">Aider</a> rather than building a code-editing loop. It integrates with Bedrock (bedrock/&lt;model-id&gt;) and supports <em>auto-test</em>: running a configured test command after edits and attempting repairs within a bounded reflection loop. This keeps repository editing separate from event handling and orchestration.</p><p>Once the GitHub App and webhook are configured, each target repository supplies its model and checks in one file. This example is the <a href="https://github.com/typekpb/devless-demo/blob/main/.devless/config.yml">demo repository’s configuration</a>:</p><pre># .devless/config.yml<br>model: bedrock/eu.anthropic.claude-sonnet-4-20250514-v1:0<br>test_command: npm ci &amp;&amp; npx vitest --run &amp;&amp; npm run build<br>max_iterations: 10</pre><p>The model IDs and versions shown here record the proof-of-concept configuration, not a standing recommendation. Model capabilities, pricing and availability evolve, so the specific choice may change. The underlying concepts — separate orchestration from code editing, validate generated changes and keep human approval explicit — do not depend on those versions. Before reusing this setup, check current Bedrock availability and evaluate candidate models on representative repository tasks.</p><p>Put build, lint and tests into test_command. Aider treats a failing build like a failing test. The state-machine implementation checks for a commit and a non-failing test outcome before opening a PR. The agent-based version follows workflow instructions instead; that is not an equivalent hard guarantee. In either version, repository CI and review must remain the merge gate.</p><p>The code-editing setup in <a href="https://github.com/typekpb/devless/blob/7e2611df1ce8488a3b9f1f3a26a2e29b117516a1/orchestrator/src/aider_runner.py#L138-L150">orchestrator/src/aider_runner.py</a> shows what is delegated to Aider (excerpt inside run_aider):</p><pre>coder = Coder.create(<br>    main_model=model_obj,<br>    io=io_obj,<br>    fnames=[],<br>    auto_test=True,<br>    test_cmd=test_cmd,<br>    auto_commits=True,<br>)<br>coder.max_reflections = 10  # Allow up to 10 iterations to fix test/build failures</pre><p>Two limits matter here. This implementation hardcodes ten reflections rather than using the configured max_iterations. Its success check accepts test_outcome is not False, which also accepts None when tests did not run. For a production gate, I would require an explicit passing result and independently enforce <a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches">required status checks and reviews through branch protection</a>. Tests help assess a change; an agent&#39;s success flag must not replace those controls.</p><h3>Orchestration: explicit state machine or agent-directed workflow</h3><p>I built the orchestrator twice to compare a deterministic design with an agentic one.</p><h4>Deterministic orchestration with a Python state machine</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Bssquc2aV9mQ5loSxRu2NA.png" /></figure><p>Python code controls the flow through explicit states: pending → cloned → agent_loop → pr_created → complete. Any failure resets the task to pending.</p><ul><li><strong>Pros:</strong> deterministic, easy to debug.</li><li><strong>Cons:</strong> rigid. Every new workflow needs code changes. About 800 lines.</li></ul><h4>Agent-directed orchestration with Strands Agents and skills</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mEcej-6et5866uiSTWdfrg.png" /></figure><p>The second version uses <a href="https://strandsagents.com">Strands Agents</a> with a BedrockModel. The model decides the flow. It follows a SYSTEM.md prompt and a set of SKILL.md files (workflow, github, git, code), and it acts through a small set of tools:</p><ul><li><strong>GitHub:</strong> get_issue, post_comment, create_pull_request, get_pull_request</li><li><strong>Git:</strong> clone_repository, create_branch, checkout_branch, push_branch</li><li><strong>Code:</strong> read_project_config, run_aider, list_files, read_file</li></ul><p>The factory in <a href="https://github.com/typekpb/devless/blob/7e2611df1ce8488a3b9f1f3a26a2e29b117516a1/orchestrator-agent/agent.py#L35-L46">orchestrator-agent/agent.py</a> configures the orchestration model and available tools:</p><pre>def create_agent(model_id: str = &quot;anthropic.claude-3-haiku-20240307-v1:0&quot;) -&gt; Agent:<br>    model = BedrockModel(<br>        model_id=model_id,<br>        region_name=&quot;us-east-1&quot;,<br>    )<br><br>    return Agent(<br>        model=model,<br>        system_prompt=load_system_prompt(),<br>        tools=ALL_TOOLS,<br>    )</pre><p>The outer Strands agent and inner Aider coding loop have separate model settings. Deployment location, orchestration-model Region and coding-model routing all need checking against residency requirements.</p><p>For changes within the existing tool set, the workflow can be adjusted in Markdown. Those files still need shipping with the application; a new capability may also need tool code. The original comparison was roughly 800 lines for the state machine and 400 for the agent version, not a measure of equivalent reliability.</p><h4><strong>State machine</strong></h4><ul><li><strong>Control flow:</strong> Hardcoded Python.</li><li><strong>Lines of code:</strong> Roughly 800.</li><li><strong>Adding workflows:</strong> Code changes.</li><li><strong>Error handling:</strong> try/except.</li><li><strong>Predictability:</strong> Deterministic control flow.</li><li><strong>Flexibility:</strong> Rigid workflow.</li></ul><h4><strong>Agent-based</strong></h4><ul><li><strong>Control flow:</strong> LLM guided by SKILL.md.</li><li><strong>Lines of code:</strong> Roughly 400.</li><li><strong>Adding workflows:</strong> Edit SKILL.md within the existing tool set.</li><li><strong>Error handling:</strong> Agent decides how to respond.</li><li><strong>Predictability:</strong> May vary.</li><li><strong>Flexibility:</strong> Adapts the workflow.</li></ul><h3>Security architecture and trust boundaries</h3><p>An agent that can push code is a high-value target. These are the boundaries I used, and the gaps I would close before production:</p><ul><li><strong>GitHub App instead of personal access tokens.</strong> Installation tokens are short-lived and scoped to the selected repositories (Contents, Issues and Pull requests read/write; Checks read-only). There are no PATs to rotate.</li><li><strong>Keep tokens out of prompts.</strong> The orchestrator passes the token to init_github() and init_git(). Tools hold it outside the prompt and perform authenticated operations. That is the intended boundary, not proof that exceptions, subprocess output or logs cannot leak it; those paths need review too.</li><li><strong>IAM for Bedrock.</strong> The Fargate task role grants access to the model, with no keys stored.</li><li><strong>OIDC for CI/CD.</strong> GitHub Actions assumes an IAM role through OIDC federation, so the pipeline holds no static AWS keys. The current <a href="https://github.com/typekpb/devless/blob/7e2611df1ce8488a3b9f1f3a26a2e29b117516a1/terraform/modules/iam/github_actions.tf#L18-L25">trust policy</a> allows repo:${var.github_actions_org}/*:*. Before production, I would narrow that to the deploying repository and branch or protected environment, following <a href="https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services">GitHub&#39;s AWS OIDC guidance</a>.</li><li><strong>HMAC on every webhook</strong>, compared in constant time. This authenticates the delivery, not the issue author’s authority to run code. The handler filters bots but has no sender-authorization gate. Public issues and comments are untrusted input; production needs an explicit authorization policy before enqueueing work.</li><li><strong>Review before merge.</strong> PRs provide the review boundary. The repository also describes auto-merge after review and checks; branch protection must enforce whichever policy the target repository requires.</li></ul><p>Keeping credentials outside prompts is a useful separation, not a sandbox. Repository code, dependency installation and test_command execute in the worker, which also has credentials and an AWS task role. I would isolate that execution and reduce credential exposure before accepting untrusted work. The current <a href="https://github.com/typekpb/devless/blob/7e2611df1ce8488a3b9f1f3a26a2e29b117516a1/terraform/modules/iam/main.tf#L82-L88">Bedrock IAM policy</a> also permits broad model and inference-profile resources across Regions; <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html">IAM least-privilege guidance</a> calls for narrowing those permissions to the workload&#39;s actual needs. These are hardening tasks, not claims that the PoC already meets every Security pillar practice.</p><h3>Deployment and operating cost</h3><p>The whole stack is Terraform modules: api_gateway, lambda, sqs, ecs, ecr, iam, s3, secrets, vpc. One terraform apply deploys everything. After the first bootstrap, every push to main runs terraform apply, builds and pushes the image to ECR, and redeploys ECS.</p><p>The reported running cost for this PoC was about <strong>$20/month</strong>. Treat that as an observation for this experiment, not a quote for another workload: model choice, token volume, retries, networking and worker runtime change the total.</p><h3>Evaluation and production-readiness gaps</h3><p>The PoC produced two implementations of the same issue-to-PR workflow and a demo repository for exercising it. The useful comparison was not just code size: explicit states made the sequence easy to inspect, while the agent version moved more workflow decisions into instructions.</p><p>This was not a controlled benchmark of completion rate, security or cost per accepted change. Before treating it as a service, I would test duplicate deliveries, interrupted jobs, concurrent work on the same repository, malicious issue content and recovery after a partially completed push. Passing generated tests alone is not evidence that a change meets the request.</p><h3>Architectural lessons and next steps</h3><p><strong>What worked</strong></p><ul><li>Aider, which is battle-tested.</li><li>GitHub App auth, which removes PATs.</li><li>Bedrock’s IAM-based authentication, alongside the required model and Region configuration.</li><li>SQS with a DLQ plus scale-to-zero Fargate, a straightforward asynchronous backbone, with retry and in-flight shutdown behavior still needing hardening.</li></ul><p><strong>Model choice matters; versions are a snapshot.</strong> In this experiment, Claude 3 Haiku got stuck on a simple TypeScript error, an unused import. It kept <em>adding</em> import React instead of removing it. Sonnet fixed it on the first try. That observation is not a ranking of later model generations. The lasting lesson is to evaluate task completion, latency and total cost, including repair iterations, rather than token price alone.</p><p>For capability categories, <a href="https://platform.claude.com/docs/en/about-claude/models/overview">Anthropic’s model overview</a> distinguishes models aimed at high-volume, latency-sensitive tasks (Haiku), a balance of speed and intelligence (Sonnet), and long-running agentic coding and knowledge work (Opus). These are useful selection categories, not fixed capability guarantees or an exhaustive list as the lineup evolves. Evaluate the orchestration and coding models separately: reliable tool selection and multi-file code repair are different demands.</p><p><strong>Key insight.</strong> Moving workflow decisions into an agent reduced orchestration code in this experiment, but did not preserve deterministic control automatically. Tools constrain available actions; they do not guarantee the order or judgment of those actions. I would use explicit checks for non-negotiable gates and an agent where flexibility earns its cost. The Haiku/Sonnet result is a reason to evaluate models on representative tasks, not a universal ranking.</p><p>This distinction follows <a href="https://www.anthropic.com/engineering/building-effective-agents">Anthropic’s <em>Building effective agents</em></a>: workflows follow predefined code paths, while agents let the model direct the process and tool use. Its advice to start with the simplest approach and add complexity only when task performance justifies the latency and cost applies beyond any particular model version.</p><h3>Workflow suitability</h3><p>Comparison:</p><ul><li><strong>Vibe coding:</strong> Instant feedback, best suited to quick prototypes by developers.</li><li><strong>Spec-driven development (e.g. Kiro):</strong> Upfront planning, best suited to greenfield projects involving architects and PMs.</li><li><strong>GitHub issues (Devless):</strong> Slower feedback while waiting for a PR, best suited to existing projects where non-developers and PMs request changes.</li></ul><p>The feedback loop is slower, but the issue-driven approach lets non-developers request features in a channel they already use. A natural next step is streaming Aider output into issue comments so users can follow progress.</p><h3>Source code and implementation details</h3><p>The AWS deployment was temporary and is no longer running. The following repositories supplied the implementation details for this post; their main branches remain available for inspection:</p><ul><li><a href="https://github.com/typekpb/devless/tree/main">Devless (</a><a href="https://github.com/typekpb/devless/tree/main">main branch)</a>: Terraform infrastructure, webhook handlers, both orchestrator implementations and workflow instructions.</li><li><a href="https://github.com/typekpb/devless-demo/tree/main">Demo target repository (</a><a href="https://github.com/typekpb/devless-demo/tree/main">main branch)</a>: the application and repository configuration used to exercise the issue-to-PR workflow during the proof of concept.</li></ul><h3>References</h3><ul><li>Devless source code (main branch): <a href="https://github.com/typekpb/devless/tree/main">https://github.com/typekpb/devless/tree/main</a></li><li>Devless demo source code (main branch): <a href="https://github.com/typekpb/devless-demo/tree/main">https://github.com/typekpb/devless-demo/tree/main</a></li><li>Anthropic, Building effective agents: <a href="https://www.anthropic.com/engineering/building-effective-agents">https://www.anthropic.com/engineering/building-effective-agents</a></li><li>Anthropic, Claude models overview and capability trade-offs: <a href="https://platform.claude.com/docs/en/about-claude/models/overview">https://platform.claude.com/docs/en/about-claude/models/overview</a></li><li>Well-Architected OPS05-BP01: Use version control: <a href="https://docs.aws.amazon.com/wellarchitected/latest/operational-excellence-pillar/ops_dev_integ_version_control.html">https://docs.aws.amazon.com/wellarchitected/latest/operational-excellence-pillar/ops_dev_integ_version_control.html</a></li><li>Well-Architected REL04-BP02: Implement loosely coupled dependencies: <a href="https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/rel_prevent_interaction_failure_loosely_coupled_system.html">https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/rel_prevent_interaction_failure_loosely_coupled_system.html</a></li><li>Well-Architected COST09-BP03: Supply resources dynamically: <a href="https://docs.aws.amazon.com/wellarchitected/latest/cost-optimization-pillar/cost_manage_demand_resources_dynamic.html">https://docs.aws.amazon.com/wellarchitected/latest/cost-optimization-pillar/cost_manage_demand_resources_dynamic.html</a></li><li>Well-Architected SEC02-BP02: Use temporary credentials: <a href="https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/sec_identities_unique.html">https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/sec_identities_unique.html</a></li><li>IAM security best practices: <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html">https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html</a></li><li>Amazon SQS visibility timeout: <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html">https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html</a></li><li>Amazon Bedrock cross-Region inference: <a href="https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html">https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html</a></li><li>GitHub protected branches: required checks and reviews: <a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches">https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches</a></li><li>Amazon Bedrock User Guide: <a href="https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html">https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html</a></li><li>Strands Agents SDK: <a href="https://strandsagents.com">https://strandsagents.com</a></li><li>Aider: <a href="https://aider.chat">https://aider.chat</a></li><li>Amazon SQS dead-letter queues: <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html">https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html</a></li><li>Amazon ECS service auto scaling: <a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-auto-scaling.html">https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-auto-scaling.html</a></li><li>Configuring OpenID Connect in AWS (GitHub Docs): <a href="https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services">https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services</a></li><li>Validating GitHub webhook deliveries: <a href="https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries">https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=02958ddc6ba7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[From Aurora to DynamoDB: Cutting Database Costs by 90% with DynamoDB and S3]]></title>
            <link>https://medium.com/@butkovic/from-aurora-to-dynamodb-cutting-database-costs-by-90-with-dynamodb-and-s3-e5bf2999a463?source=rss-74fc69b20a6f------2</link>
            <guid isPermaLink="false">https://medium.com/p/e5bf2999a463</guid>
            <category><![CDATA[aurora]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[s3]]></category>
            <category><![CDATA[migration]]></category>
            <category><![CDATA[dynamodb]]></category>
            <dc:creator><![CDATA[Peter Butkovic]]></dc:creator>
            <pubDate>Mon, 19 Oct 2026 08:06:07 GMT</pubDate>
            <atom:updated>2026-10-19T08:09:44.996Z</atom:updated>
            <content:encoded><![CDATA[<p><em>How we moved a set of Spring Boot microservices off Amazon Aurora PostgreSQL, guided by the AWS Well-Architected Framework, and what we learned along the way.</em></p><h3>The setup</h3><p>As part of a long-term client engagement, I was responsible for the design, delivery and ongoing operation of a system that generates business-critical sales documents (PDFs) through a REST API (OpenAPI). It ran as Spring Boot microservices on Amazon ECS (Fargate) in private subnets. API Gateway sat in front, with PrivateLink and Network Load Balancers behind it. Supporting services included S3, SQS, Secrets Manager, KMS, AWS Systems Manager Parameter Store and Amazon CloudWatch.</p><p>All persistence went to <strong>Amazon Aurora (PostgreSQL-compatible)</strong>. When we looked at how the data was used, we found:</p><ul><li>Most of it was <strong>key-value</strong> data.</li><li>Large objects such as integration responses and generated binaries (Base64-encoded) were stored in TEXT columns.</li></ul><p>In other words, we were using a relational database as an expensive key-value store with blobs attached. Starting from the access patterns rather than from the schema is also the first step AWS recommends for <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html">NoSQL design in DynamoDB</a>.</p><h3>Original setup</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JQc8z9FJerwAqHk6HKGjrg.png" /></figure><p><em>Before the migration: Aurora held both application data and the Quartz JDBC job store.</em></p><h3>Why we migrated</h3><p>No single event forced the move. The motivation came from the database upgrades we had already been through, and from knowing more of them would follow:</p><ul><li><strong>PostgreSQL 10.x reached end of life</strong>, so we had to do a major engine upgrade to 11.x. Aurora publishes these dates in the <a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraPostgreSQLReleaseNotes/aurorapostgresql-release-calendar.html">Aurora PostgreSQL release calendar</a>, so the next one was always coming.</li><li><a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless.html"><strong>Aurora Serverless v1</strong></a><strong> was retired</strong>, so we had to move to Serverless v2 or Provisioned. For our load, Provisioned (db.t4g.medium for DEV, db.t4g.large for INT/PROD) came out cheaper than Serverless v2.</li><li><strong>Serverless v2 couldn’t scale to zero</strong> at the time, unlike v1. That made the low-traffic DEV and INT environments costly.</li><li><strong>Each upgrade meant downtime and manual steps</strong>, both for the major version upgrade and for the move from Serverless v1 to Provisioned.</li></ul><p>Each upgrade cost us effort and downtime but didn’t make the system any better. Since the next upgrade would only be a matter of time, we asked whether it should be a relational database at all.</p><h3>Choosing a migration strategy</h3><p>AWS describes seven common migration strategies, the “7 Rs”: <strong>retire, retain, rehost, relocate, repurchase, replatform and refactor</strong>. They range from “leave it as is” to “redesign it for the cloud”:</p><ul><li><strong>Rehost</strong> (“lift and shift”): move the workload unchanged. Fastest, lowest risk, fewest cloud benefits.</li><li><strong>Replatform</strong> (“lift and reshape”): small optimizations without changing the core architecture, e.g. moving a self-managed database to a managed one.</li><li><strong>Refactor / re-architect</strong>: change the architecture and code to use cloud-native services. Most effort, biggest payoff in cost, scalability and operations.</li></ul><p>A common pattern, also recommended in AWS guidance, is <strong>migrate first, modernize later</strong>. Rehost or replatform to get into the cloud quickly, then refactor once the team knows the workload and its real access patterns in production.</p><p>That is the path the system followed. It had already been moved to AWS with a managed relational database. Several years of production data then showed how the data was actually used: mostly key-value. Instead of repeating the upgrade cycle, we picked <strong>refactor</strong>: we replaced the relational model with DynamoDB and S3, and dropped custom code where managed features could do the job.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*2VIw7Vwt6MgIsKcCQzJWLg.png" /></figure><h3>Aurora PostgreSQL vs DynamoDB</h3><p>AWS recommends choosing a database by data model and access pattern, not by habit (see the <a href="https://docs.aws.amazon.com/decision-guides/latest/databases-on-aws-how-to-choose/databases-on-aws-how-to-choose.html">AWS decision guide for choosing a database</a>). For our workload the comparison looked like this:</p><p>FeatureAurora PostgreSQLDynamoDBTypeRelational (SQL)NoSQL (key-value &amp; document)SchemaFixedSchema-lessScalingVertical, plus read replicasAutomatic horizontal scalingStrengthACID, complex queriesHigh-throughput key-based accessCost modelPay per instance (provisioned/serverless)Pay per requestManagementScaling and upgrades are your jobFully managed, serverless</p><h3>Deciding with the Well-Architected Framework</h3><p>We didn’t want to pick on gut feeling, so we scored the decision against the <a href="https://aws.amazon.com/architecture/well-architected/">AWS Well-Architected Framework</a> pillars.</p><p><a href="https://docs.aws.amazon.com/wellarchitected/latest/cost-optimization-pillar/welcome.html"><strong>Cost Optimization</strong></a><strong>.</strong> <em>“Adopt a consumption model”</em> and <em>“Measure overall efficiency.”</em> DynamoDB on-demand costs almost nothing when idle, while our Aurora instances ran all the time. DEV and INT saw very little traffic.</p><p><a href="https://docs.aws.amazon.com/wellarchitected/latest/performance-efficiency-pillar/welcome.html"><strong>Performance Efficiency</strong></a><strong>.</strong> <em>“Democratize advanced technologies”</em> and <em>“Consider mechanical sympathy”</em>, which explicitly says to consider data access patterns when selecting a database. DynamoDB scaled read and write capacity with demand automatically.</p><p><strong>Operational Excellence and </strong><a href="https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/welcome.html"><strong>Security</strong></a><strong>.</strong> <em>“Automate security best practices”</em> and <em>“Keep people away from data.”</em> Access went entirely through IAM. ECS tasks assumed a role, so there were no database credentials to rotate. Read-only users could browse data in the AWS Console under IAM policies.</p><p><a href="https://docs.aws.amazon.com/wellarchitected/latest/sustainability-pillar/sustainability-pillar.html"><strong>Sustainability</strong></a><strong>.</strong> <em>“Maximize utilization”</em> and <em>“Use managed services.”</em> A managed, consumption-based service has no idle instances burning energy.</p><p><a href="https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/welcome.html"><strong>Reliability</strong></a><strong>.</strong> We no longer needed maintenance windows for major engine upgrades.</p><h3>The numbers</h3><p>Aurora made up <strong>22% of the whole AWS bill on PROD</strong>. Our estimate for the same workload on DynamoDB across DEV, INT and PROD came out about <strong>90% lower</strong> than the Aurora cost.</p><h3>Challenges and how we solved them</h3><h3>Updated setup</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jomZiRWd14rlHjgowSyCRA.png" /></figure><p><em>After the migration: DynamoDB stores metadata and S3 keys; S3 stores large payloads. TTL, lifecycle expiration, EventBridge and SQS replace the Quartz jobs.</em></p><h3>1. The 400 KB item size limit</h3><p>A DynamoDB item can’t exceed 400 KB, and some of our payloads were far bigger.</p><p><strong>Solution:</strong> we stored integration response payloads and binary content (images, PDFs) in <strong>Amazon S3</strong>, and kept only the metadata and the S3 key in DynamoDB. This is safe because <a href="https://aws.amazon.com/s3/consistency/">S3 provides strong read-after-write consistency</a>: an object is readable as soon as the write returns. This is the pattern AWS itself recommends in <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-use-s3-too.html">best practices for storing large items</a>. Writing to S3 first and DynamoDB second means a reader never finds a pointer to an object that doesn’t exist yet.</p><pre>// simplified: real code adds error handling and content-type<br>String key = &quot;payloads/&quot; + id;<br>s3.putObject(b -&gt; b.bucket(bucket).key(key), RequestBody.fromBytes(pdf));<br>dynamo.putItem(b -&gt; b.tableName(table).item(Map.of(<br>    &quot;id&quot;,     AttributeValue.fromS(id),<br>    &quot;s3Key&quot;,  AttributeValue.fromS(key),<br>    &quot;status&quot;, AttributeValue.fromS(&quot;GENERATED&quot;))));</pre><h4>DynamoDB or S3: what goes where</h4><p>The split wasn’t arbitrary. DynamoDB and S3 are built for different shapes of data:</p><p>AspectDynamoDBAmazon S3Unit of storageItem, max 400 KBObject, up to terabytesAccessBy key, plus secondary indexes and conditional updatesBy key or prefix onlyLatencySingle-digit millisecondsHigher per request, built for throughputStorage priceRoughly 10x more per GBRoughly 10x less per GBBest forSmall, frequently read and updated recordsLarge, write-once blobsExpiryTTL per itemLifecycle rules per prefix or tag</p><p>Prices are region-dependent. See <a href="https://aws.amazon.com/dynamodb/pricing/">DynamoDB pricing</a> and <a href="https://aws.amazon.com/s3/pricing/">S3 pricing</a>.</p><p>Our rule followed from that table. Anything we <strong>query or update</strong>, like IDs, status and timestamps, went to DynamoDB. Anything we only <strong>store and fetch whole</strong>, like PDFs, images and raw integration responses, went to S3. A PDF is written once and read occasionally. Keeping it in DynamoDB would mean paying premium per-GB storage and read capacity for bytes we never filter on. This is Well-Architected best practice <a href="https://docs.aws.amazon.com/wellarchitected/latest/performance-efficiency-pillar/perf_data_use_purpose_built_data_store.html">PERF03-BP01, use a purpose-built data store</a>. It asks you to document size, access pattern, latency and retention for each data type, and lists <em>“you stick to one data store because there is internal experience”</em> as an anti-pattern. That’s a good reminder that familiarity alone isn’t a reason to keep everything in one database.</p><p>AWS’s <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-use-s3-too.html">large-items guidance</a> also lists the costs of this pattern, and they’re worth planning for:</p><ul><li><strong>No transactions span DynamoDB and S3.</strong> Writing S3 first means a failure leaves, at worst, an orphaned object, never a dangling pointer. A <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html">lifecycle rule</a> or a periodic cleanup handles the orphans.</li><li><strong>Link back from the object.</strong> AWS suggests storing the item’s primary key as S3 object metadata, which makes maintenance and orphan detection easier.</li><li><strong>Two expiry mechanisms.</strong> If items expire with DynamoDB TTL, the matching objects need an S3 lifecycle rule with the same retention, or storage keeps growing.</li></ul><h3>2. SQL aggregations</h3><p>One service did advanced aggregations in SQL, and DynamoDB has no GROUP BY.</p><p><strong>Solution:</strong> the data set was small enough to fit in container memory, so we rewrote the aggregations in Java. This also made them <strong>faster</strong>, since in-memory processing beats a database round trip. For larger data sets this wouldn’t scale. There, AWS suggests <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-gsi-aggregation.html">materialized aggregations</a>, for example updated through DynamoDB Streams and AWS Lambda.</p><h3>3. Data migration</h3><p>We considered three options:</p><ul><li><a href="https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.DynamoDB.html">AWS Database Migration Service (DMS)</a>, which supports DynamoDB as a target</li><li>A custom Lambda function</li><li>A manual CSV export from PostgreSQL followed by an import into DynamoDB</li></ul><p><strong>We chose the manual CSV path</strong> because the data volume was small. The <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/S3DataImport.HowItWorks.html">DynamoDB import from S3</a> feature handles the CSV-to-table step without custom code. It doesn’t consume write capacity on the table. Note that it always creates a <em>new</em> table, so the table definition is part of the import rather than created up front. DMS would have been the right choice for large volumes, or for near-zero downtime with ongoing replication.</p><h3>4. Quartz scheduler with a SQL job store</h3><p>We used Quartz with a JDBC job store for three jobs. Without a SQL database, each one needed a new home.</p><p><strong>Periodic cleanup of data older than X days.</strong> We replaced this with <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html"><strong>DynamoDB TTL</strong></a> and <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html"><strong>S3 lifecycle expiration</strong></a>. No cleanup code was left to maintain. TTL deletes don’t consume write throughput. They aren’t instant, though: AWS documents that expired items are typically removed within a few days, so reads have to filter out items whose expiresAt is in the past.</p><pre>long expiresAt = Instant.now().plus(Duration.ofDays(retentionDays)).getEpochSecond();<br>item.put(&quot;expiresAt&quot;, AttributeValue.fromN(Long.toString(expiresAt)));</pre><pre>resource &quot;aws_dynamodb_table&quot; &quot;documents&quot; {<br>  name         = &quot;documents&quot;<br>  billing_mode = &quot;PAY_PER_REQUEST&quot;<br>  hash_key     = &quot;id&quot;<br>attribute {<br>    name = &quot;id&quot;<br>    type = &quot;S&quot;<br>  }<br>  ttl {<br>    attribute_name = &quot;expiresAt&quot;<br>    enabled        = true<br>  }<br>}</pre><p><strong>Daily statistics exported to Amazon CloudWatch.</strong> We replaced the cron job with an <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html"><strong>Amazon EventBridge scheduled rule</strong></a>. The rule sent a message to SQS, and the service consumed it. Going through SQS rather than calling the service directly kept the trigger decoupled and retryable. For new projects AWS now recommends <a href="https://docs.aws.amazon.com/scheduler/latest/UserGuide/what-is-scheduler.html">Amazon EventBridge Scheduler</a> over scheduled rules.</p><p><strong>Detecting stuck business transactions.</strong> If PDF generation stalled, the client still had to be notified through its callback URL. We replaced the polling cron with an <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-delay-queues.html"><strong>SQS delay queue</strong></a>. When a transaction started, the service sent a delayed message. When the message became visible, the consumer checked whether the transaction had finished. If it hadn’t, the consumer fired the callback. There was no polling and no scheduler state. SQS delays are capped at 15 minutes. For longer timeouts, a one-time schedule in EventBridge Scheduler does the same job.</p><pre>sqs.sendMessage(b -&gt; b.queueUrl(timeoutQueueUrl)<br>    .messageBody(transactionId)<br>    .delaySeconds(900)); // SQS max delay is 15 minutes</pre><h3>Backups</h3><p>StrategyHow it worksBest forOn-demand backupManual or scheduled full snapshot, no performance impactRegular backups, snapshots before major changesPoint-in-time recovery (PITR)Continuous; restore to any second in the last 35 daysAccidental deletes or writes</p><p>We went with <strong>scheduled daily on-demand backups</strong> (via <a href="https://aws.amazon.com/backup/">AWS Backup</a>), which matched the existing backup policy. This follows the Reliability pillar best practice <a href="https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/back-up-data.html">REL09-BP03, perform data backup automatically</a>. The same section adds REL09-BP04: restore backups periodically to verify them. Turning on <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Point-in-time-recovery.html">PITR</a> is the next step if the recovery point objective tightens.</p><p><strong>Bonus:</strong> Aurora backed up the <em>whole cluster</em>, temporary data included. With DynamoDB, backups are configured <strong>per table</strong>. We didn’t back up tables whose data could be rebuilt from external systems, which meant less storage and lower cost.</p><h3>Results</h3><ul><li>We migrated all three microservices to DynamoDB and S3 in PROD.</li><li>Data migration took <strong>a couple of hours</strong> rather than the expected one hour or less.</li><li>Along the way we <strong>removed deprecated data and simplified the code</strong>.</li></ul><h3>Refactoring, not just a datastore swap</h3><p>We redesigned the data access around key-value lookups and S3 instead of swapping the database driver. To check that the refactor didn’t trade cost for performance, we measured before and after:</p><ul><li><strong>Same scenario, same environment.</strong> We ran an identical load test against the old and new versions, so the only variable was the data layer.</li><li><strong>Percentiles, not averages.</strong> We compared median, 95th percentile and max response times. Tail latency is what users notice.</li><li><strong>Repeatable.</strong> The test was scripted, so we reran it after each microservice migration.</li></ul><p>This followed the Performance Efficiency pillar of the Well-Architected Framework, which recommends using load testing and metrics to choose and validate architecture changes.</p><h3>Lessons learned</h3><ol><li><strong>DynamoDB fits key-value access well.</strong> If your SQL tables are mostly SELECT * WHERE id = ?, you&#39;re paying for features you don&#39;t use.</li><li><strong>Put files in S3, not in the database.</strong> Strong read-after-write consistency makes the S3-pointer pattern safe.</li><li><strong>Make the business case explicit.</strong> The migration took a couple of months of effort. Weighing short-term engineering cost against long-term AWS bill savings is what got it approved.</li><li><strong>Write tests first.</strong> Unit and integration tests caught regressions throughout the rewrite.</li><li><strong>Prefer managed features to custom code.</strong> TTL, EventBridge and SQS delay queues replaced scheduler code that we used to maintain ourselves. Find the right balance between custom implementation and what the cloud already provides.</li></ol><h3>References</h3><ul><li>AWS Well-Architected Framework: <a href="https://aws.amazon.com/architecture/well-architected/">https://aws.amazon.com/architecture/well-architected/</a></li><li>AWS Prescriptive Guidance, migration strategies (7 Rs): <a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/large-migrations/migration-strategies.html">https://docs.aws.amazon.com/prescriptive-guidance/latest/large-migrations/migration-strategies.html</a></li><li>AWS blog, “6 Strategies for Migrating Applications to the Cloud”: <a href="https://aws.amazon.com/blogs/enterprise-strategy/6-strategies-for-migrating-applications-to-the-cloud/">https://aws.amazon.com/blogs/enterprise-strategy/6-strategies-for-migrating-applications-to-the-cloud/</a></li><li>Well-Architected Performance Efficiency pillar: <a href="https://docs.aws.amazon.com/wellarchitected/latest/performance-efficiency-pillar/welcome.html">https://docs.aws.amazon.com/wellarchitected/latest/performance-efficiency-pillar/welcome.html</a></li><li>Distributed Load Testing on AWS: <a href="https://aws.amazon.com/solutions/implementations/distributed-load-testing-on-aws/">https://aws.amazon.com/solutions/implementations/distributed-load-testing-on-aws/</a></li><li>Amazon DynamoDB: <a href="https://aws.amazon.com/dynamodb/">https://aws.amazon.com/dynamodb/</a></li><li>NoSQL design for DynamoDB: <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html">https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html</a></li><li>Best practices for storing large items: <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-use-s3-too.html">https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-use-s3-too.html</a></li><li>Choosing an AWS database service: <a href="https://docs.aws.amazon.com/decision-guides/latest/databases-on-aws-how-to-choose/databases-on-aws-how-to-choose.html">https://docs.aws.amazon.com/decision-guides/latest/databases-on-aws-how-to-choose/databases-on-aws-how-to-choose.html</a></li><li>Aurora Serverless v1: <a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless.html">https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless.html</a></li><li>Aurora PostgreSQL release calendar: <a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraPostgreSQLReleaseNotes/aurorapostgresql-release-calendar.html">https://docs.aws.amazon.com/AmazonRDS/latest/AuroraPostgreSQLReleaseNotes/aurorapostgresql-release-calendar.html</a></li><li>AWS DMS with DynamoDB as target: <a href="https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.DynamoDB.html">https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.DynamoDB.html</a></li><li>Reliability pillar, back up data: <a href="https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/back-up-data.html">https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/back-up-data.html</a></li><li>Amazon EventBridge Scheduler: <a href="https://docs.aws.amazon.com/scheduler/latest/UserGuide/what-is-scheduler.html">https://docs.aws.amazon.com/scheduler/latest/UserGuide/what-is-scheduler.html</a></li><li>DynamoDB service quotas (400 KB item limit): <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ServiceQuotas.html">https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ServiceQuotas.html</a></li><li>S3 strong consistency: <a href="https://aws.amazon.com/s3/consistency/">https://aws.amazon.com/s3/consistency/</a></li><li>PERF03-BP01, purpose-built data store: <a href="https://docs.aws.amazon.com/wellarchitected/latest/performance-efficiency-pillar/perf_data_use_purpose_built_data_store.html">https://docs.aws.amazon.com/wellarchitected/latest/performance-efficiency-pillar/perf_data_use_purpose_built_data_store.html</a></li><li>S3 lifecycle management: <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html">https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html</a></li><li>DynamoDB pricing: <a href="https://aws.amazon.com/dynamodb/pricing/">https://aws.amazon.com/dynamodb/pricing/</a></li><li>S3 pricing: <a href="https://aws.amazon.com/s3/pricing/">https://aws.amazon.com/s3/pricing/</a></li><li>DynamoDB import from S3: <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/S3DataImport.HowItWorks.html">https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/S3DataImport.HowItWorks.html</a></li><li>Amazon SQS delay queues: <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-delay-queues.html">https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-delay-queues.html</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e5bf2999a463" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS SAM based Lambda development with Localstack (on MacOs and Podman)]]></title>
            <link>https://medium.com/@butkovic/aws-sam-based-lambda-development-with-localstack-on-macos-and-podman-1471de10598e?source=rss-74fc69b20a6f------2</link>
            <guid isPermaLink="false">https://medium.com/p/1471de10598e</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[podman]]></category>
            <category><![CDATA[localstack]]></category>
            <dc:creator><![CDATA[Peter Butkovic]]></dc:creator>
            <pubDate>Fri, 04 Nov 2022 09:24:53 GMT</pubDate>
            <atom:updated>2022-11-04T09:25:58.905Z</atom:updated>
            <content:encoded><![CDATA[<h3>Overview</h3><p>Serverless is a hot topic in the cloud world. AWS primary answer to serverless computing was <a href="https://aws.amazon.com/lambda/">Lambda</a>. And the recommended way to develop AWS Lambda is the usage of the <a href="https://aws.amazon.com/serverless/sam/">AWS Serverless Application Model</a> (a.k.a SAM).</p><p>Local development of the Lambda functions using SAM and <a href="https://localstack.cloud/">Localstack</a> is not streightforward. For my case is the additional complexity is introduced by the <a href="https://podman.io/">Podman</a> usage on MacOs.</p><p>The aim of this post is to give you an overview of the needed steps that you might want to follow for you very own lambda development adventures.</p><p>Enjoy.</p><h3>Demo application architecture</h3><p>Demo application architecture is depicted on the following image:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/718/0*DuC4j72769jsKyi_.png" /><figcaption>Image source: <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-dynamo-db.html">https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-dynamo-db.html</a></figcaption></figure><h3>Github sources</h3><p>Github repo with initialized project is available at:</p><p><a href="https://github.com/typekpb/blog-sam-app-demo">GitHub - typekpb/blog-sam-app-demo</a></p><p>Please note, it contains the bootstraped project adapted for the localstack/AWS Cloud usage.</p><h3>Install the tooling</h3><ol><li><strong>Install</strong> the <strong>AWS SAM cli</strong> on MacOS via Homebrew (as per <a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install-mac.html">official instructions</a>):</li></ol><pre>brew tap aws/tap<br>brew install aws-sam-cli</pre><p><strong>2. Init + start </strong>the <strong>podman machine</strong> for the SAM usage (following <a href="https://quarkus.io/guides/podman">the instructions</a>):</p><pre>sudo /opt/homebrew/Cellar/podman/*/bin/podman-mac-helper install<br>podman machine init --cpus=4 --disk-size=60 --memory=8096 -v $HOME:$HOME<br>podman machine set --rootful<br>podman machine start<br>alias docker=&#39;podman&#39;</pre><p><strong>3. Setup</strong> <strong>localstack AWS profile </strong>locally:</p><pre>aws configure --profile localstack</pre><pre>AWS Access Key ID [None]: dummy<br>AWS Secret Access Key [None]: dummy<br>Default region name [None]: eu-central-1<br>Default output format [None]: json</pre><p><strong>4. AWS SAM local cli</strong> <strong>installation</strong> is required, until the issue: <a href="https://github.com/aws/aws-sam-cli/issues/1194">https://github.com/aws/aws-sam-cli/issues/1194</a> would be resolved.</p><p>To do so use pip (as per <a href="https://github.com/localstack/aws-sam-cli-local">official instructions</a>):</p><pre>pip3 install aws-sam-cli-local</pre><p>Install NoSQL Workbench as DynamoDB client (via <a href="https://formulae.brew.sh/cask/nosql-workbench">Homebrew instructions</a>):</p><pre>brew install --cask nosql-workbench</pre><h3>Run localstack</h3><p>Clone the git repository of the sample app and run localstack via podman-compose:</p><pre>git clone <a href="https://github.com/typekpb/blog-sam-app-demo.git">https://github.com/typekpb/blog-sam-app-demo.git</a><br>cd blog-sam-app-demo/<br>podman-compose down &amp;&amp; podman-compose up</pre><p>The <a href="https://github.com/typekpb/blog-sam-app-demo/blob/main/docker-compose.yml">docker-compose.yaml</a> file contents follow:</p><pre>version: &quot;3.7&quot;<br>services:<br>  localstack:<br>    image: localstack/localstack:latest<br>    environment:<br>      - AWS_DEFAULT_REGION=eu-central-1<br>      - EDGE_PORT=4566<br>    ports:<br>      - &#39;4566:4566&#39;</pre><h3>Bootstrap the SAM project</h3><p>The following options are to be taken during the guided init process:</p><pre>sam init</pre><pre>You can preselect a particular runtime or package type when using the `sam init` experience.<br>Call `sam init --help` to learn more.</pre><pre>Which template source would you like to use?<br>        1 - AWS Quick Start Templates<br>        2 - Custom Template Location<br>Choice: 1</pre><pre>Choose an AWS Quick Start application template<br>        1 - Hello World Example<br>        2 - Multi-step workflow<br>        3 - Serverless API<br>        4 - Scheduled task<br>        5 - Standalone function<br>        6 - Data processing<br>        7 - Infrastructure event management<br>        8 - Serverless Connector Hello World Example<br>        9 - Multi-step workflow with Connectors<br>        10 - Lambda EFS example<br>        11 - Machine Learning<br>Template: 3</pre><pre>Which runtime would you like to use?<br>        1 - dotnet6<br>        2 - dotnetcore3.1<br>        3 - nodejs16.x<br>        4 - nodejs14.x<br>        5 - nodejs12.x<br>Runtime: 3</pre><pre>Based on your selections, the only Package type available is Zip.<br>We will proceed to selecting the Package type as Zip.</pre><pre>Based on your selections, the only dependency manager available is npm.<br>We will proceed copying the template using npm.</pre><pre>Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]:</pre><pre>Project name [sam-app]: sam-app-demo</pre><pre>Cloning from <a href="https://github.com/aws/aws-sam-cli-app-templates">https://github.com/aws/aws-sam-cli-app-templates</a> (process may take a moment)</pre><pre>-----------------------<br>    Generating application:<br>    -----------------------<br>    Name: sam-app-demo<br>    Runtime: nodejs16.x<br>    Architectures: x86_64<br>    Dependency Manager: npm<br>    Application Template: quick-start-web<br>    Output Directory: .</pre><pre>Next steps can be found in the README file at ./sam-app-demo/README.md</pre><pre>Commands you can use next<br>    =========================<br>    [*] Create pipeline: cd sam-app-demo &amp;&amp; sam pipeline init --bootstrap<br>    [*] Validate SAM template: cd sam-app-demo &amp;&amp; sam validate<br>    [*] Test Function in the Cloud: cd sam-app-demo &amp;&amp; sam sync --stack-name {stack-name} --watch</pre><h3>Adapt generated sources</h3><p>Following were the areas I had to update the generated sources:</p><ul><li>template.yaml — to have it running on localstack</li><li>lambda js files — to enable local dynamodb usage (introducing custom env var: LOCAL_DYNAMODB as well as some enhancements to error logging</li></ul><p>The documented version is available in the referred github repo.</p><h3>Build the project</h3><p>Change to project dir:</p><pre>cd sam-app-demo</pre><p>and build the project itself:</p><pre>sam build<br>Building codeuri: .../sam-app-demo runtime: nodejs16.x metadata: {} architecture: x86_64 functions: getAllItemsFunction, getByIdFunction, putItemFunction<br>Running NodejsNpmBuilder:NpmPack<br>Running NodejsNpmBuilder:CopyNpmrcAndLockfile<br>Running NodejsNpmBuilder:CopySource<br>Running NodejsNpmBuilder:NpmInstall<br>Running NodejsNpmBuilder:CleanUpNpmrc<br>Running NodejsNpmBuilder:LockfileCleanUp</pre><pre>Build Succeeded</pre><pre>Built Artifacts  : .aws-sam/build<br>Built Template   : .aws-sam/build/template.yaml</pre><pre>Commands you can use next<br>=========================<br>[*] Validate SAM template: sam validate<br>[*] Invoke Function: sam local invoke<br>[*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch<br>[*] Deploy: sam deploy --guided</pre><h3>Option 1 (WIP): Running full stack in Localstack</h3><p>This option provisions all the needed resources in the localstack.</p><p><strong>Unfortunatelly, this way is not working as expected</strong> as of now. I didn’t convince Lambda to call DynamoDB within the localstack yet (despite following the <a href="https://stackoverflow.com/questions/57420137/unrecognizedclientexception-when-invoking-a-lambda-from-another-lambda-using-loc">advices</a>, post should be updated, once this works as expected).</p><p>For local testing, the deploy to localstack is needed (which takes care of the all the needed resources). Run the following command to deploy (e.g. to region eu-central-1):</p><pre>samlocal deploy --resolve-s3 --stack-name sam-app-demo --capabilities CAPABILITY_IAM --no-fail-on-empty-changeset --region eu-central-1<br>        Creating the required resources...<br>        Successfully created!</pre><pre>Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-eb5effb8<br>                A different default S3 bucket can be set in samconfig.toml<br>                Or by specifying --s3-bucket explicitly.<br>Uploading to 3acbdb74d3929867f649b7505c9d4f43  10614190 / 10614190  (100.00%)<br>File with same data already exists at 3acbdb74d3929867f649b7505c9d4f43, skipping upload<br>File with same data already exists at 3acbdb74d3929867f649b7505c9d4f43, skipping upload</pre><pre>Deploying with following values<br>        ===============================<br>        Stack name                   : sam-app-demo<br>        Region                       : eu-central-1<br>        Confirm changeset            : False<br>        Disable rollback             : False<br>        Deployment s3 bucket         : aws-sam-cli-managed-default-samclisourcebucket-eb5effb8<br>        Capabilities                 : [&quot;CAPABILITY_IAM&quot;]<br>        Parameter overrides          : {}<br>        Signing Profiles             : {}</pre><pre>Initiating deployment<br>=====================<br>Uploading to 22e245ed08e7e1c6d8e4197df4c59a84.template  2969 / 2969  (100.00%)</pre><pre>Waiting for changeset to be created..<br>CloudFormation stack changeset<br>---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------<br>Operation                                    LogicalResourceId                            ResourceType                                 Replacement<br>---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------<br>+ Add                                        putItemFunctionApiPermissionProd             AWS::Lambda::Permission                      N/A<br>+ Add                                        putItemFunction                              AWS::Lambda::Function                        N/A<br>+ Add                                        getAllItemsFunction                          AWS::Lambda::Function                        N/A<br>+ Add                                        getByIdFunctionRole                          AWS::IAM::Role                               N/A<br>+ Add                                        SampleTable                                  AWS::DynamoDB::Table                         N/A<br>+ Add                                        getAllItemsFunctionApiPermissionProd         AWS::Lambda::Permission                      N/A<br>+ Add                                        getAllItemsFunctionRole                      AWS::IAM::Role                               N/A<br>+ Add                                        putItemFunctionRole                          AWS::IAM::Role                               N/A<br>+ Add                                        getByIdFunction                              AWS::Lambda::Function                        N/A<br>+ Add                                        ServerlessRestApiProdStage                   AWS::ApiGateway::Stage                       N/A<br>+ Add                                        ServerlessRestApiDeploymenta4d359a69a        AWS::ApiGateway::Deployment                  N/A<br>+ Add                                        ServerlessRestApi                            AWS::ApiGateway::RestApi                     N/A<br>+ Add                                        getByIdFunctionApiPermissionProd             AWS::Lambda::Permission                      N/A<br>---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------</pre><pre>Changeset created successfully. arn:aws:cloudformation:eu-central-1:000000000000:changeSet/samcli-deploy1667488518/14b1b688</pre><pre>2022-11-03 16:15:18 - Waiting for stack create/update to complete</pre><pre>CloudFormation events from stack operations (refresh every 0.5 seconds)<br>---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------<br>ResourceStatus                               ResourceType                                 LogicalResourceId                            ResourceStatusReason<br>---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------<br>REVIEW_IN_PROGRESS                           AWS::CloudFormation::Stack                   sam-app-demo                                 -<br>CREATE_IN_PROGRESS                           AWS::CloudFormation::Stack                   sam-app-demo                                 -<br>CREATE_COMPLETE                              AWS::CloudFormation::Stack                   SampleTable                                  -<br>CREATE_COMPLETE                              AWS::CloudFormation::Stack                   getAllItemsFunctionRole                      -<br>CREATE_COMPLETE                              AWS::CloudFormation::Stack                   putItemFunctionRole                          -<br>CREATE_COMPLETE                              AWS::CloudFormation::Stack                   ServerlessRestApi                            -<br>CREATE_COMPLETE                              AWS::CloudFormation::Stack                   putItemFunction                              -<br>CREATE_COMPLETE                              AWS::CloudFormation::Stack                   getAllItemsFunction                          -<br>CREATE_COMPLETE                              AWS::CloudFormation::Stack                   getByIdFunctionRole                          -<br>CREATE_COMPLETE                              AWS::CloudFormation::Stack                   getAllItemsFunctionApiPermissionProd         -<br>CREATE_COMPLETE                              AWS::CloudFormation::Stack                   getByIdFunction                              -<br>CREATE_COMPLETE                              AWS::CloudFormation::Stack                   ServerlessRestApiDeploymenta4d359a69a        -<br>CREATE_COMPLETE                              AWS::CloudFormation::Stack                   getByIdFunctionApiPermissionProd             -<br>CREATE_COMPLETE                              AWS::CloudFormation::Stack                   putItemFunctionApiPermissionProd             -<br>CREATE_COMPLETE                              AWS::CloudFormation::Stack                   ServerlessRestApiProdStage                   -<br>CREATE_COMPLETE                              AWS::CloudFormation::Stack                   sam-app-demo                                 -<br>---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------<br>CloudFormation outputs from deployed stack<br>----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------<br>Outputs<br>----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------<br>Key                 WebEndpoint<br>Description         API Gateway endpoint URL for Prod stage<br>Value               <a href="https://jm9nvoft0x.execute-api.amazonaws.com:4566/Prod/">https://jm9nvoft0x.execute-api.amazonaws.com:4566/Prod/</a><br>----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------</pre><pre>Successfully created/updated stack - sam-app-demo in eu-central-1</pre><p>Please note, the API Gateway URL is shown as the Value output. In my case it was: <a href="https://jm9nvoft0x.execute-api.amazonaws.com:4566/Prod/">https://jm9nvoft0x.execute-api.amazonaws.com:4566/Prod</a>/</p><p>As per <a href="https://docs.localstack.cloud/aws/apigatewayv2/#accessing-http-apis-via-local-domain-name">official docs</a>, that state that the URL format available is: http://localhost:4566/restapis/<strong>&lt;apiId&gt;</strong>/<strong>&lt;stageId&gt;</strong>/_user_request_/<strong>&lt;path&gt;</strong></p><p>I can now test running Lambda locally using e.g. curl command (as mentioned above, I’m facing error here and need deeper investigation on the topic):</p><pre>curl <a href="http://127.0.0.1:4566/restapis/jm9nvoft0x/Prod/_user_request_/">http://127.0.0.1:4566/restapis/jm9nvoft0x/Prod/_user_request_/</a><br>{&quot;message&quot;:&quot;The security token included in the request is invalid.&quot;,&quot;code&quot;:&quot;UnrecognizedClientException&quot;,&quot;time&quot;:&quot;2022-11-04T09:11:20.983Z&quot;,&quot;requestId&quot;:&quot;2993H6FK7HRL42GIVAVIENCQ4NVV4KQNSO5AEMVJF66Q9ASUAAJG&quot;,&quot;statusCode&quot;:400,&quot;retryable&quot;:false,&quot;retryDelay&quot;:36.22586241543885}</pre><p>You can use the aws cli to execute the commands, e.g. to see the cloudformation stacks, use:</p><pre>aws --no-sign-request --endpoint-url=<a href="http://localhost:4566">http://localhost:4566</a> --region eu-central-1 cloudformation list-stacks</pre><h3>Option 2: Running using SAM CLI locally (with DynamoDB only in Localstack)</h3><p>As a prerequisite, we need to provision the DynamoDB in the Localstack. As there seems to be no simple way to provision only a subset of resources (namely DynamoDB only) via SAM cli, let’s take the lazy way and deploy everything. We’ll run the same command we referred to in the previous section:</p><pre>samlocal deploy --resolve-s3 --stack-name sam-app-demo --capabilities CAPABILITY_IAM --no-fail-on-empty-changeset --region eu-central-1</pre><p>Set the DynamoDB table name in the env.json file, in my case the updated contents is:</p><pre>{<br>  &quot;getAllItemsFunction&quot;: {<br>    &quot;SAMPLE_TABLE&quot;: &quot;sam-app-demo-SampleTable-6100dad0&quot;,<br>    &quot;LOCAL_DYNAMODB&quot;: true<br>  },<br>  &quot;getByIdFunction&quot;: {<br>    &quot;SAMPLE_TABLE&quot;: &quot;sam-app-demo-SampleTable-6100dad0&quot;,<br>    &quot;LOCAL_DYNAMODB&quot;: true<br>  },<br>  &quot;putItemFunction&quot;: {<br>    &quot;SAMPLE_TABLE&quot;: &quot;sam-app-demo-SampleTable-6100dad0&quot;,<br>    &quot;LOCAL_DYNAMODB&quot;: true<br>  },<br>}</pre><p>Then start the API locally, by running:</p><pre>sam local start-api --env-vars ./env.json<br>Mounting getAllItemsFunction at <a href="http://127.0.0.1:3000/">http://127.0.0.1:3000/</a> [GET]<br>Mounting getByIdFunction at <a href="http://127.0.0.1:3000/{id">http://127.0.0.1:3000/{id</a>} [GET]<br>Mounting putItemFunction at <a href="http://127.0.0.1:3000/">http://127.0.0.1:3000/</a> [POST]<br>You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template<br>2022-11-03 13:35:57  * Running on <a href="http://127.0.0.1:3000/">http://127.0.0.1:3000/</a> (Press CTRL+C to quit)</pre><p>Afterwards, feel free to use curl for accessing the APIs (as advised in the start-api output). To retrieve all the DyanmoDB items or specific one run:</p><pre>curl <a href="http://127.0.0.1:3000/">http://127.0.0.1:3000/</a><br></pre><p>To insert new entry to DynamoDB, run:</p><pre>curl -X POST -H &quot;Content-Type: application/json&quot; -d &quot;{\&quot;id\&quot;: \&quot;id1\&quot;,\&quot;name\&quot;: \&quot;name1\&quot;}&quot; localhost:3000/</pre><p>And fetch it afterwards all:</p><pre>curl localhost:3000<br>[{&quot;name&quot;:&quot;name1&quot;,&quot;id&quot;:&quot;id1&quot;}]</pre><p>or specific one only:</p><pre>curl <a href="http://127.0.0.1:3000/id1">http://127.0.0.1:3000/id1</a><br>{&quot;name&quot;:&quot;name1&quot;,&quot;id&quot;:&quot;id1&quot;}</pre><p>Please note, this option also supports remote debugging, once using flag -d &lt;port_number&gt; (e.g. for remote debuggin port 5858):</p><pre>sam local start-api -d 5858 --env-vars ./env.json</pre><h3>Option 3: Running in AWS Cloud</h3><ol><li>Build using command:</li></ol><pre>sam build</pre><p>2. Deploy the stack to AWS account (assuming your profile is configured with name: sam-app-demo and you deploy to eu-central-1 region):</p><pre>sam deploy --profile sam-app-demo --resolve-s3 --stack-name sam-app-demo --capabilities CAPABILITY_IAM --no-fail-on-empty-changeset --region eu-central-1</pre><p>3. To test that endpoint works:</p><pre>curl <a href="https://8vedwvy560.execute-api.eu-central-1.amazonaws.com/Prod/">https://&lt;api id&gt;.execute-api.eu-central-1.amazonaws.com/Prod/</a><br></pre><h3>Connecting to DynamoDB running Localstack from NoSQL Workbench</h3><ol><li>Open NoSQL Workbench</li><li>In the Operation builder click: +Add connection button</li><li>Switch to DynamoDB local tab</li><li>Fill out the Connection name and Port number (4566 in our case), see:</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Kh2LeXtOOzgba4HUbmOzeQ.png" /></figure><p>Contents of the table sam-app-demo-SampleTable-* are shown afterwards:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kY9pdbPIdHsIK5NoUlkrmQ.png" /></figure><h3>(Optional) Generating OpenAPI definitions</h3><p>OpenAPI is these days a standard for the documentation of the REST APIs.</p><p>As the <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-export-api.html">API Gateway is capable of generating it automatically</a>, let’s go for it, by running (please replace &lt;api-id&gt; with value relevant for your API):</p><pre>aws apigateway get-export --parameters extensions=&#39;apigateway&#39; --rest-api-id &lt;api-id&gt; --stage-name Prod --export-type oas30 --profile sam-app-demo oas30.json</pre><p>The output is stored in local file: oas30.json. Please note, this option doesn’t work in the localstack, so you’d need AWS Cloud env to make it happen.</p><h3>Conclusion</h3><p>SAM offers an amazing tooling for the Lambda development and the related resources provisioning. It took me a while to find the way to overcome the issues for the local setup, so I hope you find the sum-up useful for your Lambda jump-start.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1471de10598e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Visualizing circuit breaker behaviour with kiali addon for Istio]]></title>
            <link>https://medium.com/@butkovic/visualizing-istios-circuit-breaker-with-kiali-cee45eaabc4d?source=rss-74fc69b20a6f------2</link>
            <guid isPermaLink="false">https://medium.com/p/cee45eaabc4d</guid>
            <category><![CDATA[istio]]></category>
            <category><![CDATA[kind]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[circuit-breaker]]></category>
            <category><![CDATA[kiali]]></category>
            <dc:creator><![CDATA[Peter Butkovic]]></dc:creator>
            <pubDate>Sun, 25 Sep 2022 11:48:36 GMT</pubDate>
            <atom:updated>2022-09-25T11:49:33.831Z</atom:updated>
            <content:encoded><![CDATA[<p>Let’s have a look at the option to play around with the circuit breaker pattern locally with the <a href="https://istio.io/">Istio</a> service mesh and <a href="https://kind.sigs.k8s.io/">kind</a> created kubernetes cluster. For the visual part we’ll use <a href="https://istio.io/latest/docs/tasks/observability/kiali/">kiali</a> addon.</p><p>As I’m running <a href="https://podman.io/">Podman</a>, whose setup I’ve documented in: <a href="https://medium.com/@butkovic/favoring-podman-over-docker-desktop-33368e031ba0">Favoring Podman over Docker Desktop</a>, I won’t describe that in more detail, except the Podman machine initialization arguments.</p><h3>The book</h3><p>Circuit breaker pattern itself is nicely described in the book: Release it! by Michael T. Nygard, which I can only recommend for reading.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/405/0*i0BcXBHe7d2Ejhnr.jpg" /><figcaption>Source: <a href="https://www.amazon.com/Release-Design-Deploy-Production-Ready-Software/dp/1680502395">https://www.amazon.com/Release-Design-Deploy-Production-Ready-Software/dp/1680502395</a></figcaption></figure><h3>Local environment</h3><p>Local environment setup consists of the information mostly documented on the: <a href="https://istio.io/latest/docs/setup/platform-setup/kind/">https://istio.io/latest/docs/setup/platform-setup/kind/</a>.</p><h3>Podman installation</h3><p>The process is described in my other Medium article: <a href="https://medium.com/@butkovic/favoring-podman-over-docker-desktop-33368e031ba0">Favoring Podman over Docker Desktop</a>. There is one step, that is specific to kind &amp; Istio setup, namely the Podman machine resources demand. Based on my experience following works:</p><pre>podman machine init --cpus=2 --disk-size=60 --memory=8192 -v $HOME:$HOME<br>podman machine start</pre><p>In case you’ve already started the podman machine, run the following commands as well:</p><pre>podman machine stop<br>podman machine rm</pre><h3>Istio installation</h3><p><a href="https://brew.sh/">Homebrew</a> is my favourite package manager for MacOS. Let’s use it to install istio:</p><pre>brew install istioctl</pre><h3>kubectl installation</h3><p>We’ll also need kubectl command line tool, install it with:</p><pre>brew install kubernetes-cli</pre><h3>Cluster Creation</h3><p>Following the <a href="https://istio.io/latest/docs/setup/platform-setup/kind/">official docs</a> for istio kind installation:</p><pre>kind create cluster --name istio-testing</pre><p>To setup the cluster config, run:</p><pre># to see the available contexts<br>kubectl config get-contexts<br>kubectl config use-context kind-istio-testing</pre><h3>Install Istio on k8s cluster</h3><pre>istioctl install --set profile=demo -y<br>kubectl label namespace default istio-injection=enabled</pre><h3>Install sample app</h3><p>For detailed instructions see: <a href="https://istio.io/latest/docs/tasks/traffic-management/circuit-breaking/#before-you-begin">https://istio.io/latest/docs/tasks/traffic-management/circuit-breaking/#before-you-begin</a></p><p>There is a <a href="https://github.com/istio/istio/tree/master/samples/httpbin">httpbin app</a> available at <a href="https://github.com/istio/istio">Istio GitHub</a>, so you’d need to clone it first:</p><pre>git clone <a href="https://github.com/istio/istio.git">https://github.com/istio/istio.git</a><br>cd istio<br>kubectl apply -f <a href="https://raw.githubusercontent.com/istio/istio/release-1.15/samples/httpbin/httpbin.yaml">samples/httpbin/httpbin.yaml</a></pre><p>Install circuit breaker via istio destination rule:</p><pre>kubectl apply -f - &lt;&lt;EOF<br>apiVersion: networking.istio.io/v1alpha3<br>kind: DestinationRule<br>metadata:<br>  name: httpbin<br>spec:<br>  host: httpbin<br>  trafficPolicy:<br>    connectionPool:<br>      tcp:<br>        <strong>maxConnections: 1<br></strong>      http:<br>        <strong>http1MaxPendingRequests: 1</strong><br>        maxRequestsPerConnection: 1<br>    outlierDetection:<br>      consecutive5xxErrors: 1<br>      interval: 1s<br>      baseEjectionTime: 3m<br>      maxEjectionPercent: 100<br>EOF</pre><p>As documented in official instructions:</p><blockquote>maxConnections: 1 and http1MaxPendingRequests: 1. These rules indicate that if you exceed more than one connection and request concurrently, you should see some failures when the istio-proxy opens the circuit for further requests and connections.</blockquote><p>Install client app:</p><pre>kubectl apply -f <a href="https://raw.githubusercontent.com/istio/istio/release-1.15/samples/httpbin/sample-client/fortio-deploy.yaml">samples/httpbin/sample-client/fortio-deploy.yaml</a></pre><h3>kiali dashboard</h3><p>To install it, run:</p><pre>kubectl apply -f samples/addons<br>kubectl rollout status deployment/kiali -n istio-system</pre><p>To open it in the browser, run:</p><pre>istioctl dashboard kiali</pre><h3>Test time: single call</h3><p>Let’s first run the single request and see how this affects stats in kiali:</p><pre>export FORTIO_POD=$(kubectl get pods -l app=fortio -o &#39;jsonpath={.items[0].metadata.name}&#39;)<br>kubectl exec &quot;$FORTIO_POD&quot; -c fortio -- /usr/bin/fortio curl -quiet<br> <a href="http://httpbin:8000/get">http://httpbin:8000/get</a></pre><p>In the Graph section (having last 5 mins set), we see the fortio pod contacting the httpbin service:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bUqSdFi5Jll850RqqXLkog.png" /></figure><p>We can see the traffic stats on bottom right from fortio by clicking the pod:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Gpl7BUv-FNmxQ05KR9ZzCA.png" /></figure><p>As well as much more detailed in Applications -&gt; Outbound Metrics view (of fortio):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Z7ZwYOHq_jV56WouwBT3JA.png" /></figure><h3>Test time: 3000 calls performed serially</h3><p>Now let’s see what happens when we test 3000 calls executed in a single thread:</p><pre>$ kubectl exec &quot;$FORTIO_POD&quot; -c fortio -- /usr/bin/fortio load -c 1 -qps 0 -n 3000 -loglevel Warning <a href="http://httpbin:8000/get">http://httpbin:8000/get</a></pre><pre>07:46:05 I logger.go:134&gt; Log level is now 3 Warning (was 2 Info)<br>Fortio 1.38.0 running at 0 queries per second, 2-&gt;2 procs, for 3000 calls: <a href="http://httpbin:8000/get">http://httpbin:8000/get</a><br>Starting at max qps with 1 thread(s) [gomax 2] for exactly 3000 calls (3000 per thread + 0)<br>Ended after 15.730691105s : 3000 calls. qps=190.71<br>Aggregated Function Time : count 3000 avg 0.0052430763 +/- 0.0009012 min 0.004083992 max 0.022384591 sum 15.7292288<br># range, mid point, percentile, count<br>&gt;= 0.00408399 &lt;= 0.005 , 0.004542 , 44.23, 1327<br>&gt; 0.005 &lt;= 0.006 , 0.0055 , 89.10, 1346<br>&gt; 0.006 &lt;= 0.007 , 0.0065 , 96.80, 231<br>&gt; 0.007 &lt;= 0.008 , 0.0075 , 98.73, 58<br>&gt; 0.008 &lt;= 0.009 , 0.0085 , 99.30, 17<br>&gt; 0.009 &lt;= 0.01 , 0.0095 , 99.63, 10<br>&gt; 0.01 &lt;= 0.011 , 0.0105 , 99.77, 4<br>&gt; 0.011 &lt;= 0.012 , 0.0115 , 99.83, 2<br>&gt; 0.012 &lt;= 0.014 , 0.013 , 99.87, 1<br>&gt; 0.014 &lt;= 0.016 , 0.015 , 99.90, 1<br>&gt; 0.016 &lt;= 0.018 , 0.017 , 99.93, 1<br>&gt; 0.018 &lt;= 0.02 , 0.019 , 99.97, 1<br>&gt; 0.02 &lt;= 0.0223846 , 0.0211923 , 100.00, 1<br># target 50% 0.00512853<br># target 75% 0.00568574<br># target 90% 0.00611688<br># target 99% 0.00847059<br># target 99.9% 0.016<br>Error cases : no data<br># Socket and IP used for each connection:<br>[0]   1 socket used, resolved to [10.96.157.177:8000] connection timing : count 1 avg 0.000122462 +/- 0 min 0.000122462 max 0.000122462 sum 0.000122462<br>Connection time (s) : count 1 avg 0.000122462 +/- 0 min 0.000122462 max 0.000122462 sum 0.000122462<br>Sockets used: 1 (for perfect keepalive, would be 1)<br>Uniform: false, Jitter: false<br>IP addresses distribution:<br>10.96.157.177:8000: 1<br><strong>Code 200 : 3000 (100.0 %)</strong><br>Response Header Sizes : count 3000 avg 230.00267 +/- 0.05157 min 230 max 231 sum 690008<br>Response Body/Total Sizes : count 3000 avg 824.00267 +/- 0.05157 min 824 max 825 sum 2472008<br>All done 3000 calls (plus 0 warmup) 5.243 ms avg, 190.7 qps</pre><p>We see from the log, that all passed with HTTP code 200 =&gt; OK.</p><p>Nothing unusual on a kiali graph:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EQ1P6YXE4rI_-gLaQisMhA.png" /></figure><h3>Test time: 3000 calls performed in 2 parallel threads</h3><p>Performing 3000 calls in 2 parallel threads:</p><pre>$ kubectl exec &quot;$FORTIO_POD&quot; -c fortio -- /usr/bin/fortio load -c 2 -qps 0 -n 3000 -loglevel Warning <a href="http://httpbin:8000/get">http://httpbin:8000/get</a></pre><pre>08:04:18 I logger.go:134&gt; Log level is now 3 Warning (was 2 Info)<br>Fortio 1.38.0 running at 0 queries per second, 2-&gt;2 procs, for 3000 calls: <a href="http://httpbin:8000/get">http://httpbin:8000/get</a><br>Starting at max qps with 2 thread(s) [gomax 2] for exactly 3000 calls (1500 per thread + 0)<br>08:04:18 W http_client.go:922&gt; [0] Non ok http code 503 (HTTP/1.1 503)<br>08:04:18 W http_client.go:922&gt; [0] Non ok http code 503 (HTTP/1.1 503)<br>08:04:18 W http_client.go:922&gt; [0] Non ok http code 503 (HTTP/1.1 503)<br>08:04:18 W http_client.go:922&gt; [0] Non ok http code 503 (HTTP/1.1 503)<br>08:04:19 W http_client.go:922&gt; [0] Non ok http code 503 (HTTP/1.1 503)<br>08:04:19 W http_client.go:922&gt; [0] Non ok http code 503 (HTTP/1.1 503)<br>08:04:19 W http_client.go:922&gt; [0] Non ok http code 503 (HTTP/1.1 503)<br>08:04:19 W http_client.go:922&gt; [1] Non ok http code 503 (HTTP/1.1 503)<br>08:04:19 W http_client.go:922&gt; [0] Non ok http code 503 (HTTP/1.1 503)<br>08:04:20 W http_client.go:922&gt; [0] Non ok http code 503 (HTTP/1.1 503)<br>08:04:21 W http_client.go:922&gt; [0] Non ok http code 503 (HTTP/1.1 503)<br>08:04:21 W http_client.go:922&gt; [1] Non ok http code 503 (HTTP/1.1 503)<br>08:04:24 W http_client.go:922&gt; [1] Non ok http code 503 (HTTP/1.1 503)<br>08:04:25 W http_client.go:922&gt; [1] Non ok http code 503 (HTTP/1.1 503)<br>08:04:25 W http_client.go:922&gt; [1] Non ok http code 503 (HTTP/1.1 503)<br>08:04:28 W http_client.go:922&gt; [1] Non ok http code 503 (HTTP/1.1 503)<br>08:04:28 W http_client.go:922&gt; [1] Non ok http code 503 (HTTP/1.1 503)<br>08:04:28 W http_client.go:922&gt; [1] Non ok http code 503 (HTTP/1.1 503)<br>08:04:28 W http_client.go:922&gt; [0] Non ok http code 503 (HTTP/1.1 503)<br>08:04:29 W http_client.go:922&gt; [1] Non ok http code 503 (HTTP/1.1 503)<br>08:04:29 W http_client.go:922&gt; [0] Non ok http code 503 (HTTP/1.1 503)<br>08:04:30 W http_client.go:922&gt; [1] Non ok http code 503 (HTTP/1.1 503)<br>Ended after 12.208230606s : 3000 calls. qps=245.74<br>Aggregated Function Time : count 3000 avg 0.0081280476 +/- 0.00139 min 0.000258827 max 0.025953012 sum 24.3841427<br># range, mid point, percentile, count<br>&gt;= 0.000258827 &lt;= 0.001 , 0.000629414 , 0.60, 18<br>&gt; 0.001 &lt;= 0.002 , 0.0015 , 0.73, 4<br>&gt; 0.004 &lt;= 0.005 , 0.0045 , 0.90, 5<br>&gt; 0.005 &lt;= 0.006 , 0.0055 , 1.27, 11<br>&gt; 0.006 &lt;= 0.007 , 0.0065 , 4.70, 103<br>&gt; 0.007 &lt;= 0.008 , 0.0075 , 54.90, 1506<br>&gt; 0.008 &lt;= 0.009 , 0.0085 , 88.73, 1015<br>&gt; 0.009 &lt;= 0.01 , 0.0095 , 95.57, 205<br>&gt; 0.01 &lt;= 0.011 , 0.0105 , 97.47, 57<br>&gt; 0.011 &lt;= 0.012 , 0.0115 , 98.27, 24<br>&gt; 0.012 &lt;= 0.014 , 0.013 , 99.30, 31<br>&gt; 0.014 &lt;= 0.016 , 0.015 , 99.67, 11<br>&gt; 0.016 &lt;= 0.018 , 0.017 , 99.73, 2<br>&gt; 0.018 &lt;= 0.02 , 0.019 , 99.83, 3<br>&gt; 0.02 &lt;= 0.025 , 0.0225 , 99.97, 4<br>&gt; 0.025 &lt;= 0.025953 , 0.0254765 , 100.00, 1<br># target 50% 0.00790239<br># target 75% 0.00859409<br># target 90% 0.00918537<br># target 99% 0.0134194<br># target 99.9% 0.0225<br>Error cases : count 22 avg 0.00069907759 +/- 0.000328 min 0.000258827 max 0.001549756 sum 0.015379707<br># range, mid point, percentile, count<br>&gt;= 0.000258827 &lt;= 0.001 , 0.000629414 , 81.82, 18<br>&gt; 0.001 &lt;= 0.00154976 , 0.00127488 , 100.00, 4<br># target 50% 0.000694811<br># target 75% 0.000934602<br># target 90% 0.00124739<br># target 99% 0.00151952<br># target 99.9% 0.00154673<br># Socket and IP used for each connection:<br>[0]  13 socket used, resolved to [10.96.157.177:8000] connection timing : count 13 avg 6.7982923e-05 +/- 2.743e-05 min 3.0694e-05 max 0.000127744 sum 0.000883778<br>[1]  11 socket used, resolved to [10.96.157.177:8000] connection timing : count 11 avg 0.00042713355 +/- 0.001066 min 4.9292e-05 max 0.003794291 sum 0.004698469<br>Connection time (s) : count 24 avg 0.00023259363 +/- 0.0007438 min 3.0694e-05 max 0.003794291 sum 0.005582247<br>Sockets used: 24 (for perfect keepalive, would be 2)<br>Uniform: false, Jitter: false<br>IP addresses distribution:<br>10.96.157.177:8000: 24<br><strong>Code 200 : 2978 (99.3 %)<br>Code 503 : 22 (0.7 %)</strong><br>Response Header Sizes : count 3000 avg 228.34033 +/- 19.63 min 0 max 231 sum 685021<br>Response Body/Total Sizes : count 3000 avg 819.75167 +/- 49.74 min 241 max 825 sum 2459255<br>All done 3000 calls (plus 0 warmup) 8.128 ms avg, 245.7 qps</pre><p>Results in 0.7% failure rate.</p><p>Kiali graph shows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UJ7ZJiEak_6iEkUWOZz7vQ.png" /></figure><h3>Test time: 3000 calls performed in 3 parallel threads</h3><p>To demo the limitation, I had to use 3 parallel threads, where the throttling really showed off:</p><pre>$ kubectl exec &quot;$FORTIO_POD&quot; -c fortio -- /usr/bin/fortio load -c 3 -qps 0 -n 3000 -loglevel Warning <a href="http://httpbin:8000/get">http://httpbin:8000/get</a></pre><pre>08:19:26 I logger.go:134&gt; Log level is now 3 Warning (was 2 Info)<br>Fortio 1.38.0 running at 0 queries per second, 2-&gt;2 procs, for 3000 calls: <a href="http://httpbin:8000/get">http://httpbin:8000/get</a><br>Starting at max qps with 3 thread(s) [gomax 2] for exactly 3000 calls (1000 per thread + 0)<br>08:19:26 W http_client.go:922&gt; [0] Non ok http code 503 (HTTP/1.1 503)<br>08:19:26 W http_client.go:922&gt; [0] Non ok http code 503 (HTTP/1.1 503)<br>08:19:26 W http_client.go:922&gt; [2] Non ok http code 503 (HTTP/1.1 503)<br>08:19:26 W http_client.go:922&gt; [2] Non ok http code 503 (HTTP/1.1 503)<br>08:19:26 W http_client.go:922&gt; [2] Non ok http code 503 (HTTP/1.1 503)<br>08:19:26 W http_client.go:922&gt; [2] Non ok http code 503 (HTTP/1.1 503)<br>08:19:26 W http_client.go:922&gt; [2] Non ok http code 503 (HTTP/1.1 503)<br>08:19:26 W http_client.go:922&gt; [2] Non ok http code 503 (HTTP/1.1 503)<br>08:19:26 W http_client.go:922&gt; [2] Non ok http code 503 (HTTP/1.1 503)<br>08:19:26 W http_client.go:922&gt; [1] Non ok http code 503 (HTTP/1.1 503)<br>08:19:26 W http_client.go:922&gt; [2] Non ok http code 503 (HTTP/1.1 503)<br>08:19:26 W http_client.go:922&gt; [2] Non ok http code 503 (HTTP/1.1 503)<br>08:19:26 W http_client.go:922&gt; [2] Non ok http code 503 (HTTP/1.1 503)</pre><pre>...</pre><pre>Error cases : count 1957 avg 0.001547909 +/- 0.001475 min 0.000185792 max 0.017581766 sum 3.02925787<br># range, mid point, percentile, count<br>&gt;= 0.000185792 &lt;= 0.001 , 0.000592896 , 49.41, 967<br>&gt; 0.001 &lt;= 0.002 , 0.0015 , 78.18, 563<br>&gt; 0.002 &lt;= 0.003 , 0.0025 , 89.22, 216<br>&gt; 0.003 &lt;= 0.004 , 0.0035 , 93.87, 91<br>&gt; 0.004 &lt;= 0.005 , 0.0045 , 96.01, 42<br>&gt; 0.005 &lt;= 0.006 , 0.0055 , 97.65, 32<br>&gt; 0.006 &lt;= 0.007 , 0.0065 , 98.77, 22<br>&gt; 0.007 &lt;= 0.008 , 0.0075 , 99.39, 12<br>&gt; 0.008 &lt;= 0.009 , 0.0085 , 99.59, 4<br>&gt; 0.009 &lt;= 0.01 , 0.0095 , 99.69, 2<br>&gt; 0.01 &lt;= 0.011 , 0.0105 , 99.74, 1<br>&gt; 0.011 &lt;= 0.012 , 0.0115 , 99.80, 1<br>&gt; 0.012 &lt;= 0.014 , 0.013 , 99.90, 2<br>&gt; 0.014 &lt;= 0.016 , 0.015 , 99.95, 1<br>&gt; 0.016 &lt;= 0.0175818 , 0.0167909 , 100.00, 1<br># target 50% 0.00102043<br># target 75% 0.00188943<br># target 90% 0.00316813<br># target 99% 0.00736917<br># target 99.9% 0.014086<br># Socket and IP used for each connection:<br>[0] 456 socket used, resolved to [10.96.157.177:8000] connection timing : count 456 avg 0.00012040605 +/- 0.0004198 min 2.3292e-05 max 0.005671351 sum 0.054905161<br>[1] 870 socket used, resolved to [10.96.157.177:8000] connection timing : count 870 avg 8.3235437e-05 +/- 0.0003149 min 2.5729e-05 max 0.00726584 sum 0.07241483<br>[2] 633 socket used, resolved to [10.96.157.177:8000] connection timing : count 633 avg 9.3076908e-05 +/- 0.0002951 min 2.1396e-05 max 0.004866478 sum 0.058917683<br>Connection time (s) : count 1959 avg 9.5067725e-05 +/- 0.0003368 min 2.1396e-05 max 0.00726584 sum 0.186237674<br>Sockets used: 1959 (for perfect keepalive, would be 3)<br>Uniform: false, Jitter: false<br>IP addresses distribution:<br>10.96.157.177:8000: 1959<br><strong>Code 200 : 1043 (34.8 %)<br>Code 503 : 1957 (65.2 %)</strong><br>Response Header Sizes : count 3000 avg 80.106333 +/- 109.7 min 0 max 231 sum 240319<br>Response Body/Total Sizes : count 3000 avg 443.83267 +/- 277.8 min 241 max 825 sum 1331498<br>All done 3000 calls (plus 0 warmup) 5.367 ms avg, 458.9 qps</pre><p>Finally some higher error rate (65%) caused by the istio destination route settings.</p><p>Kiali graph shows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iuyoTw9DSKhd-Zf-hGSpAA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ROLwwP35F-Jxbt_Uylkn3A.png" /></figure><h3>Sum up</h3><p>Combination of kind + Istio seems like a perfect way to quickly bootstrap and test the service mesh capabilities.</p><p>Moreover, kiali offers pleasant visualizations, giving insights to cluster components and their interactions.</p><p>The combination of these tools seems like a perfect fit for the demos and PoCs.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cee45eaabc4d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Favoring Podman over Docker Desktop]]></title>
            <link>https://medium.com/@butkovic/favoring-podman-over-docker-desktop-33368e031ba0?source=rss-74fc69b20a6f------2</link>
            <guid isPermaLink="false">https://medium.com/p/33368e031ba0</guid>
            <category><![CDATA[docker-compose]]></category>
            <category><![CDATA[macos]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[podman]]></category>
            <category><![CDATA[podman-compose]]></category>
            <dc:creator><![CDATA[Peter Butkovic]]></dc:creator>
            <pubDate>Tue, 22 Mar 2022 08:21:57 GMT</pubDate>
            <atom:updated>2022-10-11T10:00:08.986Z</atom:updated>
            <content:encoded><![CDATA[<p>Since the <a href="https://www.docker.com/blog/updating-product-subscriptions/">announcement</a>, that docker desktop won’t be available for free for the bigger organizations, I’ve been looking for the open source alternative with smooth migration option.</p><p>Podman did a great job in delivering the docker-compatible experience. The major pain point was the lack of volume mounts. Since the podman 4.x release has the gap been finally closed.</p><p>I’m used to run my dev stack on docker-compose. In general it prevents a need for manual actions or custom scripts to be written for provisioning whatever your runtime depends on, e.g. database, message broker, or even external service mockups. <a href="https://github.com/containers/podman-compose">Podman-compose project</a> provides an alternative in the podman world. Let’s have a look on short demo on usage of these. Steps are adapted to MacOS, as that is the OS of choice for my laptop.</p><h3>Installation</h3><p>Brew installs the podman (<a href="https://formulae.brew.sh/formula/podman">https://formulae.brew.sh/formula/podman</a>) and podman-compose (<a href="https://formulae.brew.sh/formula/podman-compose">https://formulae.brew.sh/formula/podman-compose</a>):</p><pre>brew install podman<br>brew install podman-compose</pre><h3>Upgrade</h3><p>If you need to upgrade the existing installations, for podman use:</p><pre>brew upgrade podman</pre><p>and for podman-compose use:</p><pre>pip3 install --upgrade podman-compose</pre><h3>Podman machine setup</h3><p>In podman we first need to create and run podman machine. Then we’re free to run podman or podman-compose commands.</p><p>Let’s say, we’re interested in:</p><ul><li>option to mount the $HOME directory (or any of it’s subdirectories) to containers,</li><li>reserving 4 CPUs, 60GB of disk space and 6GB of memory for podman machine.</li></ul><p>Let’s create the podman machine using:</p><pre>podman machine init --cpus=4 --disk-size=60 --memory=6096 -v $HOME:$HOME</pre><p>and start it afterwads:</p><pre>podman machine start</pre><p>To see if it runs, let’s use:</p><pre>podman machine ls</pre><h3>Podman usage</h3><p>Now let’s see whether we’re able to run the container with volume mount.</p><p>Let’s first run sample (ubuntu) container without mount and see what is present in /opt dir:</p><pre>podman run ubuntu ls /opt</pre><p>No output indicates, that /opt dir is empty in the container.</p><p>Now let’s try with volume mount option:</p><pre>mkdir test-dir</pre><pre>touch test-dir/test-file.txt</pre><pre>podman run -v ./test-dir:/opt ubuntu ls /opt<br>test-file.txt</pre><p>We see that mounted dir with it’s content is accessible in container now!</p><h3>Podman compose usage</h3><p>Now let’s give a podman compose a try for the similar case.</p><p>Having docker-compose.yaml file:</p><pre>version: &quot;3.9&quot;<br>services:<br>  test:<br>    image: ubuntu<br>    volumes:<br>    - ./test-dir:/opt<br>    command: ls /opt</pre><p>Running (only relevant output shown):</p><pre>podman-compose up<br>...<br>test-file.txt<br>exit code: 0</pre><p>we can see, that volume mount worked!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=33368e031ba0" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>