<?xml version="1.0" encoding="utf-8" ?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <generator>Hugo -- gohugo.io</generator>
    <title>Mikhail Shilkov</title><link href="https://mikhail.io/feed" rel="self" />
    <link href="https://mikhail.io/"/>
    <updated>2026-01-20T19:36:25+01:00</updated>
    <author>
            <name>Mikhail Shilkov</name>
            </author>
    <id>https://mikhail.io/</id>
    
    
        
        <entry>
            <title type="html"><![CDATA[Inside Claude Code Skills: Structure, prompts, invocation]]></title>
            <link href="https://mikhail.io/2025/10/claude-code-skills/"/>
            <id>https://mikhail.io/2025/10/claude-code-skills/</id>
            
            <published>2025-10-28T00:00:00+00:00</published>
            <updated>2025-10-28T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Under the hood of Claude Code skills: folder layout, tool definition, and runtime flow.</blockquote><p>Claude Code recently added <a href="https://docs.claude.com/en/docs/claude-code/skills">skills</a> as an extensibility mechanism alongside MCP servers and slash commands. While MCP servers add new tools and slash commands provide pre-defined prompts, skills expand prompts on demand with task-specific instructions and local helpers.</p>
<p>This post explains how skills are wired: how they&rsquo;re discovered, surfaced, and invoked.</p>
<h2 id="skills-folder-structure">Skills Folder Structure</h2>
<p>Skills are folders containing a <code>SKILL.md</code> file and optional scripts or other resources. Sub-folders are allowed (and encouraged) for organizing helper scripts, templates, and data files. Here&rsquo;s an example with two skills:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>.claude/skills/
</span></span><span style="display:flex;"><span>├── pdf/
</span></span><span style="display:flex;"><span>│   ├── SKILL.md
</span></span><span style="display:flex;"><span>│   ├── extract_text.py
</span></span><span style="display:flex;"><span>│   └── templates/
</span></span><span style="display:flex;"><span>│       └── summary.html
</span></span><span style="display:flex;"><span>└── csv/
</span></span><span style="display:flex;"><span>    ├── SKILL.md
</span></span><span style="display:flex;"><span>    ├── analyze.py
</span></span><span style="display:flex;"><span>    └── utils/
</span></span><span style="display:flex;"><span>        ├── parser.py
</span></span><span style="display:flex;"><span>        └── visualizer.py
</span></span></code></pre></div><p>The <code>pdf/SKILL.md</code> might contain:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>name: pdf
</span></span><span style="display:flex;"><span>description: Extract and analyze text from PDF documents. Use when users ask to process or read PDFs.
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span># PDF Processing Skill
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Use the extract_text.py script in this folder to extract text from PDFs:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    python3 extract_text.py <span style="color:#1f2328">&lt;</span><span style="color:#0550ae">input_file</span><span style="color:#1f2328">&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>After extraction, summarize the key points in a structured format.
</span></span></code></pre></div><p>Each skill packages instructions alongside executable scripts and reference materials, creating self-contained capability extensions.</p>
<h2 id="skill-tool-definition">Skill Tool Definition</h2>
<p>Claude Code provides a <code>Skill</code> tool to Claude. Here&rsquo;s the complete tool definition (captured from an actual Claude Code session):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>## Skill
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>**Input Schema:**
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>object:
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">-</span> command (required):
</span></span><span style="display:flex;"><span>    string
</span></span><span style="display:flex;"><span>      # The skill name (no arguments). E.g., &#34;pdf&#34; or &#34;xlsx&#34;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>**Description:**
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Execute a skill within the main conversation
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">&lt;</span><span style="color:#0550ae">skills_instructions</span><span style="color:#1f2328">&gt;</span>
</span></span><span style="display:flex;"><span>When users ask you to perform tasks, check if any of the available skills
</span></span><span style="display:flex;"><span>below can help complete the task more effectively. Skills provide specialized
</span></span><span style="display:flex;"><span>capabilities and domain knowledge.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>How to use skills:
</span></span><span style="display:flex;"><span><span style="color:#cf222e">-</span> Invoke skills using this tool with the skill name only (no arguments)
</span></span><span style="display:flex;"><span><span style="color:#cf222e">-</span> When you invoke a skill, you will see <span style="color:#1f2328">&lt;</span><span style="color:#0550ae">command-message</span><span style="color:#1f2328">&gt;</span>The &#34;{name}&#34; skill is loading<span style="color:#1f2328">&lt;/</span><span style="color:#0550ae">command-message</span><span style="color:#1f2328">&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">-</span> The skill&#39;s prompt will expand and provide detailed instructions on how to complete the task
</span></span><span style="display:flex;"><span><span style="color:#cf222e">-</span> Examples:
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">-</span> <span style="color:#0a3069">`command: &#34;pdf&#34;`</span> - invoke the pdf skill
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">-</span> <span style="color:#0a3069">`command: &#34;xlsx&#34;`</span> - invoke the xlsx skill
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">-</span> <span style="color:#0a3069">`command: &#34;ms-office-suite:pdf&#34;`</span> - invoke using fully qualified name
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Important:
</span></span><span style="display:flex;"><span><span style="color:#cf222e">-</span> Only use skills listed in <span style="color:#1f2328">&lt;</span><span style="color:#0550ae">available_skills</span><span style="color:#1f2328">&gt;</span> below
</span></span><span style="display:flex;"><span><span style="color:#cf222e">-</span> Do not invoke a skill that is already running
</span></span><span style="display:flex;"><span><span style="color:#cf222e">-</span> Do not use this tool for built-in CLI commands (like /help, /clear, etc.)
</span></span><span style="display:flex;"><span><span style="color:#1f2328">&lt;/</span><span style="color:#0550ae">skills_instructions</span><span style="color:#1f2328">&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">&lt;</span><span style="color:#0550ae">available_skills</span><span style="color:#1f2328">&gt;</span>
</span></span><span style="display:flex;"><span>[Skills are listed here - see next section]
</span></span><span style="display:flex;"><span><span style="color:#1f2328">&lt;/</span><span style="color:#0550ae">available_skills</span><span style="color:#1f2328">&gt;</span>
</span></span></code></pre></div><p>The tool is simple: just a <code>command</code> parameter with the skill name. But the description contains both instructions for using skills and an embedded <code>&lt;available_skills&gt;</code> section.</p>
<h2 id="available-skills-list">Available Skills List</h2>
<p>Within the tool definition&rsquo;s description, Claude Code builds an <code>&lt;available_skills&gt;</code> section based on the skill folders. The <code>name</code> and <code>description</code> fields come directly from the YAML frontmatter in each skill&rsquo;s <code>SKILL.md</code> file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#0550ae">&lt;available_skills&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&lt;skill&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;name&gt;</span>pdf<span style="color:#0550ae">&lt;/name&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;description&gt;</span>
</span></span><span style="display:flex;"><span>      Extract and analyze text from PDF documents. Use when users
</span></span><span style="display:flex;"><span>      ask to process or read PDFs.
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;/description&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;location&gt;</span>user<span style="color:#0550ae">&lt;/location&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&lt;/skill&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&lt;skill&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;name&gt;</span>csv<span style="color:#0550ae">&lt;/name&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;description&gt;</span>
</span></span><span style="display:flex;"><span>      Analyze and visualize CSV data. Use when users ask to
</span></span><span style="display:flex;"><span>      process or analyze CSV files.
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;/description&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;location&gt;</span>user<span style="color:#0550ae">&lt;/location&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&lt;/skill&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#0550ae">&lt;/available_skills&gt;</span>
</span></span></code></pre></div><p>Notice how the <code>pdf</code> skill&rsquo;s name and description match exactly what was defined in the <code>pdf/SKILL.md</code> frontmatter shown earlier. The metadata includes:</p>
<ul>
<li><strong>name</strong>: From frontmatter, used as identifier for invoking the skill</li>
<li><strong>description</strong>: From frontmatter, tells Claude when to use this skill</li>
<li><strong>location</strong>: Either <code>user</code> (machine-scoped) or <code>project</code> (defined in the current folder)</li>
</ul>
<p>The list of skills is embedded in the tool definition.</p>
<h2 id="runtime-invocation">Runtime Invocation</h2>
<p>Skills operate via a tool call/tool response pair. For example, when the user asks &ldquo;Extract text from report.pdf&rdquo;, Claude recognizes this matches the pdf skill and sends an assistant message with a tool use:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&#34;role&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;assistant&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&#34;content&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#0550ae">&#34;type&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;text&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#0550ae">&#34;text&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;Now let me use the pdf skill to read the document:&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#0550ae">&#34;type&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;tool_use&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#0550ae">&#34;id&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;toolu_01JRBZGD3vy9gDsifuT89L8B&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#0550ae">&#34;name&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;Skill&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#0550ae">&#34;input&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;command&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;pdf&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">]</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>The system responds with a user message containing a <code>tool_result</code> and two <code>text</code> blocks:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&#34;role&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;user&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&#34;content&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#0550ae">&#34;type&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;tool_result&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#0550ae">&#34;tool_use_id&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;toolu_01JRBZGD3vy9gDsifuT89L8B&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#0550ae">&#34;content&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;Launching skill: pdf&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#0550ae">&#34;type&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;text&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#0550ae">&#34;text&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;&lt;command-message&gt;The \&#34;pdf\&#34; skill is running&lt;/command-message&gt;\n&lt;command-name&gt;pdf&lt;/command-name&gt;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#0550ae">&#34;type&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;text&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>      <span style="color:#0550ae">&#34;text&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;Base Path: /Users/username/.claude/skills/pdf/\n\n# PDF Processing Skill\n\nUse the extract_text.py script in this folder to extract text from PDFs:\n\n    python3 extract_text.py &lt;input_file&gt;\n\nAfter extraction, summarize the key points in a structured format.&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">]</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>The third block contains both the skill&rsquo;s base path and the <code>SKILL.md</code> body (without frontmatter). The base path enables Claude Code to locate and execute scripts bundled with the skill relative to that folder.</p>
<p>From this point, Claude Code follows the expanded instructions: it runs the extraction script, processes the output, and creates a summary. Skills aren&rsquo;t separate processes, sub-agents, or external tools: they&rsquo;re injected instructions that guide Claude&rsquo;s behavior within the main conversation.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Skills in Claude Code are a simple mechanism for extensibility. The mechanics:</p>
<ol>
<li><strong>Folder structure</strong>: Each skill is a folder with <code>SKILL.md</code> (containing YAML frontmatter and instructions) plus optional scripts/resources</li>
<li><strong>Tool definition</strong>: The <code>Skill</code> tool embeds an <code>&lt;available_skills&gt;</code> list built from the frontmatter of all skills</li>
<li><strong>Runtime invocation</strong>: When invoked, the tool response includes the base path and <code>SKILL.md</code> body, expanding the context and referencing additional resources</li>
</ol>
<p>What makes this design clever is that it achieves on-demand prompt expansion without modifying the core system prompt. Skills are executable knowledge packages that Claude loads only when needed, extending capabilities while keeping the main prompt lean.</p>
<p>The complete tool definition shown here (captured from actual Claude Code sessions) hasn&rsquo;t been published by Anthropic, making this reverse-engineered view a novel contribution to understanding how Claude Code&rsquo;s extensibility works under the hood.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/ai" term="ai" label="AI" />
                             
                                <category scheme="https://mikhail.io/tags/claude" term="claude" label="Claude" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Inside Claude Code's Web Tools: WebFetch vs WebSearch]]></title>
            <link href="https://mikhail.io/2025/10/claude-code-web-tools/"/>
            <id>https://mikhail.io/2025/10/claude-code-web-tools/</id>
            
            <published>2025-10-06T00:00:00+00:00</published>
            <updated>2025-10-06T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>How Claude Code uses web tools under the hood: schemas, prompts, execution, and design trade-offs</blockquote><p>Claude Code is a popular coding assistant. The tool isn&rsquo;t open-source, so I inspected its runtime behavior to understand the internals. This post documents what I&rsquo;ve seen in the two &ldquo;web&rdquo; tools—<code>WebFetch</code> and <code>WebSearch</code>—and how they&rsquo;re designed. The post is written for agent builders and curious developers.</p>
<p>Claude Code comes with two built-in Web tools:</p>
<ul>
<li><strong><code>WebFetch</code></strong>: accepts a known URL to answer a focused question from that page; returns just the answer + minimal fetch metadata.</li>
<li><strong><code>WebSearch</code></strong>: accepts a search query to find credible sources; returns a small set of relevant links and page titles.</li>
</ul>
<p>Let&rsquo;s dive into the details of both.</p>
<h2 id="webfetch---page-retrieval">WebFetch - Page Retrieval</h2>
<h3 id="tldr-how-it-works">TL;DR: How it works</h3>
<ul>
<li>Input: URL to fetch and a prompt with a question about its content</li>
<li>Validate the domain against a deny-list</li>
<li>Fetch the HTML content (auto-follow same host redirects; 15-minute cache)</li>
<li>Convert HTML to Markdown; trim big pages</li>
<li>Build a prompt to a fast model to summarize the content and answer the question</li>
<li>Haiku answers the prompt with a summary</li>
<li>Result: a concise answer based on retrieved content</li>
</ul>
<h3 id="input-schema">Input schema</h3>
<p>The tool accepts two input parameters:</p>
<ul>
<li><code>url: string</code> - required, &lt;= 2000 chars</li>
<li><code>prompt: string</code> - required, the question to answer from fetched content</li>
</ul>
<p>Note that the prompt is required: the tool never returns raw HTML or markdown content but answers a question about it.</p>
<p>Here is an example:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&#34;url&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;https://mikhail.io/&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&#34;prompt&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;What topics does this blog cover? List the main categories or themes.&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><h3 id="pipeline-step-by-step">Pipeline (step by step)</h3>
<h4 id="1-url-validation--normalization">1) URL validation &amp; normalization</h4>
<p>The URL is first validated and normalized: enforcing a length limit of around 2k chars, upgrading http to https if needed, and stripping credentials and other unsafe parts.</p>
<h4 id="2-domain-safety-check">2) Domain safety check</h4>
<p>The backend calls a <code>domain_info</code> endpoint <code>https://claude.ai/api/web/domain_info?domain=${hostname}</code> to decide to allow or deny.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl <span style="color:#0a3069">&#34;https://claude.ai/api/web/domain_info?domain=mikhail.io&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#0550ae">{</span><span style="color:#0a3069">&#34;domain&#34;</span>:<span style="color:#0a3069">&#34;mikhail.io&#34;</span>,<span style="color:#0a3069">&#34;can_fetch&#34;</span>:true<span style="color:#0550ae">}</span>
</span></span></code></pre></div><p>This endpoint likely maintains a deny-list for obviously malicious domains and may also consider robots.txt and known copyright traps. The exact rules are opaque.</p>
<h4 id="3-fetch-with-redirect-policy">3) Fetch with redirect policy</h4>
<p>Same-host redirects are followed automatically. Cross-host redirects return redirect metadata instead of following: Claude Code must make another tool call if it trusts the new host. Max content size is around 10 MB at fetch time; later processing truncates further to limit token consumption. Each URL is cached with a 15-minute TTL.</p>
<h4 id="4-content-processing">4) Content processing</h4>
<p>HTML is converted to Markdown using the <a href="https://github.com/mixmark-io/turndown">Turndown</a> library. The result is truncated to 100 KB of text with a warning if necessary. Plain text content types pass through without conversion.</p>
<h4 id="5-llm-pass-haiku-35">5) LLM pass (Haiku 3.5)</h4>
<p>A small, fast model runs with an empty system prompt (yes, it&rsquo;s totally empty) and a user prompt template:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Web page content:
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>${content}
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>${userQuery}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Provide a concise response based only on the content above. In your response:
</span></span><span style="display:flex;"><span>- Enforce a strict 125-character maximum for quotes from any source document. Open Source Software is ok as long as we respect the license.
</span></span><span style="display:flex;"><span>- Use quotation marks for exact language from articles; any language outside of the quotation should never be word-for-word the same.
</span></span><span style="display:flex;"><span>- You are not a lawyer and never comment on the legality of your own prompts and responses.
</span></span><span style="display:flex;"><span>- Never produce or reproduce exact song lyrics.
</span></span></code></pre></div><p>This keeps answers short and filters out prompt-injection attempts. Short quotes in quotes; everything else paraphrased. No legal advice, no karaoke.</p>
<h3 id="output">Output</h3>
<p>Here is an example answer to the question above:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Based on the blog content, the main categories and themes are:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>1. Cloud Computing and Serverless Technologies
</span></span><span style="display:flex;"><span>- Azure Functions
</span></span><span style="display:flex;"><span>- AWS Lambda
</span></span><span style="display:flex;"><span>- Google Cloud Run
</span></span><span style="display:flex;"><span>- Serverless architecture and scalability
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>2. Infrastructure as Code (IaC)
</span></span><span style="display:flex;"><span>- Pulumi
</span></span><span style="display:flex;"><span>- Cloud resource management
</span></span><span style="display:flex;"><span>- Multi-cloud deployments
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>... (cut for brevity)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>The blog appears to be written by Mikhail Shilkov and focuses heavily on
</span></span><span style="display:flex;"><span>cloud-native development, serverless technologies, and innovative programming
</span></span><span style="display:flex;"><span>approaches across different cloud platforms and programming languages.
</span></span></code></pre></div><h3 id="why-require-a-prompt-query-instead-of-returning-the-page">Why require a prompt (query) instead of returning the page?</h3>
<p>This is all speculation on my part, but I imagine a few reasons for this particular tool design:</p>
<p><strong>Cost &amp; context control.</strong> Full pages run 10–100 KB after conversion. Pushing them into the main model (Sonnet/Opus) is expensive and crowds out code context. Haiku pre-filters to &ldquo;just answer the question,&rdquo; leaving the main conversation compact.</p>
<p><strong>Injection resistance.</strong> The Haiku prompt pins the task: answer only from the provided content; don&rsquo;t obey page instructions. It&rsquo;s not bulletproof, but it&rsquo;s a solid first gate. An attacker must first trick the Haiku pass before they reach the main agent and then trick Sonnet too—with summarized and paraphrased content.</p>
<p><strong>Copyright hygiene.</strong> The template caps verbatim quotes and bans lyrics. That reduces accidental over-quoting and aligns with conservative IP posture.</p>
<p>Yes, the summary will occasionally omit relevant details. But the cost/security/UX balance is reasonable for &ldquo;look up X on a page and answer Y.&rdquo;</p>
<h2 id="websearch---find-sources">WebSearch - Find Sources</h2>
<p>When it doesn&rsquo;t know the URL, Claude Code issues search requests to Anthropic&rsquo;s server-side <code>WebSearch</code> tool, the same one that Claude chat uses. In response, it receives page titles and URLs of top search results.</p>
<p>Here is the input schema of the <code>WebSearch</code> tool:</p>
<ul>
<li><code>query: string</code> - required, &gt;= 2 chars</li>
<li><code>allowed_domains?: string[]</code> - optional allow-list</li>
<li><code>blocked_domains?: string[]</code> - optional block-list</li>
</ul>
<p>And an example call:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&#34;query&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;Mikhail Shilkov blog AI agents&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>The result is minimal and focused on links:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Web search results for query: &#34;Mikhail Shilkov blog AI agents&#34;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Links: [
</span></span><span style="display:flex;"><span>  {&#34;title&#34;:&#34;Claude Code 2.0 System Prompt Changes - Mikhail Shilkov&#34;,&#34;url&#34;:&#34;https://mikhail.io/2025/01/claude-code-2-system-prompt/&#34;},
</span></span><span style="display:flex;"><span>  {&#34;title&#34;:&#34;How Claude Code Uses Web Tools - Mikhail Shilkov&#34;,&#34;url&#34;:&#34;https://mikhail.io/2025/10/claude-code-web-tools/&#34;},
</span></span><span style="display:flex;"><span>  {&#34;title&#34;:&#34;AI Assistants and Developer Workflows - Mikhail Shilkov&#34;,&#34;url&#34;:&#34;https://mikhail.io/tags/ai/&#34;},
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>According to <a href="https://docs.claude.com/en/docs/agents-and-tools/tool-use/web-search-tool">Anthropic&rsquo;s web search tool documentation</a>, each search result actually includes more fields:</p>
<ul>
<li><code>url</code>: The URL of the source page</li>
<li><code>title</code>: The title of the source page</li>
<li><code>page_age</code>: When the site was last updated</li>
<li><code>encrypted_content</code>: Encrypted page content for citations</li>
</ul>
<p>However, Claude Code&rsquo;s implementation only extracts <code>title</code> and <code>url</code> from the results, discarding the <code>page_age</code> and <code>encrypted_content</code> fields. If Claude Code needs actual page content, it must make an explicit <code>WebFetch</code> call. This design keeps search results lightweight and gives the agent explicit control over when to fetch page content.</p>
<p>The server-side search tool is available on Anthropic&rsquo;s first-party API but it isn&rsquo;t supported on Bedrock/Vertex. If Claude Code is configured to use those platforms, Claude Code hides the <code>WebSearch</code> tool entirely.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Claude Code uses two tools to work with the web: <code>WebFetch</code> answers questions from a given page it trusts; <code>WebSearch</code> finds the pages it needs to read. The split keeps the main agent lean, limits injection surface, and stays conservative on quoting. It’s a pragmatic design: a little less flexibility for a lot more predictability in cost, safety, and developer experience.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/ai" term="ai" label="AI" />
                             
                                <category scheme="https://mikhail.io/tags/claude" term="claude" label="Claude" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Claude Code 2.0 System Prompt Changes]]></title>
            <link href="https://mikhail.io/2025/09/sonnet-4-5-system-prompt-changes/"/>
            <id>https://mikhail.io/2025/09/sonnet-4-5-system-prompt-changes/</id>
            
            <published>2025-10-01T00:00:00+00:00</published>
            <updated>2025-10-01T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Analyzing the system prompt changes between Claude Code 1.x and 2.0, powered by Sonnet 4.5</blockquote><p>Claude Code 2.0 launched in September 2025, powered by Sonnet 4.5 which Anthropic calls the &ldquo;best coding model in the world.&rdquo; The announcement focused on checkpoints and the new VS Code extension, but the underlying prompt and tools changed as well.</p>
<p>Claude Code isn&rsquo;t open source, and Anthropic doesn&rsquo;t publish system prompts. Still, parts of the instructions are visible at runtime. Comparing versions 1.x and 2.0 shows how they&rsquo;ve adjusted for Sonnet 4.5.</p>
<h2 id="the-changes-are-minor">The Changes Are Minor</h2>
<p>Here&rsquo;s a visualization of all changes:</p>
<figure class="wide" >
    
        <img src="system-prompt-diffs.png"
            alt="System Prompt: 188 → 169 lines (&#43;7 / -26) | Tools: 869 → 851 lines (&#43;70 / -88)"
             />
        
    
    <figcaption>
        <h4>System Prompt: 188 → 169 lines (&#43;7 / -26) | Tools: 869 → 851 lines (&#43;70 / -88)</h4>
    </figcaption>
    
</figure>
<p>Most of both files stay unchanged (gray areas). The changes cluster in specific sections; it&rsquo;s refinement not revolution.</p>
<p>The prompt is a bit shorter. Some &ldquo;hot-fix&rdquo; instructions from prior versions seem to have been folded into the model and removed from the prompt.</p>
<h2 id="less-hand-holding-more-trust">Less Hand-Holding, More Trust</h2>
<p>The system prompt changes show a pattern: less prescriptive guidance, more trust in the model&rsquo;s judgment.</p>
<h3 id="rigid-rules-became-guidelines">Rigid Rules Became Guidelines</h3>
<p>Throughout the prompt, absolute commands got softened:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-You MUST answer concisely with fewer than 4 lines
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9"></span><span style="color:#116329;background-color:#dafbe1">+A concise response is generally less than 4 lines
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1"></span>
</span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-One word answers are best
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9"></span><span style="color:#116329;background-color:#dafbe1">+Brief answers are best, but be sure to provide complete information
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1"></span>
</span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-After working on a file, just stop
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9"></span><span style="color:#116329;background-color:#dafbe1">+After working on a file, briefly confirm that you have completed the task
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1"></span>
</span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-VERY IMPORTANT: You MUST avoid using search commands
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9"></span><span style="color:#116329;background-color:#dafbe1">+Avoid using Bash with find, grep... unless explicitly instructed
</span></span></span></code></pre></div><p>The pattern is consistent: &ldquo;MUST&rdquo; became &ldquo;should,&rdquo; &ldquo;NEVER&rdquo; got qualified with &ldquo;unless,&rdquo; and &ldquo;VERY IMPORTANT&rdquo; disappeared. Sonnet 4.5 can handle more nuance.</p>
<h3 id="some-sections-got-cut">Some Sections Got Cut</h3>
<p>A few sections were removed:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-# Following conventions
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-When making changes to files, first understand the file&#39;s code conventions.
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-Mimic code style, use existing libraries and utilities, and follow existing patterns.
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-- NEVER assume that a given library is available, even if it is well known.
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-- When you create a new component, first look at existing components...
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-- When you edit a piece of code, first look at the code&#39;s surrounding context...
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-[~10 lines total]
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9"></span>
</span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-# Code style
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-- IMPORTANT: DO NOT ADD ***ANY*** COMMENTS unless asked
</span></span></span></code></pre></div><p>The &ldquo;Doing tasks&rdquo; block was trimmed from five bullet points to one:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> # Doing tasks
</span></span><span style="display:flex;"><span> The user will primarily request you perform software engineering tasks...
</span></span><span style="display:flex;"><span> - Use the TodoWrite tool to plan the task if required
</span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-- Use the available search tools to understand the codebase
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-- Implement the solution using all tools available
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-- Verify the solution if possible with tests
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-- VERY IMPORTANT: Run lint and typecheck commands
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-NEVER commit changes unless explicitly asked
</span></span></span></code></pre></div><p>These Sonnet 4.0 workarounds were likely removed because the behaviors are now baked into the model through reinforcement learning.</p>
<h2 id="git-workflow-got-more-specific">Git Workflow Got More Specific</h2>
<p>While general instructions got looser, git safety tightened:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span># Version 1.x:
</span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">- NEVER update the git config
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9"></span>
</span></span><span style="display:flex;"><span># Version 2.0:
</span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+Git Safety Protocol:
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+- NEVER update the git config
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+- NEVER run destructive/irreversible git commands unless explicitly requested
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+- NEVER skip hooks (--no-verify, --no-gpg-sign, etc)
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+- NEVER run force push to main/master
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+- Avoid git commit --amend. ONLY use --amend when either:
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+  (1) user explicitly requested amend OR
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+  (2) adding edits from pre-commit hook
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+- Before amending: ALWAYS check authorship
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+- NEVER commit changes unless the user explicitly asks you to
</span></span></span></code></pre></div><p>These read like responses to real incidents from 1.x.</p>
<h3 id="new-home">New Home</h3>
<p>URLs updated throughout to claude.com paths (e.g., /product/claude-code) instead of claude.ai/code.</p>
<h2 id="tool-changes">Tool Changes</h2>
<h3 id="multiedit-tool-removed">MultiEdit Tool Removed</h3>
<p>Version 1.x had a 70-line tool for batch file edits. Version 2.0 removed it entirely. Sonnet 4.5 can &ldquo;execute parallel tool actions&rdquo; according to the announcement, and I noticed it&rsquo;s much more willing to run multiple operations simultaneously without needing specialized batch tools.</p>
<h3 id="slashcommand-tool-added">SlashCommand Tool Added</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#57606a">## SlashCommand</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">Input Schema</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">  </span><span style="color:#0550ae">command</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>string <span style="color:#fff"> </span><span style="color:#57606a"># The slash command to execute with its arguments</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">Description</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span>Execute a slash command within the main conversation<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">Important Notes</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span>- Only available slash commands can be executed<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span>- Some commands may require arguments<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">Available Commands</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span></code></pre></div><p>The &ldquo;Available Commands&rdquo; section is empty in the base tool definition. Commands are likely populated dynamically based on what&rsquo;s available in your environment. This infrastructure creates an extensibility point where developers can create custom workflows more easily.</p>
<h3 id="bash-tool-got-more-detailed">Bash Tool Got More Detailed</h3>
<p>Bash guidance is expanded and clearer about scope:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+IMPORTANT: This tool is for terminal operations like git, npm, docker, etc.
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+DO NOT use it for file operations (reading, writing, editing, searching, finding files)
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+—use the specialized tools for this instead.
</span></span></span></code></pre></div><p>The earlier strict bans on common commands are now more nuanced:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-VERY IMPORTANT: You MUST avoid using search commands like `find` and `grep`.
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-You MUST avoid read tools like `cat`, `head`, and `tail`
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9"></span><span style="color:#116329;background-color:#dafbe1">+Avoid using Bash with the `find`, `grep`, `cat`, `head`, `tail`, `sed`, `awk`,
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+or `echo` commands, unless explicitly instructed or when these commands are
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+truly necessary for the task.
</span></span></span></code></pre></div><p>And there&rsquo;s explicit guidance on parallel execution:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-When issuing multiple commands, use the &#39;;&#39; or &#39;&amp;&amp;&#39; operator to separate them.
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9">-DO NOT use newlines
</span></span></span><span style="display:flex;"><span><span style="color:#82071e;background-color:#ffebe9"></span><span style="color:#116329;background-color:#dafbe1">+When issuing multiple commands:
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+- If the commands are independent and can run in parallel, make multiple
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+  Bash tool calls in a single message
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+- If the commands depend on each other and must run sequentially, use a
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+  single Bash call with &#39;&amp;&amp;&#39; to chain them together
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+- Use &#39;;&#39; only when you need to run commands sequentially but don&#39;t care
</span></span></span><span style="display:flex;"><span><span style="color:#116329;background-color:#dafbe1">+  if earlier commands fail
</span></span></span></code></pre></div><h2 id="what-this-means">What This Means</h2>
<p>Taken together, the deltas point to less prescriptive prompt text and more reliance on model behavior. The system prompt moves from rigid rules (“do not add comments”) toward guidelines (“briefly confirm”). Tooling shifts from special-purpose batch edits to native parallel execution.</p>
<p>Anthropic also frames Sonnet 4.5 as more aligned (reductions in sycophancy, deception, power-seeking), which may explain the softer language in the prompt. Most of the text stayed the same; the interesting bits are where constraints loosen and safety around git gets stricter.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/ai" term="ai" label="AI" />
                             
                                <category scheme="https://mikhail.io/tags/claude" term="claude" label="Claude" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[AI-Assisted Infrastructure as Code with Pulumi's Model Context Protocol Server]]></title>
            <link href="https://mikhail.io/2025/04/mcp-server-ai-assistants/"/>
            <id>https://mikhail.io/2025/04/mcp-server-ai-assistants/</id>
            
            <published>2025-04-08T00:00:00+00:00</published>
            <updated>2025-04-08T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Learn how AI assistants like Cursor with Pulumi&rsquo;s MCP server accelerate IaC workflows and improve developer experience.</blockquote><p>Infrastructure as Code (IaC) has revolutionized how we manage cloud resources, but navigating complex cloud provider APIs, writing boilerplate code, and iterating through deployment cycles can still be time-consuming. Pulumi offers a fantastic developer experience using familiar programming languages. But what if we could make it even <em>faster</em> and more intuitive by integrating powerful AI assistants directly into the development loop?</p>
<p>This is where the <strong>Pulumi Model Context Protocol (MCP) Server integration</strong> shines. <a href="https://modelcontextprotocol.io">MCP</a> is a specification that allows language models (like the AI in your coding assistant) to interact with external tools and data sources in a structured way. By connecting AI-powered code assistants with Pulumi&rsquo;s CLI and registry via MCP, we can bring real-time resource information and infrastructure management directly into the development environment, dramatically reducing friction and accelerating workflows.</p>
<p>Several AI coding assistants like GitHub Copilot, Anthropic&rsquo;s Claude Code, Windsurf and others are rapidly evolving; this post will use <strong>Cursor</strong> (an AI-first code editor) to demonstrate a real-world example of this synergy in action.</p>
<h2 id="setting-up-the-pulumi-mcp-integration-in-cursor">Setting up the Pulumi MCP Integration in Cursor</h2>
<p>Before diving in, you typically need to configure your AI assistant to communicate with the Pulumi MCP server. In Cursor, you create a configuration file named <code>mcp.json</code> within the <code>.cursor</code> directory in your project&rsquo;s root.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&#34;mcpServers&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;pulumi&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#0550ae">&#34;type&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;stdio&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#0550ae">&#34;command&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;npx&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#0550ae">&#34;args&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">[</span><span style="color:#0a3069">&#34;@pulumi/mcp-server&#34;</span><span style="color:#1f2328">]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>Validate the connection within the assistant&rsquo;s settings (e.g., Cursor has a dedicated section to check the MCP connection status).</p>
<p><img src="cursor-mcp-settings.png" alt="Cursor MCP Settings"></p>
<p>Once configured, the AI assistant can leverage Pulumi tools seamlessly. These tools are specific actions enabled by the MCP server—like searching the Pulumi Registry or running a <code>pulumi</code> command—allowing the assistant to gather information or interact with your Pulumi project.</p>
<h2 id="the-goal-provisioning-an-aks-cluster">The Goal: Provisioning an AKS Cluster</h2>
<p>Our objective for this walkthrough is to provision a temporary Azure Kubernetes Service (AKS) cluster for a short experiment using Pulumi and TypeScript. We need the cluster created with minimal fuss and its <code>kubeconfig</code> exported for access.</p>
<h2 id="the-traditional-approach">The Traditional Approach</h2>
<p>The conventional method for this task involves significant context switching: searching Azure and Pulumi documentation in a browser, writing code in an editor, running commands (<code>pulumi preview</code>, <code>pulumi up</code>) in a terminal, and manually correlating information between these different environments. This process can be slow, requires deep knowledge recall (or constant lookups), and is prone to errors during the manual translation from documentation to code.</p>
<h2 id="the-ai-assistant--pulumi-mcp-approach">The AI Assistant + Pulumi MCP Approach</h2>
<p>Let&rsquo;s walk through how the same task unfolds much more efficiently using an AI assistant integrated with Pulumi MCP.</p>
<h3 id="understanding-the-ai-assistant--tool-interaction">Understanding the AI Assistant + Tool Interaction</h3>
<p>Before detailing the steps, it&rsquo;s helpful to understand the user interface flow when the AI assistant uses integrated tools like those provided by the Pulumi MCP integration. Typically:</p>
<ol>
<li>The developer provides a prompt or instruction in the chat interface within their editor.</li>
<li>The AI assistant analyzes the request and determines that it needs specific information (like resource properties) or needs to perform an action (like running <code>pulumi preview</code>).</li>
<li>The assistant indicates it will use a specific tool (e.g., <code>pulumi_registry_listResources</code>, <code>pulumi_cli_preview</code>). This often appears as a distinct UI element in the chat, showing the tool name and parameters being used.</li>
<li>The tool executes via the MCP server, interacting with the Pulumi CLI or registry as needed.</li>
<li>The output or result from the tool (e.g., registry listings, preview results, deployment errors) is displayed directly in the chat interface.</li>
<li>The AI assistant processes this output and uses it to continue the task – either by generating code, providing an answer, or deciding on the next step (like suggesting a fix for an error).</li>
</ol>
<p>This tight loop keeps the developer focused within their editor environment, minimizing disruptions.</p>
<p><img src="cursor-tool-calls.png" alt="Cursor calling Pulumi tools"></p>
<h3 id="step-by-step-walkthrough">Step-by-Step Walkthrough</h3>
<p>Here&rsquo;s how the AKS provisioning task played out:</p>
<h4 id="the-request">The Request</h4>
<p>The developer starts with a natural language request to the AI assistant within the editor:</p>
<blockquote>
<p>&ldquo;I have an empty Pulumi project with TypeScript. Please edit the program to provision an AKS cluster for me. It&rsquo;s a temporary AKS cluster that I need for a short experiment, so I don&rsquo;t need any particular configuration of it. Just export its kubeconfig when you are done. Please use the tools to lookup resource information and to run pulumi preview to make sure your code works. When done, run pulumi up for me.&rdquo;</p></blockquote>
<h4 id="ai-powered-resource-discovery">AI-Powered Resource Discovery</h4>
<p>Instead of the developer manually searching docs, the AI assistant leverages the MCP integration.</p>
<ul>
<li>The AI assistant first queries the Pulumi Registry via MCP to list resources within the Azure Native provider&rsquo;s <code>containerservice</code> module (using the <code>pulumi_registry_listResources</code> tool). This immediately identified <code>ManagedCluster</code> as the relevant resource.</li>
<li>The AI assistant then requests detailed information <em>specifically</em> for <code>ManagedCluster</code> (using <code>pulumi_registry_getResource</code>), directly retrieving its required properties, descriptions, and structure without leaving the editor.</li>
</ul>
<p><img src="registry-tools.png" alt="Cursor calling Pulumi tools"></p>
<h4 id="ai-code-generation--editing">AI Code Generation &amp; Editing</h4>
<p>Using the information retrieved from the Pulumi Registry, the assistant generated the necessary TypeScript code. It defined the <code>ManagedCluster</code> resource with a basic configuration and used its code editing capabilities to insert the code directly into the developer&rsquo;s <code>index.ts</code> file.</p>
<h4 id="integrated-validation">Integrated Validation</h4>
<p>Before attempting a potentially time-consuming deployment, the assistant ran <code>pulumi preview</code> using the integrated CLI tool (<code>pulumi_cli_preview</code>). The preview output appeared directly in the chat.</p>
<p>The preview succeeded, showing the resources that would be created. The assistant then proceeded to run <code>pulumi up</code> using the integrated CLI tool (<code>pulumi_cli_up</code>). However, the deployment flagged an error: an incorrect attempt to interpolate a Pulumi <code>Output&lt;string&gt;</code> directly into a resource property.</p>
<p><img src="up-failed.png" alt="Pulumi UP tool failed due to incorrect interpolation"></p>
<h4 id="ai-assisted-debugging--iteration">AI-Assisted Debugging &amp; Iteration</h4>
<p>The assistant analyzed the error message from the tool:</p>
<ul>
<li>It identified the incorrect string interpolation and proposed a fix (using <code>pulumi.interpolate</code>), applying it via the editing tool.</li>
<li><em>Self-correction:</em> The first fix inadvertently introduced a circular dependency (caught by linters integrated into the editor and surfaced to the assistant). The assistant recognized this and further refined the code by removing the problematic property, correctly relying on Azure&rsquo;s default behavior.</li>
<li>Another attempt to deploy using <code>pulumi up</code> failed, this time surfacing an Azure API error: the provided SSH key format was invalid.</li>
</ul>
<p><img src="ssh-key.png" alt="Pulumi UP tool failed due to incorrect SSH key"></p>
<h4 id="collaborative-problem-solving--tooling">Collaborative Problem Solving &amp; Tooling</h4>
<p>The developer, seeing the SSH key error, suggested using the Pulumi TLS package to generate a valid key dynamically.</p>
<ul>
<li>The assistant used the integrated terminal tool to execute <code>npm install @pulumi/tls</code>.</li>
<li>It then edited the <code>index.ts</code> file again, incorporating the <code>tls.PrivateKey</code> resource and correctly using its <code>publicKeyOpenssh</code> output for the <code>ManagedCluster</code>&rsquo;s Linux profile.</li>
</ul>
<p><img src="tls-key.png" alt="Use the Pulumi TLS provider"></p>
<h4 id="successful-deployment">Successful Deployment</h4>
<p>The subsequent <code>pulumi preview</code> showed the correct plan (including the new TLS key resource). The assistant then executed <code>pulumi up</code>, which completed successfully. The success message and resource summary appeared in the chat.</p>
<p><img src="success.png" alt="Successful Deployment"></p>
<h4 id="accessing-outputs">Accessing Outputs</h4>
<p>Finally, retrieving the <code>kubeconfig</code> was trivial. The assistant used the stack output tool (<code>pulumi_cli_stack_output kubeconfig</code>) to fetch and display the configuration directly.</p>
<p><img src="stack-output.png" alt="Stack Output showing Kubeconfig"></p>
<h2 id="why-this-changes-the-game">Why This Changes the Game</h2>
<p>Integrating AI assistants with the Pulumi Model Context Protocol offers tangible benefits:</p>
<ul>
<li><strong>Reduced Context Switching:</strong> The entire workflow – discovery, coding, validation, deployment, debugging, output retrieval – happens primarily <em>inside</em> the editor. No more juggling browser tabs and terminal windows.</li>
<li><strong>Accelerated Discovery:</strong> MCP integration provides immediate, context-aware access to Pulumi resource schemas and documentation snippets.</li>
<li><strong>Faster Coding:</strong> AI generates boilerplate and resource definitions quickly based on registry data and natural language requests.</li>
<li><strong>Tighter Feedback Loop:</strong> <code>pulumi preview</code> and <code>pulumi up</code> results are instantly available within the coding environment, enabling rapid iteration.</li>
<li><strong>Intelligent Assistance &amp; Collaboration:</strong> The AI assists with debugging complex errors, incorporates developer suggestions, and leverages integrated tooling effectively.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>The synergy between AI coding assistants and the Pulumi Model Context Protocol (MCP) integration creates a remarkably efficient environment for Infrastructure as Code development. By bringing cloud resource knowledge, code generation, and the Pulumi CLI workflow directly into the editor, developers can build, deploy, and iterate on their infrastructure faster and with significantly less friction.</p>
<p>Whether you use Cursor, Copilot, Claude Code, Windsurf, or another emerging AI tool, integrating it with the Pulumi MCP server offers a glimpse into the future of streamlined, intelligent IaC development, ultimately boosting productivity and improving the overall developer experience.</p>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                             
                                <category scheme="https://mikhail.io/tags/ai" term="ai" label="AI" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/kubernetes" term="kubernetes" label="Kubernetes" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Introducing Customizable Resource Auto-naming in Pulumi]]></title>
            <link href="https://mikhail.io/2025/01/autonaming-configuration/"/>
            <id>https://mikhail.io/2025/01/autonaming-configuration/</id>
            
            <published>2025-01-16T00:00:00+00:00</published>
            <updated>2025-01-16T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Discover how to customize Pulumi&rsquo;s resource naming to align with your organization&rsquo;s standards and naming conventions.</blockquote><p>I&rsquo;m thrilled to announce that you can now customize how Pulumi names your cloud resources! Our default auto-naming feature has helped thousands of customers successfully manage cloud resources at scale by automatically ensuring unique, conflict-free resource names across their cloud deployments. This robust naming system has been particularly valuable for teams managing multiple environments, handling zero-downtime deployments, and maintaining clear resource organization. Today, we&rsquo;re taking it to the next level by giving you control over how these names are generated.</p>
<p>Over the years, we&rsquo;ve heard from many teams using Pulumi that while they love the power and convenience of our auto-naming system, they need it to work with their organization&rsquo;s naming standards - whether that&rsquo;s adding cost center identifiers, following compliance rules, or matching existing naming patterns. The <a href="https://github.com/pulumi/pulumi/issues/1518">GitHub issue tracking this feature</a> has gathered quite some attention (50 thumbs up!) and lots of great input from the community.</p>
<p>Today, I&rsquo;m excited to introduce resource auto-naming configuration. Now you can have the best of both worlds: keep the robustness of Pulumi&rsquo;s auto-naming while making it follow your team&rsquo;s naming conventions. Want your resources to include environment tags? Project prefixes? Random suffixes of specific length? Disable auto-naming entirely? It&rsquo;s all possible now, and it works across all major cloud providers.</p>
<h2 id="the-road-to-better-resource-naming">The Road to Better Resource Naming</h2>
<p>The original <a href="https://github.com/pulumi/pulumi/issues/1518">feature request</a> I opened in June 2018 when I was a Pulumi customer. It has generated extensive discussion, with users sharing various use cases and requirements. Today, I&rsquo;m happy to finally close that issue with a solution that addresses the community&rsquo;s needs while maintaining Pulumi&rsquo;s robust resource management capabilities.</p>
<p>Check out the full story:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Y7tedYSZly4?rel=0?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<h2 id="introducing-auto-naming-configuration">Introducing Auto-naming Configuration</h2>
<p>With the new auto-naming configuration feature, you now have full control over how Pulumi generates resource names. Here are some common scenarios you can achieve:</p>
<h3 id="disable-auto-naming">Disable Auto-naming</h3>
<p>If you want complete control over your resource names, you can disable Pulumi auto-naming entirely:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#0550ae">pulumi:autonaming</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">  </span><span style="color:#0550ae">mode</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>disabled<span style="color:#fff">
</span></span></span></code></pre></div><p>In this mode, Pulumi will require you to provide explicit physical names for all resources.</p>
<h3 id="use-logical-names-as-is">Use Logical Names As-Is</h3>
<p>For scenarios where you want Pulumi to copy exactly the logical names to become the physical names, you can use the <code>verbatim</code> mode:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#0550ae">pulumi:autonaming</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">  </span><span style="color:#0550ae">mode</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>verbatim<span style="color:#fff">
</span></span></span></code></pre></div><p>No random suffixes will be added to the resource names.</p>
<p>Note, when an update requires replacing the resource, Pulumi&rsquo;s default behavior is to create the new resource and then delete the old resource. However, when using verbatim names or patterns without random components, resources that need to be replaced will be deleted before creating the new resource. This can lead to downtime.</p>
<h3 id="custom-naming-patterns">Custom Naming Patterns</h3>
<p>Create your own naming patterns that combine static text, resource information, and random elements:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#0550ae">pulumi:autonaming</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">  </span><span style="color:#0550ae">pattern</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>${project}-${stack}-${name}${alphanum(6)}<span style="color:#fff">
</span></span></span></code></pre></div><p>See the <a href="/docs/concepts/resources/names/#autonaming-configuration">auto-naming configuration documentation</a> to see the full list of available expressions.</p>
<h2 id="see-it-in-action">See It In Action</h2>
<p>Let&rsquo;s look at a practical example. Say you&rsquo;re creating an S3 bucket and a DynamoDB table in your Pulumi program:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">aws</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/aws&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Create an S3 bucket
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">const</span> <span style="color:#1f2328">bucket</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">s3</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Bucket</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;uploads&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Create a DynamoDB table
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">const</span> <span style="color:#1f2328">table</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">dynamodb</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Table</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;users&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">hashKey</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;id&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">attributes</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;id&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">type</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;S&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">billingMode</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;PAY_PER_REQUEST&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>By default, Pulumi would generate names like <code>uploads-ae26f3b</code> and <code>users-4c2dd09</code>. But let&rsquo;s say you want your resources to follow a pattern that includes your project and stack name. You can configure this in your stack configuration file (<code>Pulumi.&lt;stack-name&gt;.yaml</code>):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#0550ae">config</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">  </span><span style="color:#0550ae">pulumi:autonaming</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">pattern</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>${project}-${stack}-${name}<span style="color:#fff">
</span></span></span></code></pre></div><p>Now when you run <code>pulumi up</code>, your resources will be created with predictable names:</p>
<ul>
<li>S3 bucket: <code>myproject-dev-uploads</code></li>
<li>DynamoDB table: <code>myproject-dev-users</code></li>
</ul>
<p>You can also set different patterns for specific providers or resource types:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#0550ae">config</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">  </span><span style="color:#0550ae">pulumi:autonaming</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">pattern</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>${project}-${stack}-${name}<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">providers</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">      </span><span style="color:#0550ae">aws</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">        </span><span style="color:#0550ae">resources</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">          </span><span style="color:#0a3069">&#34;aws:s3/bucket:Bucket&#34;</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">            </span><span style="color:#0550ae">pattern</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>${name}-${stack}-${alphanum(6)}<span style="color:#fff">
</span></span></span></code></pre></div><p>With this configuration, you&rsquo;ll get:</p>
<ul>
<li>S3 bucket: <code>uploads-dev-x7yz9n</code> (with a random suffix for global uniqueness)</li>
<li>DynamoDB table: <code>myproject-dev-users</code> (following the default pattern)</li>
</ul>
<h3 id="configuration-syntax">Configuration Syntax</h3>
<p>The configuration syntax differs slightly depending on where you define it:</p>
<p>In your project file <code>Pulumi.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#0550ae">config</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">  </span><span style="color:#0550ae">pulumi:autonaming</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">value</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">      </span><span style="color:#0550ae">mode</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>verbatim<span style="color:#fff">
</span></span></span></code></pre></div><p>In your stack configuration file <code>Pulumi.&lt;stack-name&gt;.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#0550ae">config</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">  </span><span style="color:#0550ae">pulumi:autonaming</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">mode</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>verbatim<span style="color:#fff">
</span></span></span></code></pre></div><p>The same applies to other configuration patterns shown above - use the <code>value:</code> key in project-level configuration, but omit it in stack-level configuration.</p>
<h2 id="getting-started">Getting Started</h2>
<p>To use the auto-naming configuration feature, you&rsquo;ll need:</p>
<ol>
<li>Pulumi CLI 3.146.0 or later</li>
<li>The following minimum provider versions (as applicable):
<ul>
<li>AWS provider 6.66.0 or later</li>
<li>Azure Native provider 2.78.0 or later</li>
<li>Azure Classic provider 6.14.0 or later</li>
<li>Google Cloud Platform provider 8.11.0 or later</li>
<li>Kubernetes provider 4.20.0 or later</li>
<li>AWS Cloud Control provider 1.21.0 or later</li>
</ul>
</li>
</ol>
<p>Once you have the required versions installed, simply add your desired auto-naming configuration to your Pulumi configuration file.</p>
<p>For complete documentation and advanced usage scenarios, visit our <a href="/docs/intro/concepts/resources/names/#auto-naming-configuration">resource auto-naming documentation</a>.</p>
<h2 id="general-availability">General Availability</h2>
<p>We&rsquo;re excited to announce that the auto-naming feature is now generally available across our major cloud providers. This release marks an important milestone in Pulumi&rsquo;s evolution, delivering a robust and flexible solution for resource naming.</p>
<p>Thank you to everyone who participated in the <a href="https://github.com/pulumi/pulumi/discussions/17592">RFC discussion</a> and the preview period and for providing valuable feedback. Your input has been invaluable in creating a solution that works for diverse use cases while maintaining Pulumi&rsquo;s core strengths.</p>
<p>If you have any questions or feedback about the resource auto-naming feature, please don&rsquo;t hesitate to reach out to us on GitHub or in the <a href="https://slack.pulumi.com">Pulumi Community Slack</a>.</p>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Pulumi + Azure Deployment Environments: Better Together for Enterprise Developers]]></title>
            <link href="https://mikhail.io/2024/05/azure-deployment-environments/"/>
            <id>https://mikhail.io/2024/05/azure-deployment-environments/</id>
            
            <published>2024-05-21T00:00:00+00:00</published>
            <updated>2024-05-21T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Author Azure Deployment Environments definitions with Pulumi using your favorite programming language.</blockquote><p>We are excited to announce the support for authoring <a href="https://learn.microsoft.com/en-us/azure/deployment-environments/">Azure Deployment Environments (ADE)</a> environment definitions in Pulumi Infrastructure as Code (IaC) empowering developers to self-serve app infrastructure required to deploy and test cloud-based applications. With Pulumi support, you can now manage your Azure resources in these environments using the same familiar programming model and the full power of our IaC platform.</p>
<p>Azure Deployment Environments is a service that enables developers to quickly spin up app infrastructure with project-based templates, all while maintaining centralized management and governance. Azure Deployment Environments also provides developers with an intuitive self-service portal where they can choose a curated, project-specific template to deploy new environments with just a few clicks. Thanks to the <a href="https://learn.microsoft.com/en-us/azure/deployment-environments/how-to-configure-extensibility-generic-container-image">ADE extensibility model</a>, you can now turn any Pulumi program into an environment definition and use it directly to securely provision application infrastructure in Azure. This post will guide you through creating ADE templates and deploying resources from the Microsoft developer portal.</p>
<p><img src="./devportal.png" alt="Developer Portal"></p>
<h2 id="how-it-works">How It Works</h2>
<p>Azure Deployment Environments natively supports the Azure Resource Manager (ARM) and Bicep IaC frameworks. In addition, ADE provides an extensibility model that enables customers to create custom container images to author environment definitions.</p>
<p>The Azure Deployment Environments and Pulumi teams collaborated to publish a standard container image that our joint customers can use directly to run Pulumi programs in ADE: <a href="https://hub.docker.com/r/pulumi/azure-deployment-environments"><code>pulumi/azure-deployment-environments</code></a>. The image leverages ADE’s extensibility model to run Pulumi programs in the container.</p>
<p>When used with Pulumi, each environment definition consists of two components:</p>
<ol>
<li>
<p>An <code>environment.yaml</code> file that describes the environment by referencing the Pulumi Docker image and pointing to a Pulumi program. Here is an example of such a file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#0550ae">name</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>MyPulumiEnvironment<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">version</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0550ae">1.0.0</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">summary</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>My First Pulumi-Enabled Environment<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">description</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>Deploys a Pulumi stack into ADE<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">runner</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>pulumi/azure-deployment-environments:latest<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">templatePath</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>Pulumi.yaml<span style="color:#fff">
</span></span></span></code></pre></div></li>
<li>
<p>A Pulumi program that defines the resources to deploy. This program can use any of the <a href="https://www.pulumi.com/docs/clouds/azure/">Pulumi Azure providers</a> to define resources in the environment. Here is a very simple Pulumi program defined in YAML:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#0550ae">name</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>ade-pulumi<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">runtime</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>yaml<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">description</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>My first Pulumi program in ADE<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">config</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">resource-group-name</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">type</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>string<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">resources</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#0550ae">sa</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">type</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>azure-native:storage:StorageAccount<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">properties</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">kind</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>Storage<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">resourceGroupName</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>${resource-group-name}<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">sku</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">        </span><span style="color:#0550ae">name</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>Standard_LRS<span style="color:#fff">
</span></span></span></code></pre></div></li>
</ol>
<p>This is all you need to define a Pulumi environment in ADE. If you prefer to use a programming language like C#, TypeScript, Python, or Go, you will also need to add the code next to the <code>Pulumi.yaml</code> file.</p>
<p>Once the environment definition is added to the catalog, a user simply chooses an applicable environment in their developer portal. After accepting parameter values, ADE will run the Pulumi program in the container to provision Azure resources. In the case of the above program, it will create a new Storage Account in the environment, and the user will be able to use the resources once they are provisioned by Pulumi.</p>
<h2 id="getting-started">Getting Started</h2>
<p>To get started, you must have the <strong>Azure Deployment Environments</strong> service configured. You can manually create one in the Azure Portal by following the <a href="https://learn.microsoft.com/en-us/azure/deployment-environments/quickstart-create-and-configure-devcenter">instructions</a> or using our <a href="https://github.com/pulumi/azure-deployment-environments/tree/main/Provisioning/ade">sample Pulumi program</a> to provision all the required resources.</p>
<p>Once you have an ADE instance, you can start authoring your <strong>environment definitions</strong>. You can use the <a href="https://github.com/pulumi/azure-deployment-environments/tree/main/Environments">Pulumi Azure Deployment Environments samples</a> as a starting point. The folder contains four simple Pulumi programs in four different languages, each creating a Storage Account in the environment. The programs also show how to use parameters to integrate with ADE’s configuration system.</p>
<p>Once you have the definitions, you will <strong>publish them to a GitHub or Azure DevOps repository</strong> and <a href="https://learn.microsoft.com/en-us/azure/deployment-environments/how-to-configure-catalog?tabs=DevOpsRepoMSI"><strong>attach the repository as a Catalog in Azure Deployment Environments</strong></a>. You can do so manually using the Azure Portal or configure it within your Pulumi project, as illustrated in <a href="https://github.com/pulumi/azure-deployment-environments/blob/1a9633cd31977be2b78cd727b21225ff7b48913d/Provisioning/ade/index.ts#L132-L141">our sample</a>.</p>
<p>Finally, <strong>navigate to the <a href="https://devportal.microsoft.com">Microsoft developer portal</a></strong> to create a new environment. You will see the list of available templates, including the ones you just published.</p>
<h2 id="bring-a-custom-container-image">Bring a Custom Container Image</h2>
<p>The Pulumi container image <a href="https://hub.docker.com/r/pulumi/azure-deployment-environments"><code>pulumi/azure-deployment-environments</code></a> is a great starting point for your environment definitions. However, if you want to customize the behavior to your needs, you can also bring your own container image to run Pulumi programs in ADE. To do so, refer to the <a href="https://aka.ms/ade/pulumi-docs">Configure a container image to execute deployments with Pulumi</a> article on Microsoft Learn.</p>
<p>One benefit of using a custom image is the ability to provide a Pulumi Cloud access token to the image via the <code>PULUMI_ACCESS_TOKEN</code> environment variable. This allows the image to authenticate with the Pulumi Cloud backend without requiring additional configuration. Pulumi Cloud securely encrypts and stores your infrastructure state, manages secrets, provides search and clear visibility across all your cloud resources, runs remote deployments, integrates with CI/CD pipelines, detects configuration drift, and enables centralized policy enforcement. Additional features such as RBAC and audit logging enable your team to collaborate easily, ship faster, more securely and with confidence for every deployment.</p>
<p>If you have a suggestion for improving the standard Pulumi container image, please <a href="https://github.com/pulumi/azure-deployment-environments/issues/new">open an issue</a> on our GitHub repository. We are always looking for ways to improve the experience for our Azure users.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Together with the Azure Deployment Environments team, Pulumi is excited to enable our shared customers to take advantage of the ADE extensibility model, Pulumi IaC platform, and empower developers with self-service of Azure App infrastructure, within enterprise guardrails. We hope that this new capability will help you streamline your development processes and make it easier for your teams to provision new environments in Azure quickly.</p>
<p>If you want to learn more:</p>
<ul>
<li>Attend our workshop <a href="https://www.pulumi.com/resources/platform-engineering-with-azure-pulumi/">Platform Engineering with Microsoft Azure and Pulumi</a> that takes place on June 20</li>
<li>Read the <a href="https://aka.ms/build24/ade-blog">announcement on the Azure blog</a></li>
<li>Explore the code at <a href="https://github.com/pulumi/azure-deployment-environments"><code>pulumi/azure-deployment-environments</code></a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Infrastructure as Code with Java and Pulumi]]></title>
            <link href="https://mikhail.io/2022/05/infrastructure-as-code-with-java-and-pulumi/"/>
            <id>https://mikhail.io/2022/05/infrastructure-as-code-with-java-and-pulumi/</id>
            
            <published>2022-05-04T00:00:00+00:00</published>
            <updated>2022-05-04T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Learn about Pulumi&rsquo;s support for Java and JVM languages, which enable you to use Infrastructure As Code on any Cloud with the JVM ecosystem.</blockquote><p>Infrastructure has become a core part of application development as modern cloud capabilities such as microservices, containers, serverless, and data stores define your application&rsquo;s architecture. The term &ldquo;infrastructure&rdquo; covers all of the cloud resources your application needs to run. Modern architectures require thinking deeply about infrastructure while building your application, instead of treating it as an afterthought. Pulumi&rsquo;s approach helps developers, infrastructure engineers, and platform teams work together to leverage everything the modern cloud has to offer.</p>
<p>Pulumi has worked with hundreds of companies to get cloud applications into production, and Java has quickly risen to become one of the most frequently requested features by the community. Pulumi is an open source product, and we are grateful to our awesome community members who bootstrapped Pulumi for Java last year and were instrumental in helping us with this public preview. Thank you to <a href="https://twitter.com/pawelprazak">Paweł Prażak</a> and his <a href="https://virtuslab.com">VirtusLab</a> colleagues!</p>
<p>Pulumi supports Java for all of your modern infrastructure as code needs, this means you can build, deploy, and manage your infrastructure on any cloud—including all of AWS, Azure, Google Cloud, Kubernetes, Oracle Cloud, and more—using Java and other JVM languages. With Pulumi, you will have the entire cloud at your fingertips without ever having to leave your code editor, while using production-ready infrastructure as code techniques.</p>
<h2 id="what-is-pulumi">What is Pulumi?</h2>
<p>Pulumi lets you build, deploy, and manage infrastructure on any cloud using general-purpose programming languages (TypeScript, Python, Go, .NET, Java) and markup languages (YAML, CUE) to express your application&rsquo;s infrastructure needs, using a powerful technique called &ldquo;infrastructure as code.&rdquo; You declare desired infrastructure, and an engine provisions it for you, so that it&rsquo;s automated, easy to replicate, and robust enough for demanding production requirements. Pulumi takes this approach a step further by leveraging programming languages and software engineering tools to make modern cloud infrastructure patterns, such as containers and serverless programs, easy and first-class citizens.</p>
<p>With Pulumi for Java you can:</p>
<ul>
<li>
<p><strong>Declare infrastructure</strong> using programs, classes, and libraries written in Java or other JVM languages (Kotlin, Scala, Clojure, Groovy, etc.).</p>
</li>
<li>
<p><strong>Automatically create, update, or delete cloud resources</strong> using Pulumi&rsquo;s infrastructure as code engine, removing manual point-and-clicking in web consoles and ad-hoc scripts.</p>
</li>
<li>
<p><strong>Use your favorite IDEs and tools</strong>, including IntelliJ IDEA and Visual Studio Code, taking advantage of features like auto-completion, refactoring, and interactive documentation.</p>
</li>
<li>
<p><strong>Catch mistakes early on</strong> with standard compiler errors, analyzers, and an infrastructure-specific policy engine for enforcing security, compliance, and best practices.</p>
</li>
<li>
<p><strong>Reuse any existing Java package</strong>, or distribute your own, whether that&rsquo;s for infrastructure best practices, productivity, or just general programming patterns.</p>
</li>
<li>
<p><strong>Deploy continuously, predictably, and reliably</strong> using GitHub Actions, or one of over a dozen CI integrations.</p>
</li>
<li>
<p><strong>Build scalable cloud applications</strong> using cloud native technologies like Kubernetes, Docker containers, serverless functions, and PaaS services into your core development experience, bringing them closer to your application code.</p>
</li>
</ul>
<p>Pulumi&rsquo;s free open source SDK, which includes a CLI and an assortment of libraries, enables these capabilities.</p>
<h2 id="example-provision-a-gke-cluster-with-a-kubernetes-namespace">Example: Provision a GKE cluster with a Kubernetes namespace</h2>
<p>The following Java snippet demonstrates the power of Pulumi for Java (<a href="https://github.com/pulumi/examples/tree/master/gcp-java-gke-hello-world">full source code</a>). The program defines a Google Kubernetes Engine cluster, calculates its <code>kubeconfig</code> and exports it for user&rsquo;s needs, and deploys a Kubernetes namespace into the newly provisioned cluster.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#cf222e">package</span><span style="color:#fff"> </span><span style="color:#24292e">gke_sample</span><span style="color:#1f2328">;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#cf222e">import</span><span style="color:#fff"> </span><span style="color:#24292e">java.util.*</span><span style="color:#1f2328">;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#cf222e">import</span><span style="color:#fff"> </span><span style="color:#24292e">java.io.*</span><span style="color:#1f2328">;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#cf222e">import</span><span style="color:#fff"> </span><span style="color:#24292e">java.nio.*</span><span style="color:#1f2328">;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#cf222e">import</span><span style="color:#fff"> </span><span style="color:#24292e">com.pulumi.*</span><span style="color:#1f2328">;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#cf222e">import</span><span style="color:#fff"> </span><span style="color:#24292e">com.pulumi.gcp.container.*</span><span style="color:#1f2328">;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#cf222e">import</span><span style="color:#fff"> </span><span style="color:#24292e">com.pulumi.kubernetes.core_v1.*</span><span style="color:#1f2328">;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#cf222e">public</span><span style="color:#fff"> </span><span style="color:#cf222e">class</span> <span style="color:#1f2328">Program</span><span style="color:#fff"> </span><span style="color:#1f2328">{</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">   </span><span style="color:#cf222e">private</span><span style="color:#fff"> </span><span style="color:#cf222e">static</span><span style="color:#fff"> </span><span style="color:#cf222e">void</span><span style="color:#fff"> </span><span style="color:#6639ba">stack</span><span style="color:#1f2328">(</span>Context<span style="color:#fff"> </span>ctx<span style="color:#1f2328">)</span><span style="color:#fff"> </span><span style="color:#1f2328">{</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">       </span><span style="color:#57606a">// Create a GKE cluster</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">       </span><span style="color:#cf222e">var</span><span style="color:#fff"> </span>cluster<span style="color:#fff"> </span><span style="color:#0550ae">=</span><span style="color:#fff"> </span><span style="color:#cf222e">new</span><span style="color:#fff"> </span>Cluster<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;mygke&#34;</span><span style="color:#1f2328">,</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">           </span>ClusterArgs<span style="color:#1f2328">.</span><span style="color:#1f2328">builder</span><span style="color:#1f2328">()</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">               </span><span style="color:#1f2328">.</span><span style="color:#1f2328">initialNodeCount</span><span style="color:#1f2328">(</span>1<span style="color:#1f2328">)</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">               </span><span style="color:#1f2328">.</span><span style="color:#1f2328">minMasterVersion</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;1.20.7&#34;</span><span style="color:#1f2328">)</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">               </span><span style="color:#1f2328">.</span><span style="color:#1f2328">build</span><span style="color:#1f2328">()</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">       </span><span style="color:#1f2328">);</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">       </span><span style="color:#57606a">// Build and export a Kubeconfig for the newly created cluster.</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">       </span><span style="color:#cf222e">var</span><span style="color:#fff"> </span>kubeconfig<span style="color:#fff"> </span><span style="color:#0550ae">=</span><span style="color:#fff"> </span>Utils<span style="color:#1f2328">.</span><span style="color:#1f2328">buildKubeconfig</span><span style="color:#1f2328">(</span>cluster<span style="color:#1f2328">);</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">       </span>ctx<span style="color:#1f2328">.</span><span style="color:#1f2328">export</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;kubeconfig&#34;</span><span style="color:#1f2328">,</span><span style="color:#fff"> </span>kubeconfig<span style="color:#1f2328">);</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">       </span><span style="color:#57606a">// Create a Kubernetes provider instance that uses our cluster from above.</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">       </span><span style="color:#cf222e">var</span><span style="color:#fff"> </span>clusterProvider<span style="color:#fff"> </span><span style="color:#0550ae">=</span><span style="color:#fff"> </span><span style="color:#cf222e">new</span><span style="color:#fff"> </span>Provider<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;gke-provider&#34;</span><span style="color:#1f2328">,</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">           </span>ProviderArgs<span style="color:#1f2328">.</span><span style="color:#1f2328">builder</span><span style="color:#1f2328">().</span><span style="color:#1f2328">kubeconfig</span><span style="color:#1f2328">(</span>kubeconfig<span style="color:#1f2328">).</span><span style="color:#1f2328">build</span><span style="color:#1f2328">());</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">       </span><span style="color:#57606a">// Create a Kubernetes Namespace</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">       </span><span style="color:#cf222e">var</span><span style="color:#fff"> </span>ns<span style="color:#fff"> </span><span style="color:#0550ae">=</span><span style="color:#fff"> </span><span style="color:#cf222e">new</span><span style="color:#fff"> </span>Namespace<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;test&#34;</span><span style="color:#1f2328">,</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">           </span>NamespaceArgs<span style="color:#1f2328">.</span><span style="color:#1f2328">Empty</span><span style="color:#1f2328">,</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">           </span>CustomResourceOptions<span style="color:#1f2328">.</span><span style="color:#1f2328">builder</span><span style="color:#1f2328">().</span><span style="color:#1f2328">provider</span><span style="color:#1f2328">(</span>clusterProvider<span style="color:#1f2328">).</span><span style="color:#1f2328">build</span><span style="color:#1f2328">()</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">       </span><span style="color:#1f2328">);</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">   </span><span style="color:#1f2328">}</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">   </span><span style="color:#cf222e">public</span><span style="color:#fff"> </span><span style="color:#cf222e">static</span><span style="color:#fff"> </span><span style="color:#cf222e">void</span><span style="color:#fff"> </span><span style="color:#6639ba">main</span><span style="color:#1f2328">(</span>String<span style="color:#0550ae">[]</span><span style="color:#fff"> </span>args<span style="color:#1f2328">)</span><span style="color:#fff"> </span><span style="color:#1f2328">{</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">       </span>Pulumi<span style="color:#1f2328">.</span><span style="color:#1f2328">run</span><span style="color:#1f2328">(</span>App<span style="color:#1f2328">::</span>stack<span style="color:#1f2328">);</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">   </span><span style="color:#1f2328">}</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#1f2328">}</span><span style="color:#fff">
</span></span></span></code></pre></div><p>Resources are defined declaratively using class constructors and argument builders. Dependencies between resources are managed automatically by the Pulumi engine based on the way you use variables in the program. You are free to use any libraries, helper functions—for instance, to build the kubeconfig string above, classes, if statements, for loops, and all the other tools available to Java developers.</p>
<h2 id="why-is-java-great-for-infrastructure-too">Why is Java great for infrastructure too?</h2>
<p>Many of us love using Java to author our applications, so why not use it for infrastructure as code too? By using Java, you get many helpful features for your infrastructure code:</p>
<ul>
<li>
<p><strong>Familiarity</strong>: No need to learn DSLs or markup templating languages.</p>
</li>
<li>
<p><strong>Expressiveness</strong>: Use loops, conditionals, pattern matching, async code, and more, to dynamically create infrastructure that meets the target environment&rsquo;s needs.</p>
</li>
<li>
<p><strong>Abstraction</strong>: Encapsulate common patterns into classes and functions to hide complexity and avoid copy-and-pasting the same boilerplate repeatedly.</p>
</li>
<li>
<p><strong>Sharing and reuse</strong>: Tap into a community of cloud applications and infrastructure experts by sharing and reusing Maven or Gradle packages with your team or the global community.</p>
</li>
<li>
<p><strong>Productivity</strong>: Use your favorite IDE and get statement completion, go to definition, live error checking, refactoring, static analysis, and interactive documentation.</p>
</li>
<li>
<p><strong>Project organization</strong>: Use common code structuring techniques to manage your infrastructure across one or more projects.</p>
</li>
<li>
<p><strong>Application lifecycle</strong>: Use existing ALM systems and techniques to manage and deploy your infrastructure projects, including source control, code review, testing, and continuous integration (CI) and delivery (CD).</p>
</li>
</ul>
<p>Pulumi unlocks access to the entire JVM ecosystem—something that&rsquo;s easy to take for granted but is missing from other solutions based on DSLs or CLI scripts. This approach also helps developers and operators work better together using a shared foundation. Add all of the above together, and you get things done faster and more reliably.</p>
<h2 id="join-the-community-and-get-started">Join the community and get started</h2>
<p>The first preview of Pulumi for Java includes support for the entire breadth of services in AWS, Azure, Google Cloud, and more. Give Pulumi a try, visit the <a href="https://www.pulumi.com/docs/languages-sdks/java/">Pulumi for Java docs</a>.</p>
<p>There you will find several instructions on installing and getting started with Pulumi for Java. The following resources provide additional useful information:</p>
<ul>
<li>
<p><a href="https://github.com/pulumi/examples/tree/master/gcp-java-gke-hello-world">Full example code</a></p>
</li>
<li>
<p><a href="https://www.pulumi.com/docs/get-started/">Getting started with Pulumi</a></p>
</li>
<li>
<p><a href="https://www.pulumi.com/docs/concepts/">General Pulumi overview (concepts and architecture)</a></p>
</li>
</ul>
<p>Although Pulumi for Java is listed in &ldquo;preview&rdquo; status, it supports all of the most essential Pulumi programming model features (and the rest is on its way). Our goal is to gather feedback over the next few weeks, and we will be working hard to improve the Java experience across the board, including more examples and better documentation.</p>
<p>Pulumi is <a href="https://github.com/pulumi/pulumi">open source on GitHub</a> and you can find the Java plugin at <a href="https://github.com/pulumi/pulumi-java">pulumi/pulumi-java</a>.
And you&rsquo;re welcome to <a href="https://slack.pulumi.com/">join the community in Slack</a> to discuss your scenarios, ideas, and to get any needed assistance from the team and other end users.</p>
<p>We look forward to seeing the new and amazing cloud applications you build with Pulumi for Java!</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/java" term="java" label="Java" />
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                             
                                <category scheme="https://mikhail.io/tags/kubernetes" term="kubernetes" label="Kubernetes" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Get Up and Running with Azure Synapse and Pulumi]]></title>
            <link href="https://mikhail.io/2021/12/get-up-and-running-with-azure-synapse-and-pulumi/"/>
            <id>https://mikhail.io/2021/12/get-up-and-running-with-azure-synapse-and-pulumi/</id>
            
            <published>2021-12-04T00:00:00+00:00</published>
            <updated>2021-12-04T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Use infrastructure as code to automate deployment of an Azure Synapse workspace</blockquote><p>Azure Synapse is an integrated analytics service that combines enterprise data warehousing of Azure SQL Data Warehouse and Big Data analytics of Apache Spark. Azure Synapse is a managed service well integrated with other Azure services for data ingestion and business analytics.</p>
<p>You could use the Azure portal to get started with Azure Synapse, but it can be hard to define sophisticated infrastructure for your analytics pipeline using the portal alone, and many users need to apply version control to their cloud configurations.</p>
<p>The alternative is to use an <a href="/what-is/what-is-infrastructure-as-code/">infrastructure as code</a> tool to automate building and deploying cloud resources. This article demonstrates how to provision an Azure Synapse workspace using Pulumi and general-purpose programming languages like Python and C#.</p>
<h2 id="azure-synapse-components">Azure Synapse Components</h2>
<p>Let&rsquo;s start by introducing the components required to provision a basic Azure Synapse workspace. To follow along with the <a href="https://docs.microsoft.com/en-us/azure/synapse-analytics/get-started">Synapse Getting Started Guide</a>, you need the following key Azure infrastructure components:</p>
<ul>
<li><strong>Resource Group</strong> to contain all other resources.</li>
<li><strong>Storage Account</strong> to store input data and analytics artifacts.</li>
<li><strong>Azure Synapse Workspace</strong>—a collaboration boundary for cloud-based analytics in Azure.</li>
<li><strong>SQL Pool</strong>—a dedicated Synapse SQL pool to run T-SQL based analytics.</li>
<li><strong>Spark Pool</strong> to use Apache Spark analytics.</li>
<li><strong>IP Filters</strong> and <strong>Role Assignments</strong> for secure access control.</li>
</ul>
<h2 id="infrastructure-as-code">Infrastructure as Code</h2>
<p>Let&rsquo;s walk through the steps to build a workspace with all the components mentioned above. We&rsquo;ll use Pulumi to provision the necessary resources. Feel free to pick the language of your choice that will apply to all code snippets.</p>
<p>You can check out the <a href="https://github.com/pulumi/examples/tree/master/aws-ts-lambda-thumbnailer">full source code</a> in the Pulumi Examples.</p>
<h3 id="resource-group">Resource Group</h3>
<p>Let&rsquo;s start by defining a resource group to contain all other resources. Be sure to adjust its name and region to your preferred values.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>resource_group <span style="color:#0550ae">=</span> resources<span style="color:#0550ae">.</span>ResourceGroup<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;resourceGroup&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    resource_group_name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;synapse-rg&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    location<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;westus2&#34;</span><span style="color:#1f2328">)</span>
</span></span></code></pre></div><h3 id="data-lake-storage-account">Data Lake Storage Account</h3>
<p>Synapse workspace will store data in a data lake storage account. We use a Standard Read-Access Geo-Redundant Storage account (SKU <code>Standard_RAGRS</code>) for this purpose. Make sure to change the <code>accountName</code> to your own globally unique name.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>storage_account <span style="color:#0550ae">=</span> storage<span style="color:#0550ae">.</span>StorageAccount<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;storageAccount&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    resource_group_name<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    location<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>location<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    account_name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;yoursynapsesa&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    access_tier<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;Hot&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    enable_https_traffic_only<span style="color:#0550ae">=</span><span style="color:#cf222e">True</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    is_hns_enabled<span style="color:#0550ae">=</span><span style="color:#cf222e">True</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    kind<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;StorageV2&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    sku<span style="color:#0550ae">=</span>storage<span style="color:#0550ae">.</span>SkuArgs<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>        name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;Standard_RAGRS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">))</span>
</span></span></code></pre></div><p>Let&rsquo;s build a data lake URL for this storage account.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>data_lake_storage_account_url <span style="color:#0550ae">=</span> storage_account<span style="color:#0550ae">.</span>name<span style="color:#0550ae">.</span>apply<span style="color:#1f2328">(</span><span style="color:#cf222e">lambda</span> name<span style="color:#1f2328">:</span> <span style="color:#0a3069">f</span><span style="color:#0a3069">&#34;https://</span><span style="color:#0a3069">{</span>name<span style="color:#0a3069">}</span><span style="color:#0a3069">.dfs.core.windows.net&#34;</span><span style="color:#1f2328">)</span>
</span></span></code></pre></div><p>We&rsquo;ll use the <code>users</code> blob container as the analytics file system.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>users <span style="color:#0550ae">=</span> storage<span style="color:#0550ae">.</span>BlobContainer<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;users&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    resource_group_name<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    account_name<span style="color:#0550ae">=</span>storage_account<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    container_name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;users&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    public_access<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;None&#34;</span><span style="color:#1f2328">)</span>
</span></span></code></pre></div><h3 id="synapse-workspace">Synapse Workspace</h3>
<p>It&rsquo;s time to use all of the above to provision an Azure Synapse workspace! Adjust the name and the SQL credentials in the definition below.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>workspace <span style="color:#0550ae">=</span> synapse<span style="color:#0550ae">.</span>Workspace<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;workspace&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    resource_group_name<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    location<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>location<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    workspace_name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;mysynapse&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    default_data_lake_storage<span style="color:#0550ae">=</span>synapse<span style="color:#0550ae">.</span>DataLakeStorageAccountDetailsArgs<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>        account_url<span style="color:#0550ae">=</span>data_lake_storage_account_url<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        filesystem<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;users&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>    identity<span style="color:#0550ae">=</span>synapse<span style="color:#0550ae">.</span>ManagedIdentityArgs<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>        <span style="color:#6639ba">type</span><span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;SystemAssigned&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>    sql_administrator_login<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;sqladminuser&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    sql_administrator_login_password<span style="color:#0550ae">=</span>random<span style="color:#0550ae">.</span>RandomPassword<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;workspacePwd&#34;</span><span style="color:#1f2328">,</span> length<span style="color:#0550ae">=</span><span style="color:#0550ae">12</span><span style="color:#1f2328">)</span><span style="color:#0550ae">.</span>result<span style="color:#1f2328">)</span>
</span></span></code></pre></div><blockquote>
<p>Note that we also defined a system-assigned managed identity for the workspace.</p></blockquote>
<h3 id="security-setup">Security Setup</h3>
<p>You need to allow access to the workspace with a firewall rule. The following is a blank access rule but feel free to restrict it to your target IP range.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>allow_all <span style="color:#0550ae">=</span> synapse<span style="color:#0550ae">.</span>IpFirewallRule<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;allowAll&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    resource_group_name<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    workspace_name<span style="color:#0550ae">=</span>workspace<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    rule_name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;allowAll&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    end_ip_address<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;255.255.255.255&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    start_ip_address<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;0.0.0.0&#34;</span><span style="color:#1f2328">)</span>
</span></span></code></pre></div><p>The following snippet assigns the <strong>Storage Blob Data Contributor</strong> role to the workspace managed identity and your target user. If you use the Azure CLI, run <code>az ad signed-in-user show --query=objectId</code> to look up your user ID.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>subscription_id <span style="color:#0550ae">=</span> resource_group<span style="color:#0550ae">.</span>id<span style="color:#0550ae">.</span>apply<span style="color:#1f2328">(</span><span style="color:#cf222e">lambda</span> <span style="color:#6639ba">id</span><span style="color:#1f2328">:</span> <span style="color:#6639ba">id</span><span style="color:#0550ae">.</span>split<span style="color:#1f2328">(</span><span style="color:#0a3069">&#39;/&#39;</span><span style="color:#1f2328">)[</span><span style="color:#0550ae">2</span><span style="color:#1f2328">])</span>
</span></span><span style="display:flex;"><span>role_definition_id <span style="color:#0550ae">=</span> subscription_id<span style="color:#0550ae">.</span>apply<span style="color:#1f2328">(</span><span style="color:#cf222e">lambda</span> <span style="color:#6639ba">id</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">f</span><span style="color:#0a3069">&#34;/subscriptions/</span><span style="color:#0a3069">{</span><span style="color:#6639ba">id</span><span style="color:#0a3069">}</span><span style="color:#0a3069">/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe&#34;</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>authorization<span style="color:#0550ae">.</span>RoleAssignment<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;storageAccess&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    role_assignment_name<span style="color:#0550ae">=</span>random<span style="color:#0550ae">.</span>RandomUuid<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;roleName&#34;</span><span style="color:#1f2328">)</span><span style="color:#0550ae">.</span>result<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    scope<span style="color:#0550ae">=</span>storage_account<span style="color:#0550ae">.</span>id<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    principal_id<span style="color:#0550ae">=</span>workspace<span style="color:#0550ae">.</span>identity<span style="color:#0550ae">.</span>principal_id<span style="color:#0550ae">.</span>apply<span style="color:#1f2328">(</span><span style="color:#cf222e">lambda</span> v<span style="color:#1f2328">:</span> v <span style="color:#0550ae">or</span> <span style="color:#0a3069">&#34;&lt;preview&gt;&#34;</span><span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>    principal_type<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;ServicePrincipal&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    role_definition_id<span style="color:#0550ae">=</span>role_definition_id<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>authorization<span style="color:#0550ae">.</span>RoleAssignment<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;userAccess&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    role_assignment_name<span style="color:#0550ae">=</span>random<span style="color:#0550ae">.</span>RandomUuid<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;userRoleName&#34;</span><span style="color:#1f2328">)</span><span style="color:#0550ae">.</span>result<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    scope<span style="color:#0550ae">=</span>storage_account<span style="color:#0550ae">.</span>id<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    principal_id<span style="color:#0550ae">=</span>config<span style="color:#0550ae">.</span>get<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;userObjectId&#34;</span><span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>    principal_type<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;User&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    role_definition_id<span style="color:#0550ae">=</span>role_definition_id<span style="color:#1f2328">)</span>
</span></span></code></pre></div><h3 id="sql-and-spark-pools">SQL and Spark Pools</h3>
<p>Finally, let&rsquo;s add two worker pools to the Synapse workspace. A SQL pool for T-SQL analytic queries&hellip;</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>sql_pool <span style="color:#0550ae">=</span> synapse<span style="color:#0550ae">.</span>SqlPool<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;sqlPool&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    resource_group_name<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    location<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>location<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    workspace_name<span style="color:#0550ae">=</span>workspace<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    sql_pool_name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;SQLPOOL1&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    collation<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;SQL_Latin1_General_CP1_CI_AS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    create_mode<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;Default&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    sku<span style="color:#0550ae">=</span>synapse<span style="color:#0550ae">.</span>SkuArgs<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>        name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;DW100c&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">))</span>
</span></span></code></pre></div><p>&hellip; and a Spark pool for Big Data analytics.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>spark_pool <span style="color:#0550ae">=</span> synapse<span style="color:#0550ae">.</span>BigDataPool<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;sparkPool&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    resource_group_name<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    location<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>location<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    workspace_name<span style="color:#0550ae">=</span>workspace<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    big_data_pool_name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;Spark1&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    auto_pause<span style="color:#0550ae">=</span>synapse<span style="color:#0550ae">.</span>AutoPausePropertiesArgs<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>        delay_in_minutes<span style="color:#0550ae">=</span><span style="color:#0550ae">15</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        enabled<span style="color:#0550ae">=</span><span style="color:#cf222e">True</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>    auto_scale<span style="color:#0550ae">=</span>synapse<span style="color:#0550ae">.</span>AutoScalePropertiesArgs<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>        enabled<span style="color:#0550ae">=</span><span style="color:#cf222e">True</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        max_node_count<span style="color:#0550ae">=</span><span style="color:#0550ae">3</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        min_node_count<span style="color:#0550ae">=</span><span style="color:#0550ae">3</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>    node_count<span style="color:#0550ae">=</span><span style="color:#0550ae">3</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    node_size<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;Small&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    node_size_family<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;MemoryOptimized&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    spark_version<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;2.4&#34;</span><span style="color:#1f2328">)</span>
</span></span></code></pre></div><h2 id="ready-to-dive-into-analytics">Ready to Dive into Analytics</h2>
<p>Our resource definitions are ready. Run <code>pulumi up</code> to provision your Azure Synapse infrastructure.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ pulumi up
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>Do you want to perform this update? yes
</span></span><span style="display:flex;"><span>Updating <span style="color:#0550ae">(</span>dev<span style="color:#0550ae">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>     Type                                          Name                  Plan
</span></span><span style="display:flex;"><span> +   pulumi:pulumi:Stack                           azure-py-synapse-dev  created
</span></span><span style="display:flex;"><span> +   ├─ azure-native:resources:ResourceGroup       resourceGroup         created
</span></span><span style="display:flex;"><span> +   ├─ azure-native:storage:StorageAccount        storageAccount        created
</span></span><span style="display:flex;"><span> +   ├─ azure-native:storage:BlobContainer         users                 created
</span></span><span style="display:flex;"><span> +   ├─ azure-native:synapse:Workspace             workspace             created
</span></span><span style="display:flex;"><span> +   ├─ random:index:RandomUuid                    roleName              created
</span></span><span style="display:flex;"><span> +   └─ azure-native:authorization:RoleAssignment  storageAccess         created
</span></span><span style="display:flex;"><span> +   ├─ random:index:RandomUuid                    userRoleName          created
</span></span><span style="display:flex;"><span> +   ├─ azure-native:authorization:RoleAssignment  userAccess            created
</span></span><span style="display:flex;"><span> +   ├─ azure-native:synapse:IpFirewallRule        allowAll              created
</span></span><span style="display:flex;"><span> +   ├─ azure-native:synapse:SqlPool               sqlPool               created
</span></span><span style="display:flex;"><span> +   ├─ azure-native:synapse:BigDataPool           sparkPool             created
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> Resources:
</span></span><span style="display:flex;"><span>    + <span style="color:#0550ae">12</span> created
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Duration: 10m51s
</span></span></code></pre></div><p>You can now navigate to the <a href="https://docs.microsoft.com/en-us/azure/synapse-analytics/get-started-analyze-sql-pool">Azure Synapse Quickstart, Step 2</a>, and follow along with the data analysis tutorial.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Azure Synapse is a managed analytics service that accelerates time to insight across data warehouses and big data workloads. A Synapse workspace is a critical component of your cloud infrastructure that you should provision with infrastructure as code and other management best practices.</p>
<p>Pulumi and the native Azure provider open up full access to all types of Azure resources using your favorite programming languages, including Python, C#, and TypeScript. Navigate to the complete Azure Synapse example in <a href="https://github.com/pulumi/examples/tree/master/azure-py-synapse">Python</a>, <a href="https://github.com/pulumi/examples/tree/master/azure-cs-synapse">C#</a>, or <a href="https://github.com/pulumi/examples/tree/master/azure-ts-synapse">TypeScript</a> and get started today.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[How To Deploy Temporal to Azure Kubernetes Service (AKS)]]></title>
            <link href="https://mikhail.io/2021/11/how-to-deploy-temporal-to-azure-kubernetes-aks/"/>
            <id>https://mikhail.io/2021/11/how-to-deploy-temporal-to-azure-kubernetes-aks/</id>
            
            <published>2021-11-11T00:00:00+00:00</published>
            <updated>2021-11-11T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Get up and running with Temporal workflows in Azure and Kubernetes in several CLI commands</blockquote><p>In my article <a href="https://mikhail.io/2020/10/practical-approach-to-temporal-architecture/">A Practical Approach to Temporal Architecture</a>, I outlined the various <a href="https://temporal.io/">Temporal</a> components and how they interact. Today&rsquo;s blog builds on this knowledge and demonstrates an example of deploying Temporal to Kubernetes and, more specifically, to Azure Kubernetes Service (AKS).</p>
<p>My example is self-contained: it provisions a full environment with all the required Azure resources, Temporal service, and application deployment artifacts. Here is a diagram of the cloud infrastructure:</p>
<p><img src="temporal-aks.png" alt="Deployment architecture"></p>
<p>This sample deployment is implemented as a <a href="https://pulumi.com/">Pulumi</a> program in TypeScript. You can find the full code in <a href="https://github.com/mikhailshilkov/temporal-samples/tree/main/azure-aks">my GitHub</a>.</p>
<h2 id="application-code">Application Code</h2>
<p>The <code>workflow</code> folder contains all of the application code. The application is written with Go and consists of two source files:</p>
<ol>
<li><code>helloworld.go</code> - defines a workflow &amp; activity</li>
<li><code>main.go</code> - application entry point.</li>
</ol>
<p>The example deploys a &ldquo;Hello World&rdquo; Temporal application copied from <a href="https://github.com/temporalio/samples-go/blob/master/helloworld/helloworld.go">this Go sample</a>. Once you get it up and running, you can certainly customize the code with your own workflow and activities.</p>
<p>The <code>main.go</code> file does two things.</p>
<p><strong>First</strong>, it spins up a worker:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#1f2328">w</span> <span style="color:#0550ae">:=</span> <span style="color:#1f2328">worker</span><span style="color:#1f2328">.</span><span style="color:#6639ba">New</span><span style="color:#1f2328">(</span><span style="color:#1f2328">c</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;hello-world&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">worker</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Options</span><span style="color:#1f2328">{})</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">w</span><span style="color:#1f2328">.</span><span style="color:#6639ba">RegisterWorkflow</span><span style="color:#1f2328">(</span><span style="color:#1f2328">helloworld</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Workflow</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">w</span><span style="color:#1f2328">.</span><span style="color:#6639ba">RegisterActivity</span><span style="color:#1f2328">(</span><span style="color:#1f2328">helloworld</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Activity</span><span style="color:#1f2328">)</span>
</span></span></code></pre></div><p><strong>Second</strong>, it launches an HTTP server in the same process. The server exposes endpoints to start workflows. The <code>/async?name=&lt;yourname&gt;</code> endpoint starts a new workflow and immediately returns, while the <code>/sync?name=&lt;yourname&gt;</code> blocks and waits for the result of the execution and returns the response.</p>
<p>You can find the implementation in the <a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/workflow/main.go#L22"><code>start</code></a> function. Note that this simplistic starter is specifc to the &ldquo;Hello World&rdquo; workflow as it expects one argument and one result, both strings.</p>
<h2 id="deployment-structure">Deployment Structure</h2>
<p>My program combines three component resources: a MySQL Database, an AKS cluster, and a Temporal deployment. As a result, the main file deploy all of these resources to a single Azure Resource Group:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">resourceGroup</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">resources</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ResourceGroup</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;resourceGroup&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroupName</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">location</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;WestEurope&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">database</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">MySql</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;mysql&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">location</span>: <span style="color:#cf222e">resourceGroup.location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">administratorLogin</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;mikhail&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">administratorPassword</span>: <span style="color:#cf222e">mysqlPassword</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">cluster</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">AksCluster</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;aks&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">location</span>: <span style="color:#cf222e">resourceGroup.location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">kubernetesVersion</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;1.16.13&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">vmSize</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard_DS2_v2&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">vmCount</span>: <span style="color:#cf222e">3</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">temporal</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">Temporal</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;temporal&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">location</span>: <span style="color:#cf222e">resourceGroup.location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">version</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;1.1.1&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">storage</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">type</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;mysql&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">hostName</span>: <span style="color:#cf222e">database.hostName</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">login</span>: <span style="color:#cf222e">database.administratorLogin</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">password</span>: <span style="color:#cf222e">database.administratorPassword</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">cluster</span>: <span style="color:#cf222e">cluster</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">app</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">namespace</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;temporal&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">folder</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;./workflow&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">port</span>: <span style="color:#cf222e">8080</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">webEndpoint</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">temporal</span><span style="color:#1f2328">.</span><span style="color:#1f2328">webEndpoint</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">starterEndpoint</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">temporal</span><span style="color:#1f2328">.</span><span style="color:#1f2328">starterEndpoint</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><p>The rest of the article gives an overview of the building blocks of these three components.</p>
<h2 id="docker-image">Docker Image</h2>
<p>Since the application is deployed to Kubernetes, we need to produce a custom Docker image. The <a href="https://github.com/mikhailshilkov/temporal-samples/blob/main/azure-aks/workflow/Dockerfile"><code>Dockerfile</code></a> builds the Go application and exposes port <code>8080</code> to the outside world so we can access the starter HTTP endpoints.</p>
<p>Pulumi deploys this <code>Dockerfile</code> to Azure in three steps:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L42-L50">Deploy</a> an Azure Container Registry.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L52-L58">Retrieve</a> the registry&rsquo;s admin credentials generated by Azure.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L61-L69">Publish</a> the application image to the registry.</li>
</ul>
<h2 id="mysql-database">MySQL Database</h2>
<p>There are several persistence options supported by Temporal. A straightforward option for Azure users is to deploy an instance of Azure Database for MySQL. It&rsquo;s a fully managed database service where Azure is responsible for uptime and maintenance, and users pay a flat fee per hour.</p>
<p>My example <a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/mysql.ts#L24-L50">provisions</a> an instance of MySQL 5.7 at the Basic tier. The database size is limited to 5 GB.</p>
<p>A final tweak is to <a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/mysql.ts#L52-L58">add</a> a firewall rule for the IP address <code>0.0.0.0</code>, which enables network access to MySQL from any Azure service. Note that this option isn&rsquo;t secure for production workloads: read more in <a href="https://docs.microsoft.com/en-us/azure/mysql/concepts-firewall-rules#connecting-from-azure">Connecting from Azure</a>.</p>
<h2 id="azure-kubernetes-cluster">Azure Kubernetes Cluster</h2>
<p>The example creates a new AKS cluster and deploys the Temporal service and applications components to that cluster.</p>
<p>The <code>AksCluster</code> component:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/cluster.ts#L23-L39">Sets up</a> an Azure Active Directory Application and a Service Principal.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/cluster.ts#L42-L45">Creates</a> an SSH key for the cluster&rsquo;s admin user profile.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/cluster.ts#L48-L86">Provisions</a> a managed Kubernetes cluster base on VM Scale Sets node pool. Feel free to adjust the VM size, count, and the Kubernetes version.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/cluster.ts#L88-L96">Builds</a> the Kubeconfig YAML to connect to the cluster and deploy application components.</li>
</ul>
<h2 id="temporal-service-and-web-console">Temporal Service and Web Console</h2>
<p>Next, we deploy the Temporal Service and Temporal Web Console as two Kubernetes services.</p>
<p>We start with sound groundwork:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L71-L78">Declare</a> a custom Pulumi Kubernetes provider and point it to the Kubeconfig string that we retrieved from the managed cluster.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L71-L78">Grant</a> permission for the managed cluster&rsquo;s service principal to access images from the Azure Container Registry.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L85-L89">Define</a> a new Kubernetes namespace that contains all Temporal deployments and services.</li>
</ul>
<p>Then, we can deploy the Temporal Service:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L91-L103">Stores</a> the MySQL password as a Kubernetes secret.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L136">Refers</a> to the <code>temporalio/auto-setup</code> Docker image provided by Temporal. The image automatically populates the database schema during the first run.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L143-L163">Sets up</a> environment variables to connect to MySQL.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L186-L213">Deploys</a> a <code>ClusterIP</code> service using port <code>7233</code>.</li>
</ul>
<p>The Web Console follows:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L246">Refers</a> to the <code>temporalio/web</code> Docker image provided by Temporal.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L249-L252">Connects</a> to the gRPC endpoint of the Temporal Service.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L275-L301">Deploys</a> a <code>ClusterIP</code> service using port <code>8088</code>.</li>
</ul>
<h2 id="temporal-worker">Temporal Worker</h2>
<p>The final component is a Temporal worker that runs application workflows and activities. In my setup, the worker is a Kubernetes deployment that pulls the custom Docker image from the container registry.</p>
<p>The application component:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L337">Refers</a> to the custom Docker image created above.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L337">Connects</a> to the gRPC endpoint of the Temporal Service.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L337">Deploys</a> a <code>ClusterIP</code> service using port <code>8080</code>.</li>
</ul>
<h2 id="get-started">Get Started</h2>
<p>The Pulumi Command-Line Interface (CLI) runs the deployment. <a href="https://www.pulumi.com/docs/get-started/install/">Install Pulumi</a>, navigate to the folder where you have the example cloned, and run the following commands:</p>
<ol>
<li>Create a new stack (a Pulumi deployment environment):</li>
</ol>
<pre tabindex="0"><code>pulumi stack init dev
</code></pre><ol start="2">
<li>Login to <a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli">Azure CLI</a>:</li>
</ol>
<pre tabindex="0"><code>az login
</code></pre><ol start="3">
<li>Install NPM dependencies:</li>
</ol>
<pre tabindex="0"><code>npm install
</code></pre><ol start="4">
<li>Run <code>pulumi up</code> and confirm when asked if you want to deploy. Azure resources are provisioned:</li>
</ol>
<pre tabindex="0"><code>$ pulumi up...
Performing changes:...

Outputs:
    starterEndpoint: &#34;http://21.55.177.186:8080/async?name=&#34;
    webEndpoint : &#34;http://52.136.6.198:8088&#34;

Resources:+ 27 created
Duration: 6m46s
</code></pre><ol start="5">
<li>The output above prints the endpoints to interact with the application. Run the following command to start a &ldquo;Hello World&rdquo; workflow:</li>
</ol>
<pre tabindex="0"><code>curl $(pulumi stack output starterEndpoint)WorldStarted workflow ID=World, RunID=b4f6db00-bb2f-498b-b620-caad81c91a81%
</code></pre><p>Now, open the <code>webEndpoint</code> URL in your browser and find the workflow (it&rsquo;s probably already in the Completed state).</p>
<h2 id="cost-security-and-further-steps">Cost, Security, and Further Steps</h2>
<p>The deployment above provisions real Azure resources, so be mindful of the related costs. Here is an estimated calculation for the &ldquo;West US 2&rdquo; region:</p>
<ul>
<li>Azure Database for MySQL Gen5 Basic with 1 vCore and 5 GB of storage = $25.32/month</li>
<li>Azure Kubernetes Cluster of 3 VMs type Standard_DS2_v2: 3 x $83.22/month = $249.66/month (but feel free to adjust to your needs)</li>
<li>Azure Container Registry Basic = $5.00/month</li>
</ul>
<p>The total cost for this example is approximately $280 per month.</p>
<p>Whenever you are done experimenting, run <code>pulumi destroy</code> to delete the resources. Note that all the data will be lost after destruction.</p>
<p>You can find the full code in <a href="https://github.com/mikhailshilkov/temporal-samples/tree/main/azure-aks">my GitHub</a>.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/temporal" term="temporal" label="Temporal" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/kubernetes" term="kubernetes" label="Kubernetes" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Deploying new Azure Container Apps with familiar languages]]></title>
            <link href="https://mikhail.io/2021/11/azure-container-apps/"/>
            <id>https://mikhail.io/2021/11/azure-container-apps/</id>
            
            <published>2021-11-02T00:00:00+00:00</published>
            <updated>2021-11-02T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Learn how to deploy Docker containers to Azure Container Apps using Pulumi. A step-by-step guide for building scalable serverless apps in any language.</blockquote><p>Today, <a href="https://aka.ms/containerapps/ignite-blog">Microsoft announced</a> a new general-purpose serverless container platform: <a href="https://aka.ms/containerapps/">Azure Container Apps</a>. Container Apps is a fully managed platform for microservice applications that runs on top of Kubernetes and open-source technologies like KEDA, Envoy, and Dapr.</p>
<p>Container Apps are designed to abstract infrastructure management with flexible serverless containers. Developers can run containers at scale without the burden of standing up and managing a Kubernetes cluster manually.</p>
<p>We are happy to announce same-day support for Azure Container Apps in the Pulumi Azure Native Provider, which covers 100% of the Azure Resource Manager APIs and gives you highest fidelity integration with Azure&rsquo;s resources.</p>
<p>The new service supports a broad range of usage scenarios, including</p>
<ul>
<li>Microservices over HTTP or gRPC</li>
<li>HTTP APIs and websites</li>
<li>Event processing workers</li>
<li>Long-running background jobs</li>
</ul>
<h2 id="serverless-containers">Serverless containers</h2>
<p>Container Apps provide serverless containers for microservice developers.</p>
<ul>
<li><strong>Managed infrastructure</strong>. Microsoft operates the control plane that takes care of orchestrating containers and their configuration, allowing developers to focus on apps, not cloud infrastructure.</li>
<li><strong>Fully integrated auto-scaling with scale to zero</strong>. The platform relies on Kubernetes Event-driven Autoscaling (KEDA) to scale apps and microservices dynamically based on HTTP traffic or event workload.</li>
<li><strong>Consumption pricing</strong>. The billing model is based on the actual resource consumption with per-second granularity. Applications incur charges per request, compute time and memory used with no need to pre-provision a fixed capacity.</li>
<li><strong>Any language, any base image</strong>. Container Apps put no limitation on the container images. You may use an arbitrary base image and host any web server or a console application, ensuring flexibility and interoperability with other container orchestrators.</li>
</ul>
<h2 id="how-it-works">How it works</h2>
<p>Microsoft has built Container Apps as a managed service on the foundation of open-source technology in the Kubernetes ecosystem. This enables teams to build microservices without having to manage Kubernetes directly while providing application portability by leveraging open standards and APIs. Behind the scenes, every container app runs on the Azure Kubernetes Service (AKS). Enabling open-source integrations include:</p>
<p><strong>Envoy</strong>, a built-in ingress controller that exposes user containers internally and externally via HTTP endpoints. It supports multiple container versions with dynamic load balancing and several traffic rollout strategies.</p>
<p><strong>KEDA</strong> enables dynamic auto-scaling for user applications based on the current workload, including scaling down to zero during idle periods.</p>
<p>Container Apps are also tightly integrated with <strong>Dapr</strong>—an open-source runtime system designed to support cloud-native microservice applications. It provides extra blocks for service discovery, state management, asynchronous message passing.</p>
<h2 id="how-its-different">How it&rsquo;s different</h2>
<p>A few other services provided by Microsoft Azure and other cloud providers operate in the space of running container-based workloads:</p>
<p><strong>Azure Kubernetes Service</strong> delivers the full power of Kubernetes but requires expertise in configuring and operating the cluster. When building on AKS, users handle most infrastructure management aspects themselves. Instead, Container Apps provide a platform built on top of AKS, focusing on developer productivity and switching to consumption-based pricing.</p>
<p><strong>Azure Container Instances</strong> (ACI) provides atomic infrastructure units with per-usage pricing. However, ACI comes without higher-level functionality like auto-scaling, load balancing, versioning, and managed rollouts.</p>
<p><strong>Azure App Service</strong> is a Platform-as-a-Service comparable to Container Apps in terms of being simple-to-operate and developer-friendly. App Service can also run arbitrary containers. However, it is best suited to run websites. The billing model is mostly capacity-based with some built-in autoscaling but without scaling to zero.</p>
<p><strong>Azure Functions</strong> is a developer-oriented truly-serverless offering. However, it comes with an opinionated programming model that is focused on achieving a high developer productivity as long as your application can use the Azure Functions SDK or container base images. Unlike Container APps, it does not support long-running jobs in consumption mode.</p>
<p>Comparing to other vendors: Azure Container Apps are in the same space as <strong>Google Cloud Run</strong> and <strong>AWS App Runner</strong>. In contrast to the competition, Azure Container Apps is built on Kubernetes and related open-source projects, which should benefit its users in terms of interoperability and transparency. Additionally, Container Apps is the only service that provides features like service discovery for microservices-style communication out of the box.</p>
<h2 id="example-run-an-http-api-with-azure-container-apps-and-pulumi">Example: Run an HTTP API with Azure Container Apps and Pulumi</h2>
<p>Let&rsquo;s walk through the steps to build an example application with Azure Container Apps using infrastructure as code in familiar languages. In this scenario, we create an HTTP application that is available via a public domain name. We&rsquo;ll use Pulumi to provision the necessary resources. In this example, we will use TypeScript however you could also use JavaScript, Python, Go, and C#.</p>
<p>You can check out the complete source code in the Pulumi Examples:</p>
<ul>
<li><a href="https://github.com/pulumi/examples/tree/master/azure-ts-containerapps">TypeScript Azure Container Apps Example</a></li>
<li><a href="https://github.com/pulumi/examples/tree/master/azure-cs-containerapps">C# Azure Container Apps Example</a></li>
<li><a href="https://github.com/pulumi/examples/tree/master/azure-py-containerapps">Python Azure Container Apps Example</a></li>
<li><a href="https://github.com/pulumi/examples/tree/master/azure-go-containerapps">Go Azure Container Apps Example</a></li>
</ul>
<h3 id="define-a-dockerfile-and-app">Define a Dockerfile and app</h3>
<p>Here are the key features of the container image for our application:</p>
<ul>
<li>Based on the <code>node:8.9.3-alpine</code> image</li>
<li>Installs <code>express</code> with NPM</li>
<li>Runs a node web app using <code>index.js</code> and <code>index.html</code> user files</li>
</ul>
<p><a href="https://github.com/pulumi/examples/tree/master/azure-ts-containerapps/node-app/Dockerfile">Full Dockerfile</a>.</p>
<h3 id="set-up-the-environment">Set up the environment</h3>
<p>The resource <code>KubeEnvironment</code> defines a cluster that can host multiple Container Apps. Behind the scenes, it creates an AKS cluster in a subscription managed internally by Microsoft and deploys the Apps control plane.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">web</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/azure-native/web/v20210301&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">env</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">web</span><span style="color:#1f2328">.</span><span style="color:#1f2328">KubeEnvironment</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;env&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">type</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Managed&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><h3 id="build-and-publish-a-container-image">Build and publish a container image</h3>
<p>We can build the Docker image and publish it to a new Azure Container Registry (ACR) repository:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">docker</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/docker&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">containerregistry</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/azure-native/containerregistry&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">customImage</span> <span style="color:#0550ae">=</span> <span style="color:#0a3069">&#34;node-app&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">registry</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">containerregistry</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Registry</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;registry&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">sku</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Basic&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">adminUserEnabled</span>: <span style="color:#cf222e">true</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">credentials</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">containerregistry</span><span style="color:#1f2328">.</span><span style="color:#1f2328">listRegistryCredentialsOutput</span><span style="color:#1f2328">({</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">registryName</span>: <span style="color:#cf222e">registry.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">adminUsername</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">credentials</span><span style="color:#1f2328">.</span><span style="color:#1f2328">apply</span><span style="color:#1f2328">(</span><span style="color:#1f2328">c</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">c</span><span style="color:#1f2328">.</span><span style="color:#1f2328">username</span><span style="color:#0550ae">!</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">adminPassword</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">credentials</span><span style="color:#1f2328">.</span><span style="color:#1f2328">apply</span><span style="color:#1f2328">(</span><span style="color:#1f2328">c</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">c</span><span style="color:#1f2328">.</span><span style="color:#1f2328">passwords</span><span style="color:#0550ae">!</span><span style="color:#1f2328">[</span><span style="color:#0550ae">0</span><span style="color:#1f2328">].</span><span style="color:#1f2328">value</span><span style="color:#0550ae">!</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">myImage</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">docker</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Image</span><span style="color:#1f2328">(</span><span style="color:#1f2328">customImage</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">imageName</span>: <span style="color:#cf222e">pulumi.interpolate</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">registry</span><span style="color:#1f2328">.</span><span style="color:#1f2328">loginServer</span><span style="color:#0a3069">}</span><span style="color:#0a3069">/</span><span style="color:#0a3069">${</span><span style="color:#1f2328">customImage</span><span style="color:#0a3069">}</span><span style="color:#0a3069">:v1.0.0`</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">build</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">context</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">`./</span><span style="color:#0a3069">${</span><span style="color:#1f2328">customImage</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span> <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">registry</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">server</span>: <span style="color:#cf222e">registry.loginServer</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">username</span>: <span style="color:#cf222e">adminUsername</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">password</span>: <span style="color:#cf222e">adminPassword</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><h2 id="deploy-the-container-app">Deploy the container app</h2>
<p>Finally, we can define the Container App itself. We point the App to the environment resource and instruct it to run our custom image. Image container credentials are specified in the <code>configuration</code> block, with the password marked as a secret. We&rsquo;ve also enabled external ingress to publish the app on the web.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">containerApp</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">web</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ContainerApp</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;app&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">kubeEnvironmentId</span>: <span style="color:#cf222e">env.id</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">configuration</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">ingress</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">external</span>: <span style="color:#cf222e">true</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">targetPort</span>: <span style="color:#cf222e">80</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">registries</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">server</span>: <span style="color:#cf222e">registry.loginServer</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">username</span>: <span style="color:#cf222e">adminUsername</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">passwordSecretRef</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;pwd&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">secrets</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;pwd&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">value</span>: <span style="color:#cf222e">adminPassword</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">template</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">containers</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;myapp&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">image</span>: <span style="color:#cf222e">myImage.imageName</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">url</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">interpolate</span><span style="color:#0a3069">`https://</span><span style="color:#0a3069">${</span><span style="color:#1f2328">containerApp</span><span style="color:#1f2328">.</span><span style="color:#1f2328">configuration</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ingress</span><span style="color:#1f2328">.</span><span style="color:#1f2328">fqdn</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><h3 id="test-the-app">Test the app</h3>
<p>And that is it! We run <code>pulumi up</code> to get the application up and running. Once the deployment completes, we can send an HTTP request and check the response.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ curl <span style="color:#cf222e">$(</span>pulumi stack output url<span style="color:#cf222e">)</span>
</span></span><span style="display:flex;"><span>&lt;html&gt;
</span></span><span style="display:flex;"><span>&lt;body&gt;
</span></span><span style="display:flex;"><span>&lt;h1&gt;Your custom docker image is running in Azure Container Apps!&lt;/h1&gt;
</span></span><span style="display:flex;"><span>&lt;/body&gt;
</span></span><span style="display:flex;"><span>&lt;/html&gt;
</span></span></code></pre></div><h2 id="conclusion">Conclusion</h2>
<p>Azure Container Apps enable you to build applications in your favorite language with any dependencies and tools, package them as a container image, and deploy them in seconds. Container Apps abstract away infrastructure management by automatically scaling up and down to zero and only charging for the exact resources you use.</p>
<p>This post shows how to use Pulumi to build a container image and publish it as an Azure Container App. Pulumi makes it easy to create artifacts and provision and manage cloud infrastructure on any cloud using familiar programming languages, including C#, TypeScript, Python, and Go. Docker images, ACR registries, container environments, and Apps can be managed within the same infrastructure definition.</p>
<h3 id="further-steps">Further steps</h3>
<ul>
<li><a href="https://www.pulumi.com/docs/iac/get-started/azure/">Get Started with Pulumi for Azure today.</a></li>
<li><a href="https://aka.ms/containerapps/ignite-blog">Read about Azure Container Apps announcement.</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/containers" term="containers" label="Containers" />
                             
                                <category scheme="https://mikhail.io/tags/docker" term="docker" label="Docker" />
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[How To Deploy Temporal to Azure Container Instances]]></title>
            <link href="https://mikhail.io/2021/10/how-to-deploy-temporal-to-azure-container-instances/"/>
            <id>https://mikhail.io/2021/10/how-to-deploy-temporal-to-azure-container-instances/</id>
            
            <published>2021-10-28T00:00:00+00:00</published>
            <updated>2021-10-28T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Get up and running with Temporal workflows in Azure in several CLI commands</blockquote><p>In <a href="https://mikhail.io/2020/10/practical-approach-to-temporal-architecture/">my previous article</a>, I outlined the various components of <a href="https://temporal.io">Temporal</a> and how they interact. Today&rsquo;s blog builds on this knowledge and demonstrates an example Temporal deployment.</p>
<p>It&rsquo;s a minimalistic deployment on Azure which combines a managed MySQL database with Azure Container Instances, suitable for simple experimentation and development. Here is a diagram of the cloud infrastructure:</p>
<p><img src="./azure.png" alt="Azure Diagram"></p>
<p>This sample deployment is implemented as a <a href="https://pulumi.com">Pulumi</a> program in TypeScript. You can find the full code in <a href="https://github.com/mikhailshilkov/temporal-samples/tree/main/azure-aci">my GitHub</a>.</p>
<h2 id="application-code">Application Code</h2>
<p>The <code>workflow</code> folder contains all of the application code. The application is written with Go and consists of two source files:</p>
<ol>
<li><code>helloworld.go</code> - defines a workflow and an activity</li>
<li><code>main.go</code> - application entry point.</li>
</ol>
<p>The example deploys a &ldquo;Hello World&rdquo; Temporal application copied from <a href="https://github.com/temporalio/samples-go/blob/master/helloworld/helloworld.go">this Go sample</a>. Once you get it up and running, you can certainly customize the code with your own workflow and activities.</p>
<p>The <code>main.go</code> file does two things. First, it spins up a worker:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#1f2328">w</span> <span style="color:#0550ae">:=</span> <span style="color:#1f2328">worker</span><span style="color:#1f2328">.</span><span style="color:#6639ba">New</span><span style="color:#1f2328">(</span><span style="color:#1f2328">c</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;hello-world&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">worker</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Options</span><span style="color:#1f2328">{})</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">w</span><span style="color:#1f2328">.</span><span style="color:#6639ba">RegisterWorkflow</span><span style="color:#1f2328">(</span><span style="color:#1f2328">helloworld</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Workflow</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">w</span><span style="color:#1f2328">.</span><span style="color:#6639ba">RegisterActivity</span><span style="color:#1f2328">(</span><span style="color:#1f2328">helloworld</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Activity</span><span style="color:#1f2328">)</span>
</span></span></code></pre></div><p>Second, it launches an HTTP server in the same process. The server exposes endpoints to start workflows. The <code>/async?name=&lt;yourname&gt;</code> endpoint starts a new workflow and immediately returns, while the <code>/sync?name=&lt;yourname&gt;</code> blocks and waits for the result of the execution and returns the response. You can find the implementation in the <a href="https://github.com/mikhailshilkov/temporal-samples/blob/33024f614d4a99a7700eacf2142c8ef2b7cea0fc/azure-aci/workflow/main.go#L22"><code>start</code></a> function.</p>
<h2 id="docker-image">Docker Image</h2>
<p>Since the application is deployed to Azure Container Instances, we need to produce a custom Docker image. <a href="https://github.com/mikhailshilkov/temporal-samples/blob/33024f614d4a99a7700eacf2142c8ef2b7cea0fc/azure-aci/workflow/Dockerfile">The <code>Dockerfile</code></a> builds the Go application and exposes port <code>8080</code> to the outside world so we can access the starter HTTP endpoints.</p>
<p>Pulumi deploys this <code>Dockerfile</code> to Azure in three steps:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/33024f614d4a99a7700eacf2142c8ef2b7cea0fc/azure-aci/temporal.ts#L99-L107">Deploy</a> an Azure Container Registry.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/33024f614d4a99a7700eacf2142c8ef2b7cea0fc/azure-aci/temporal.ts#L109-L115">Retrieve</a> the registry&rsquo;s admin credentials generated by Azure.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/33024f614d4a99a7700eacf2142c8ef2b7cea0fc/azure-aci/temporal.ts#L117-L125">Publish</a> the application image to the registry.</li>
</ul>
<h2 id="mysql-database">MySQL Database</h2>
<p>There are several persistence options supported by Temporal. A straightforward option in Azure is to deploy an instance of Azure Database for MySQL. It&rsquo;s a fully managed database service where Azure is responsible for uptime and maintenance, and users pay a flat fee per hour.</p>
<p>My example <a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/mysql.ts#L24-L50">provisions</a> an instance of MySQL 5.7 at the Basic tier. The database size is limited to 5 GB.</p>
<p>A final tweak is to <a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/mysql.ts#L52-L58">add</a> a firewall rule for the IP address <code>0.0.0.0</code>, which enables network access to MySQL from any Azure service. Note that this option isn&rsquo;t secure for production workloads: read more in <a href="https://docs.microsoft.com/en-us/azure/mysql/concepts-firewall-rules#connecting-from-azure">Connecting from Azure</a>.</p>
<h2 id="temporal-service-and-web-console">Temporal Service and Web Console</h2>
<p>Next, we deploy the Temporal Service and Temporal Web Console as two Azure Container Instances.</p>
<p>The Service container:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L56">Refers</a> to the <code>temporalio/server</code> Docker image provided by Temporal.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L34-L43">Sets up</a> environment variables to connect to MySQL.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L51-L52">Exposes</a> port <code>7233</code> to the outside world. Note that this is not secure for a production environment!</li>
</ul>
<p>The Web Console container:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L82">Refers</a> to the <code>temporalio/web</code> Docker image provided by Temporal.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L91">Connects</a> to the gRPC endpoint gathered from the Service container.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L77-L78">Exposes</a> port <code>8088</code> to the outside world. Note that this is not secure for a production environment!</li>
</ul>
<h2 id="temporal-worker">Temporal Worker</h2>
<p>The final component is a Temporal worker that runs application workflows and activities. In my setup, the worker is another Azure Container Instance that pulls the custom Docker image from the container registry. The worker container:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L143">Refers</a> to the custom Docker image created above.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L152">Connects</a> to the gRPC endpoint gathered from the Service container.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L136-L140">Configures</a> registry credentials to access the private Azure Container Registry.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L77-L78">Exposes</a> the starter endpoints at the port <code>8080</code>.</li>
</ul>
<h2 id="get-started">Get Started</h2>
<p>The Pulumi Command-Line Interface (CLI) runs the deployment. <a href="https://www.pulumi.com/docs/get-started/install/">Install Pulumi</a>, navigate to the folder where you have the example cloned, and run the following commands:</p>
<ol>
<li>Create a new stack (a Pulumi deployment environment):</li>
</ol>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pulumi stack init dev
</span></span></code></pre></div><ol start="2">
<li>Login to <a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli">Azure CLI</a>:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>az login
</span></span></code></pre></div><ol start="3">
<li>Install NPM dependencies:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install
</span></span></code></pre></div><ol start="4">
<li>Run <code>pulumi up</code> and confirm when asked if you want to deploy. Azure resources are provisioned:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ pulumi up
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>Performing changes:
</span></span><span style="display:flex;"><span>  Type                                                         Name                    Status
</span></span><span style="display:flex;"><span>  pulumi:pulumi:Stack                                          temporal-azure-aci-dev  created
</span></span><span style="display:flex;"><span>  ├─ my:example:MySql                                          mysql                   created
</span></span><span style="display:flex;"><span>  │  ├─ azure-nextgen:dbformysql/latest:Server                 mysql                   created
</span></span><span style="display:flex;"><span>  │  └─ azure-nextgen:dbformysql/latest:FirewallRule           mysql-allow-all         created
</span></span><span style="display:flex;"><span>  ├─ my:example:Temporal                                       temporal                created
</span></span><span style="display:flex;"><span>  │  ├─ docker:image:Image                                     temporal-worker         created
</span></span><span style="display:flex;"><span>  │  ├─ azure-nextgen:containerregistry/latest:Registry        registry                created
</span></span><span style="display:flex;"><span>  │  ├─ azure-nextgen:containerinstance/latest:ContainerGroup  temporal-server         created
</span></span><span style="display:flex;"><span>  │  ├─ azure-nextgen:containerinstance/latest:ContainerGroup  temporal-web            created
</span></span><span style="display:flex;"><span>  │  └─ azure-nextgen:containerinstance/latest:ContainerGroup  temporal-worker         created
</span></span><span style="display:flex;"><span>  ├─ random:index:RandomString                                 resourcegroup-name      created
</span></span><span style="display:flex;"><span>  ├─ random:index:RandomPassword                               mysql-password          created
</span></span><span style="display:flex;"><span>  └─ azure-nextgen:resources/latest:ResourceGroup              rg                      created
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Outputs:
</span></span><span style="display:flex;"><span>    serverEndpoint : <span style="color:#0a3069">&#34;21.55.179.245:7233&#34;</span>
</span></span><span style="display:flex;"><span>    starterEndpoint: <span style="color:#0a3069">&#34;http://21.55.177.186:8080/async?name=&#34;</span>
</span></span><span style="display:flex;"><span>    webEndpoint    : <span style="color:#0a3069">&#34;http://52.136.6.198:8088&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Resources:
</span></span><span style="display:flex;"><span>    + <span style="color:#0550ae">13</span> created
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Duration: 7m48s
</span></span></code></pre></div><ol start="5">
<li>The output above prints the endpoints to interact with the application. Run the following command to start a &ldquo;Hello World&rdquo; workflow:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl <span style="color:#cf222e">$(</span>pulumi stack output starterEndpoint<span style="color:#cf222e">)</span>World
</span></span><span style="display:flex;"><span>Started workflow <span style="color:#953800">ID</span><span style="color:#0550ae">=</span>World, <span style="color:#953800">RunID</span><span style="color:#0550ae">=</span>b4f6db00-bb2f-498b-b620-caad81c91a81%
</span></span></code></pre></div><p>Now, open the <code>webEndpoint</code> URL in your browser and find the workflow (it&rsquo;s probably already in the Completed state).</p>
<h2 id="cost-security-and-further-steps">Cost, Security, and Further Steps</h2>
<p>The deployment above provisions real Azure resources, so be mindful of the related costs. Here is an estimated calculation for the &ldquo;West US 2&rdquo; region:</p>
<ul>
<li>Azure Database for MySQL Gen5 Basic with 1 vCore and 5 GB of storage = $25.32/month</li>
<li>Azure Container Instance with 1 vCPU and 1 GB of RAM: 3 x $32.36/month = $97.08/month</li>
<li>Azure Container Registry Basic = $5.00/month</li>
</ul>
<p>The total cost for this example is approximately $127.40 per month.</p>
<p>Whenever you are done experimenting, run <code>pulumi destroy</code> to delete the resources. Note that all the data will be lost after destruction.</p>
<p>As noted in the sections above, the security setup is minimal and is not suitable for any environment that processes real data. In addition to a secure networking setup, a production environment would need to handle scalability, resilience, backups, observability, and so on.</p>
<p>I plan to address those topics in future blog posts. Stay tuned!</p>
<p>You can find the full code in <a href="https://github.com/mikhailshilkov/temporal-samples/tree/main/azure-aci">my GitHub</a>.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/temporal" term="temporal" label="Temporal" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Eliminate Cold Starts by Predicting Invocations of Serverless Functions]]></title>
            <link href="https://mikhail.io/2021/06/eliminate-cold-starts-by-predicting-invocations-of-serverless-functions/"/>
            <id>https://mikhail.io/2021/06/eliminate-cold-starts-by-predicting-invocations-of-serverless-functions/</id>
            
            <published>2021-06-18T00:00:00+00:00</published>
            <updated>2021-06-18T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Azure Functions introduce a data-driven strategy to pre-warm serverless applications right before the next request comes in</blockquote><p>Developers and decision-makers often mention <a href="/serverless/coldstarts/">cold starts</a> as a significant drawback of serverless functions. Cloud providers continually invest in reducing the latency of a cold start, but they haven&rsquo;t done much to prevent them altogether. The most common technique is to keep a worker alive for 10-20 minutes after each request, hoping that another request comes in and benefits from the warm instance.</p>
<p>This simple strategy works to some extent, but it&rsquo;s both wasteful in terms of resource utilization and not particularly helpful for low-usage applications. Is there an alternative strategy that could adapt to the workload, reduce the frequency of cold starts, <em>and</em> be more efficient?</p>
<p>In <a href="/2020/05/serverless-in-the-wild-azure-functions-usage-stats">Azure Functions Production Usage Statistics</a>, I reviewed the first part of the <a href="https://arxiv.org/pdf/2003.03423.pdf">Serverless in the Wild</a> paper, which outlines statistics of Azure Functions running in production. Actually, the ultimate goal of that paper is to suggest an improvement to the cold start mitigation policy and validate the proposed strategy based on the data.</p>
<p>This article is my second installment of the paper review. I focus on the idea of predicting future invocations and pre-warming of serverless workers. I describe the current state of cold starts, then explain the suggested improvements from the paper. Finally, I present my own take on those ideas.</p>
<p>The new policy is definitely worth studying because it will be applied to your Azure Functions soon!</p>
<h2 id="challenges">Challenges</h2>
<p>As the statistics show, many Azure Function Apps are called very infrequently. Let&rsquo;s consider a concrete example: an HTTP-triggered function that runs approximately once per hour and returns current data for a report.</p>
<p>Currently, every invocation of such a function would hit a cold start. Specifically, Azure uses a fixed &ldquo;keep-alive&rdquo; policy that retains the resources in memory for 20 minutes after execution. This isn&rsquo;t helpful in our scenario since requests come every 60 minutes.</p>
<p><img src="scenario.png" alt="Warm instances are recycled after 20 minutes, so an hourly request hits a cold start"></p>
<p>In this specific case, the fixed policy is problematic for everyone:</p>
<ul>
<li>Users hit a cold start every time, which significantly increases the response time.</li>
<li>Azure wastes resources on keeping a warm instance in memory for 20 minutes every hour without any benefits.</li>
</ul>
<p>Indeed, cloud providers seek to achieve high function performance at the lowest possible resource cost. They can&rsquo;t keep all functions in memory all the time: as the stats show, most functions run very seldom, so keeping all of them warm would be a massive waste of resources.</p>
<p>What if the cloud provider could observe each application and adapt the policy for each application workload according to its actual invocation frequency and pattern?</p>
<h2 id="optimal-strategy-predicts-the-future">Optimal Strategy Predicts The Future</h2>
<p>Let&rsquo;s start with a hypothetical ideal solution for our specific one-call-per-hour example. Instead of keeping a warm instance for a fixed period after each invocation, an efficient policy would shut down the instance immediately after each execution. Then, it would boot a new instance right before the next invocation is about to come in.</p>
<p><img src="prewarming.png" alt="A new instance is pre-warmed right before the request comes in and recycled immediately after the execution is complete"></p>
<p>This solves both problems above. Now, all requests are served quickly because Azure creates a fresh instance <em>before</em> a request comes in. Also, Azure saves resources because it can pre-warm the instance shortly before each request and shut it down immediately after the invocation is completed.</p>
<p>This ideal case only works in the world of perfect information. Of course, Azure can&rsquo;t perfectly predict the future and spin up a new instance right before the next HTTP request. Instead, it would need to learn the workload and try to predict the next invocation probabilistically. That&rsquo;s the essence of the policy that the authors of &ldquo;Serverless in the Wild&rdquo; suggest.</p>
<p>Predicting arbitrary workloads can be pretty hard. Every application is unique, and invocations are caused by external events like human behavior or actions in business processes.</p>
<p>Also, there are limits to resources that a prediction policy may consume. The policy calculation should be efficient and not have a significant impact on the system&rsquo;s overall performance. A practical policy would have low overhead both in terms of the size of data structures and the CPU usage overhead.</p>
<h2 id="proposed-policy">Proposed Policy</h2>
<p>The paper suggests a practical policy that tries to predict future invocations without being too expensive.</p>
<p>Let&rsquo;s start at the point when a new application has just been deployed. Azure knows nothing about its invocation patterns yet. The new policy would default to the traditional fixed &ldquo;keep-alive&rdquo; interval but would keep an instance running for a generous 4 hours. Simultaneously, it starts learning the workload.</p>
<p>Every time a new request comes in, the policy calculates how many minutes passed since the end of the previous invocation and records this value in a histogram. The histogram&rsquo;s bins have one-minute granularity. So, in my example, if an invocation came 60 minutes after the previous one, the value for the bin <code>60</code> will increase by one.</p>
<p>At some point, the policy would decide that it knows enough to start predicting future invocations. The histogram for my application may look like this:</p>
<p><img src="histogram.png" alt="Every column shows how many requests landed on minute X after a previous execution"></p>
<p>The new adaptive policy kicks in. Now, it shuts down the active instance after each request, because it knows that the next invocation is unlikely to come any time soon. Then, it uses two cut-off points to plan the warming strategy:</p>
<ul>
<li>The <strong>head cut-off</strong> point is when a new warm instance should be ready. Calculated as 5th percentile minus 10% margin. Approximately 53 minutes in my example.</li>
<li>The <strong>tail cut-off</strong> point is when to kill the warm instance if no request comes in. Calculated as 95th percentile plus 10% margin. Approximately 67 minutes in my example.</li>
</ul>
<p><img src="cutoff.png" alt="An adaptive policy pre-warms an instance at the head cut-off and keeps it until a request comes or until the tail cut-off"></p>
<p>Because my specific workload is highly predictable, it would enjoy the absence of cold starts. Would the policy work for other scenarios?</p>
<h2 id="scenarios">Scenarios</h2>
<p>The suggested policy makes sense for the example we considered so far. However, real-life workloads are very diverse. Let&rsquo;s try to generalize and consider several possible scenarios and how the policy handles them.</p>
<h4 id="regular-cadence">Regular cadence</h4>
<p>Consistent intervals between invocations are the ideal match for the suggested policy. If a histogram is well-shaped and relatively narrow, both head and tail cut-offs are easy to identify. These distributions produce the ideal situation: long shutdown periods and short keep-alive windows.</p>
<p><img src="regular.png" alt="Well-shaped narrow distribution produces clear cut-off points"></p>
<p>My example above falls into this category.</p>
<h4 id="frequent-invocations">Frequent invocations</h4>
<p>Many applications would invoke functions frequently, so, many measured intervals would be close to zero.</p>
<p><img src="frequent.png" alt="In this case, instances are not unloaded after a request is executed and wait for another one, or the tail cut-off moment"></p>
<p>In these cases, the head cut-off is rounded down to zero. The policy does not shut down the application after a function execution but keeps it alive until the next execution, or until the tail cut-off.</p>
<h4 id="inconsistent-invocations-or-not-enough-data">Inconsistent invocations or not enough data</h4>
<p>The policy needs a certain amount of quality data to start being useful in predicting the next invocation.
The application may be recently deployed and may not have enough points yet:</p>
<p><img src="sparse.png" alt="Too few data to make reliable predictions: wait and learn while using the default policy"></p>
<p>Alternatively, data points might not come in a well-shaped cluster:</p>
<p><img src="random.png" alt="No definite shape of the histogram: fall back to the default policy"></p>
<p>In both of these cases, the policy reverts to a default approach: no shutdown and a long keep-alive window. This puts an extra burden on the cloud provider, but the idea is that these scenarios would be rare enough to allow the policy to stay practical.</p>
<h4 id="invocations-beyond-4-hours">Invocations beyond 4 hours</h4>
<p>The policy defines a maximum value for histogram data to limit the storage capacity for the histogram. The paper suggests a maximum of 4 hours. All intervals beyond that threshold are recorded in a special overflow bin.</p>
<p><img src="outofbounds.png" alt="Values beyond the 4-hour limit end up in a special &ldquo;everything else&rdquo; overflow bin"></p>
<p>If a lot of values start to fall into that range, the bin-based policy can&rsquo;t perform well anymore. For this category of applications, the paper suggests switching to time-series analysis to predict the next interval duration. They mention the <a href="https://en.wikipedia.org/wiki/Autoregressive_integrated_moving_average">autoregressive integrated moving average</a> model without providing many details.</p>
<p>As invocations of this type are infrequent, the overall overhead of such a model would stay relatively low.</p>
<h2 id="evaluation">Evaluation</h2>
<p>The authors evaluated the policy based on recorded <a href="/2020/05/serverless-in-the-wild-azure-functions-usage-stats">production usage statistics</a>. The evaluation consists of two parts:</p>
<ul>
<li>A simulation that iterates through the data, calculates the policy models, and evaluates whether each invocation would yield a cold starts with the new policy.</li>
<li>A replay of the recorded invocation trace on a modified version of Apache OpenWhisk, an open-source FaaS platform. The controller was modified to use the new policy while managing workers. The trace was scaled down by randomly selecting applications with mid-range popularity.</li>
</ul>
<p>Both parts showed similarly promising results of reducing the cold start frequency and resource waste. The new policy reduced the average and 99-percentile function execution time 33% and 82%, respectively, while also reducing the average memory consumption of workers by 16%.</p>
<p>The overhead of the policy looks manageable too. The controller&rsquo;s policy implementation adds less than 1 ms to the end-to-end latency and only a 5% increase in controller&rsquo;s CPU utilization.</p>
<p>By tuning the parameters of the policy (default window, cut-off points, granularity), its implementation can achieve the same number of cold starts at much lower resource cost, or keep the same resource cost but reduce the frequency of cold starts significantly.</p>
<h2 id="production-implementation">Production Implementation</h2>
<p>Encouraged by the position evaluation, the team implemented the policy in Azure Functions for HTTP-triggered applications, it will be rolling out to production in stages starting this month (June 2020).</p>
<p>Here are some implementation details:</p>
<ul>
<li>Each histogram contains data for 4 hours, filling up 960 bytes per application.</li>
<li>The histogram is stored in memory and backed up to a database once per hour.</li>
<li>A new histogram is started every day with a history of previous values stored for two weeks.</li>
<li>Worker warming is scheduled for the calculated head cut-off point minus 90 seconds.</li>
<li>Pre-warming loads function dependencies and performs JIT where possible.</li>
<li>All policy decisions are asynchronous, off the critical path to minimize the latency impact on the invocation.</li>
</ul>
<p>I look forward to testing this new policy once it&rsquo;s rolled out. Expect a follow-up blog post!</p>
<h2 id="open-questions">Open Questions</h2>
<p>Everything above is my summary of the &ldquo;Serverless in the Wild&rdquo; paper&rsquo;s ideas and findings. I want to close the blog post with some questions that I still have, personally.</p>
<p>Whether true in practice or not, the public opinion strongly perceives cold starts as a notable obstacle to serverless adoption. It&rsquo;s great to see some concrete suggestions driven by data that may improve cold starts in serverless functions.</p>
<p>The sweet spot of the suggested policy is applications with relatively regular intervals between invocations below several hours. I think the practical effect of the strategy might still be limited. Here are some concerns that I see.</p>
<p><em>The authors reviewed my questions and provided their answers, which I&rsquo;m including in the text below.</em></p>
<h4 id="who-benefits">Who benefits?</h4>
<p>Not every serverless function is sensitive to cold starts.</p>
<p>The policy favors functions with predictable intervals. Timer-based schedules (whether functions with timer trigger or external timers sending requests to the app or IoT devices communicating periodically) may produce perfect histograms. And yet, those functions are not damaged by a cold start and may entirely live with it.</p>
<p>On the other side, human-initiated requests that care about cold starts are likely to be less predictable. Does it mean they don&rsquo;t enjoy much improvement with the new policy?</p>
<p>The Azure Functions team is rolling the policy out for HTTP functions, so they do expect it to be useful. We will know soon!</p>
<h5 id="response-from-the-authors">Response from the authors:</h5>
<blockquote>
<p>There is a large fraction of the applications that will likely benefit. Because for many applications the keep-alive interval can drastically reduce, the provider can afford to increase the keep-alive for other applications. About 50% of the applications have average inter-invocation times of 30 minutes of more. These incur many cold starts in the fixed policy, and will likely see a reduction in cold starts because their keep-alive intervals will be able to grow. Applications with idle times longer than 4 hours can also benefit because of the ARIMA time-series prediction.</p></blockquote>
<h4 id="is-it-flexible-enough">Is it flexible enough?</h4>
<p>The policy assumes that the interval distribution is stable over time. How will this hold in practice over more extended periods?</p>
<p>Imagine an application that is only used during business hours. Or, an application that is mostly idle but is sporadically applied for a specific business process. Or, a demo app that you want to show to your colleagues during a planning meeting. Likely, all of them would still hit cold starts at the beginning of a session.</p>
<p>Maybe that&rsquo;s why the production implementation begins with a clean histogram every day. They will watch and learn, and may use data for the last two weeks to improve later on.</p>
<h5 id="response-from-the-authors-1">Response from the authors:</h5>
<blockquote>
<p>The policy does not have to assume a stationary distribution of arrival patterns. In the traces analyzed in the paper there was not a significant enough variation in the distributions to enable a deep investigation. With the production deployment, we will investigate different policies to decay information from previous histograms.</p></blockquote>
<h4 id="what-about-function-warming">What about function warming?</h4>
<p>There&rsquo;s the elephant in the room of cold starts: function &ldquo;warming&rdquo;. Warming is a trick when a developer adds an extra timer-based function to their Function App so that the timer triggers every few minutes. This way, the runtime would never unload it, and an instance would always be ready.</p>
<p>I suspect that this simple trick still outperforms the suggested histogram-based policy in terms of cold-start prevention. Obviously, it doesn&rsquo;t help the cloud provider to save costs.</p>
<p>Is there a way to combine two approaches and get the benefits of both?</p>
<h5 id="response-from-the-authors-2">Response from the authors:</h5>
<blockquote>
<p>The results in the paper take into account user-generated warm-up functions that are present in the data. If the hybrid policy is successful in preventing cold starts, there will be no need for users to create periodic warm-up functions, which represent extra effort and higher costs.</p></blockquote>
<h4 id="what-about-the-scale-out-cold-start">What about the scale-out cold start?</h4>
<p>The paper focuses on applications that are invoked rarely. However, even applications with higher utilization may still hit cold starts when scaled out on multiple instances. Every new instance would need to boot, and the requests would still be waiting.</p>
<p>The suggested policy does not address cold starts beyond the first instance, even though they do occur and potentially have a more significant impact on latency percentiles of real-world human-facing applications.</p>
<h5 id="response-from-the-authors-3">Response from the authors:</h5>
<blockquote>
<p>We didn&rsquo;t address scale-out cold starts in the paper. We can pre-warm the scale-out instances by forcing the scale out to happen slightly before it normally would. For example, when the scale out is triggered by a threshold number of concurrent invocations, we can lower the threshold slightly. We will be experimenting with such techniques in our implementation in Azure Functions.</p></blockquote>
<h2 id="conclusion">Conclusion</h2>
<p>Despite several open questions above, I&rsquo;m delighted that the paper was published. I welcome any structured effort that focuses on cold start optimization. The paper highlights usage statistics, the challenges of the cold start problem, and suggests several improvements.</p>
<p>It&rsquo;s even more exciting to see the finding being applied in Azure in production!</p>
<p>If you want to learn more, you can read the full paper here: <a href="https://arxiv.org/pdf/2003.03423.pdf">Serverless in the Wild: Characterizing and Optimizing the Serverless Workload at a Large Cloud Provider</a>.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                             
                                <category scheme="https://mikhail.io/tags/paper-review" term="paper-review" label="Paper review" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Choosing the Number of Shards in Temporal History Service]]></title>
            <link href="https://mikhail.io/2021/05/choose-the-number-of-shards-in-temporal-history-service/"/>
            <id>https://mikhail.io/2021/05/choose-the-number-of-shards-in-temporal-history-service/</id>
            
            <published>2021-05-25T00:00:00+00:00</published>
            <updated>2021-05-25T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Tuning the sharding configuration for the optimal cluster performance with the numHistoryShards config.</blockquote><p>Today, I&rsquo;m diving into the topic of tuning your Temporal clusters for optimal performance. More specifically, I will be discussing the single configuration option: the number of History service shards. Through a series of experiments, I’ll explain how a low number of shards can lead to contention, while a large number can cause excessive resource consumption.</p>
<p>All the experimental data is collected with <a href="https://github.com/temporalio/maru/">Maru</a>—the open-source Temporal load simulator and benchmarking tool.</p>
<p>Let&rsquo;s start with a hypothetical scenario that illustrates the struggle of a misconfigured Temporal cluster.</p>
<h2 id="symptom-contention-in-history-service">Symptom: Contention in History Service</h2>
<p>Imagine you are benchmarking your Temporal deployment. You are not entirely happy with the throughput yet, so you look into performance metrics. Storage utilization seems relatively low, and storage latency is excellent. CPU usage of Temporal services is low too. Where is the bottleneck?</p>
<p>You start looking at response times and notice that the History service has high latency. Why is that the case? If the storage latency is good, what is the History service waiting for?</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="latency-pattern.png"
            alt="Example of a problematic latency pattern"
             />
        
    
    <figcaption>
        <h4>Example of a problematic latency pattern</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>If the latencies in your system look somewhat like the picture above, your cluster is probably not configured appropriately for the current workload. You are likely in a situation of lock contention due to an insufficient number of History shards.</p>
<h2 id="theory-history-shards">Theory: History Shards</h2>
<p>Temporal provides high guarantees around Workflow consistency. To achieve this goal, Temporal allocates a single host of the History service to process all events of a given Workflow execution.</p>
<p>Each Workflow execution receives a unique identifier. Temporal calculates a hash for each identifier and allocates it to a specific <strong>shard</strong> based on the hash value. A shard is represented as a number from 1 to N. Executions with the same shard value are processed by the same host.</p>
<p>The History service has a configuration value <code>numHistoryShards</code> to define the total number of shards, set once and forever. Each instance of the History service obtains a lock on multiple shards and starts processing Workflow executions that belong to them.</p>
<p>Shards are logically independent and isolated from each other. To achieve the consistency requirements with Cassandra, Temporal serializes all updates belonging to the same shard.</p>
<p>Therefore, a shard is a unit of parallelism. Since each shard executes all updates under a single lock, all updates are sequential. The maximum theoretical throughput of a single shard is limited by the latency of a database operation. For example, if a single database update takes 10 ms, then a single shard can do a maximum of 100 updates per second. If you try to push more updates, you will see the History service latencies go up without database latency going up. Precisely the case illustrated in the chart above!</p>
<h2 id="experiment-observe-history-service-contention">Experiment: Observe History Service Contention</h2>
<p>Let&rsquo;s design an experiment to illustrate the impact of the number of History shards on cluster throughput and latency. I created a test Kubernetes cluster with three node pools: one for Cassandra, one for Elasticsearch, and one for Temporal services and workers. I deployed the datastores and Temporal cluster. For the sake of simplicity, every Temporal service has just one pod.</p>
<p>Then, using <a href="https://github.com/temporalio/maru/">Maru</a>, I run a benchmark of 25,000 workflow executions to collect metrics around processing speed, response latency, and resource utilization. I conduct the same experiment several times but tweak just one configuration parameter: the number of History service shards.</p>
<h3 id="single-shard">Single Shard</h3>
<p>Let&rsquo;s start with a base case of just a single shard. All Workflow executions are assigned to the same shard and have to be serialized. As expected, the system throughput is quite limited, and it takes 28 minutes to complete the scenario:</p>
<figure >
    
        <img src="1-workflow-rate.png"
            alt="Workflow processing rates for a single-shard configuration"
             />
        
    
    <figcaption>
        <h4>Workflow processing rates for a single-shard configuration</h4>
    </figcaption>
    
</figure>
<p>Due to the interference of read and write operations, the processing rate starts really low but goes up as the backlog decreases. This behavior makes sense for the high-contention scenario.</p>
<p>Here is the latency chart of the History service versus the underlying Cassandra database:</p>
<figure >
    
        <img src="1-latency.png"
            alt="Database and service latencies for a single-shard configuration"
             />
        
    
    <figcaption>
        <h4>Database and service latencies for a single-shard configuration</h4>
    </figcaption>
    
</figure>
<p>Cassandra&rsquo;s response time is very close to zero as compared to the History service response time. The vast majority of the latter is internal contention.</p>
<p>The CPU utilization reasonably fairly low too:</p>
<figure >
    
        <img src="1-cpu.png"
            alt="Database and service CPU usage for a single-shard configuration"
             />
        
    
    <figcaption>
        <h4>Database and service CPU usage for a single-shard configuration</h4>
    </figcaption>
    
</figure>
<p>Both the database and the service are underutilized, as the cluster spends the majority of time waiting.</p>
<h3 id="increasing-the-number-of-shards">Increasing the Number of Shards</h3>
<p>I re-ran the exact same experiment with 4, 8, 64, 512, and 4096 shards and correlated the shard number with the key metrics. Note that the horizontal axis is out of scale on all the charts below.</p>
<p>The processing time goes down as the number of shards grows:</p>
<figure >
    
        <img src="processing-time.png"
            alt="Total time to process the workflows as a function of the number of shards"
             />
        
    
    <figcaption>
        <h4>Total time to process the workflows as a function of the number of shards</h4>
    </figcaption>
    
</figure>
<p>The History service latency gets closer and closer to the persistence latency as the former decreases and the latter increases due to a higher load:</p>
<figure >
    
        <img src="latency.png"
            alt="Database and service latencies as functions of the number of shards"
             />
        
    
    <figcaption>
        <h4>Database and service latencies as functions of the number of shards</h4>
    </figcaption>
    
</figure>
<p>The CPU utilization is pretty good for all experiments with 8 or more shards.</p>
<figure >
    
        <img src="cpu.png"
            alt="Database and service CPU usage as functions of the number of shards"
             />
        
    
    <figcaption>
        <h4>Database and service CPU usage as functions of the number of shards</h4>
    </figcaption>
    
</figure>
<p>Adding more and more shards has diminishing returns: the difference between 1 and 4 or 4 and 8 is much more pronounced than 8 to 64 or 512 to 4096. However, it still looks like more shards are universally better than fewer shards. Would it be reasonable to set the shard number to an arbitrarily large number and be done with it?</p>
<h2 id="too-many-shards">Too Many Shards</h2>
<p>Can there be too many shards?</p>
<p>Yes. Each shard consumes resources of History service pods and performs some background processing on the database. So, the system pays an overhead fee for each additional shard. Also, an excessive number of shards slows down node recovery as each shard has to load its state from the database on redistribution after a service host goes down.</p>
<p>The next chart shows the distribution of memory consumption of the History service host across all experiments.</p>
<figure >
    
        <img src="memory.png"
            alt="Service memory consumption as functions of the number of shards"
             />
        
    
    <figcaption>
        <h4>Service memory consumption as functions of the number of shards</h4>
    </figcaption>
    
</figure>
<p>In fact, I tried running the experiment with 32.768 shards, but it utterly failed because the History service pod consumed too much memory, and the controller kept evicting it.</p>
<h2 id="conclusions">Conclusions</h2>
<p>The shard number of the History service is a critical configuration of a Temporal cluster. The configured value has a direct impact on throughput, latency, and resource utilization of the system.</p>
<p>It&rsquo;s essential to get this configuration right for your setup and your performance targets. Even more so because the value can&rsquo;t be changed after the initial cluster deployment.</p>
<p><a href="https://github.com/temporalio/maru/">Maru</a> is a performance testing tool that can help you determine the optimal configuration value empirically. You can use Maru to run multiple experiments with your cluster and application code, collect the results, and decide before going into production. The data in this article was collected with Maru.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/temporal" term="temporal" label="Temporal" />
                             
                                <category scheme="https://mikhail.io/tags/workflows" term="workflows" label="Workflows" />
                             
                                <category scheme="https://mikhail.io/tags/performance" term="performance" label="Performance" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Maru: Load Testing Tool for Temporal Workflows]]></title>
            <link href="https://mikhail.io/2021/03/maru-load-testing-tool-for-temporal-workflows/"/>
            <id>https://mikhail.io/2021/03/maru-load-testing-tool-for-temporal-workflows/</id>
            
            <published>2021-03-18T00:00:00+00:00</published>
            <updated>2021-03-18T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Benchmarking Temporal deployments with a simple load simulator tool</blockquote><p><a href="https://temporal.io">Temporal</a> enables developers to build highly reliable applications without having to worry about all the edge cases. If you are new to Temporal, check out my article <a href="https://mikhail.io/2020/10/temporal-open-source-workflows-as-code/">Open Source Workflows as Code</a>.</p>
<p>Temporal is an open-source project, a system <a href="https://mikhail.io/2020/10/practical-approach-to-temporal-architecture/">consisting of several components</a>. While a managed Temporal service is coming, all the existing Temporal end-users administer and operate deployments themselves. Typically, the Temporal services run in a Kubernetes cluster. I described a sample Temporal installation in <a href="https://mikhail.io/2020/11/how-to-deploy-temporal-to-azure-kubernetes-aks/">How To Deploy Temporal to Azure Kubernetes Service (AKS)</a>.</p>
<p>Many Temporal newcomers have a reasonable question: <em>&ldquo;What kind of infrastructure setup do I need to handle my target workload? What is the right capacity for a Kubernetes cluster, a persistent datastore, server components, and workers?”</em></p>
<p>So a key goal became helping users answer the above question. I decided to create a load simulating tool that could be a practical first step in the capacity planning process. Here is the primary idea:</p>
<ul>
<li>Choose a supported persistence solution and deploy it or pay someone to run it (ideal)</li>
<li>Build your Kubernetes cluster and deploy Temporal services to it.</li>
<li>Deploy a <strong>target</strong> workflow that is a simplified representation of your real-life workflows.</li>
<li>Also, deploy the <strong>bench</strong> workflow that I created. This specialized Temporal workflow orchestrates a load-test scenario, invokes the target workflow accordingly, and collects the execution statistics.</li>
</ul>
<p>The bench workflow can give you the first approximation of whether the current cluster size would be sufficient for the target workload. For more detailed insights, you can also use tools like Prometheus and Grafana to observe the metrics and find issues.</p>
<p>Based on the statistics, you find a bottleneck, tweak the cluster size, persistence configuration, number of service instances, workers, shards, or a combination of those. Then, you re-run the tests and obtain the new results. Repeat the process until you are happy with the outcome.</p>
<p>Meet <a href="https://github.com/temporalio/maru/">Maru</a>—an open-source load simulating tool for Temporal! You can give it a try today. The rest of this blog post describes the steps to get from nothing to the first benchmark results.</p>
<h2 id="build-a-temporal-cluster">Build a Temporal Cluster</h2>
<p>A working Temporal Cluster is the first thing you need to run the benchmark. If you already have one, skip to the next section. Otherwise, you can build a sample AKS cluster with a compatible deployment of Temporal using <a href="https://github.com/temporalio/maru/tree/master/pulumi">this Pulumi program</a>.</p>
<p>For this blog post, I&rsquo;m using a sample deployment that provisions the following resources:</p>
<ul>
<li><strong>Azure Kubernetes Service</strong> managed cluster with three nodes of size <code>Standard_DS2_v2</code> (2 vCore, 7 GB RAM).</li>
<li><strong>Temporal Helm chart</strong> with all defaults, except Kafka is disabled. The Temporal service uses 512 history shards.</li>
<li>In-cluster <strong>Cassandra</strong> as the datastore.</li>
<li><strong>Elasticsearch</strong> as the visibility store. Note: Elasticsearch is currently required for the benchmark.</li>
<li><strong>Prometheus</strong> and <strong>Grafana</strong>. I won&rsquo;t use those in this post, but they come in handy for performance deep-dives.</li>
</ul>
<p>This setup is pretty basic and does not resemble a realistic production-grade deployment. It’s simply good enough for illustration purposes.</p>
<p>The next step is to define the target scenario for the load-testing.</p>
<h2 id="target-workflow">Target Workflow</h2>
<p>The bench comes with a simple &ldquo;basic&rdquo; workflow that executes several sequential activities. The number of activities is configurable; I set it to three for the test run.</p>
<p>The basic workflow is just an example of a workflow that generates a very predictable workload. It makes sense to start with it, but you should adjust the target scenario to be closer to a real-life workload later on. There&rsquo;s no specific interface that such a workflow has to match: you can tweak it to your needs.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#57606a">// Workflow implements a basic bench scenario to schedule activities in sequence.</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">func</span> <span style="color:#6639ba">Workflow</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span> <span style="color:#1f2328">workflow</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">request</span> <span style="color:#1f2328">workflowRequest</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">(</span><span style="color:#cf222e">string</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">error</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">ao</span> <span style="color:#0550ae">:=</span> <span style="color:#1f2328">workflow</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ActivityOptions</span><span style="color:#1f2328">{</span> <span style="color:#1f2328">TaskQueue</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">common</span><span style="color:#1f2328">.</span><span style="color:#1f2328">TaskQueue</span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">ctx</span> <span style="color:#1f2328">=</span> <span style="color:#1f2328">workflow</span><span style="color:#1f2328">.</span><span style="color:#6639ba">WithActivityOptions</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">ao</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">for</span> <span style="color:#1f2328">i</span> <span style="color:#0550ae">:=</span> <span style="color:#0550ae">0</span><span style="color:#1f2328">;</span> <span style="color:#1f2328">i</span> <span style="color:#1f2328">&lt;</span> <span style="color:#1f2328">request</span><span style="color:#1f2328">.</span><span style="color:#1f2328">SequenceCount</span><span style="color:#1f2328">;</span> <span style="color:#1f2328">i</span><span style="color:#0550ae">++</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">var</span> <span style="color:#1f2328">result</span> <span style="color:#cf222e">string</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">err</span> <span style="color:#0550ae">:=</span> <span style="color:#1f2328">workflow</span><span style="color:#1f2328">.</span><span style="color:#6639ba">ExecuteActivity</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;basic-activity&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">request</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ActivityDurationMilliseconds</span><span style="color:#1f2328">).</span><span style="color:#6639ba">Get</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span><span style="color:#1f2328">,</span> <span style="color:#0550ae">&amp;</span><span style="color:#1f2328">result</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">if</span> <span style="color:#1f2328">err</span> <span style="color:#0550ae">!=</span> <span style="color:#cf222e">nil</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>           <span style="color:#cf222e">return</span> <span style="color:#0a3069">&#34;&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">err</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">return</span> <span style="color:#1f2328">request</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ResultPayload</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Activity sleeps and returns the result.</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">func</span> <span style="color:#6639ba">Activity</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span> <span style="color:#1f2328">context</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">req</span> <span style="color:#1f2328">basicActivityRequest</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">(</span><span style="color:#cf222e">string</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">error</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">time</span><span style="color:#1f2328">.</span><span style="color:#6639ba">Sleep</span><span style="color:#1f2328">(</span><span style="color:#1f2328">time</span><span style="color:#1f2328">.</span><span style="color:#6639ba">Duration</span><span style="color:#1f2328">(</span><span style="color:#1f2328">req</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ActivityDelayMilliseconds</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">*</span> <span style="color:#1f2328">time</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Millisecond</span><span style="color:#1f2328">)</span>   
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">return</span> <span style="color:#1f2328">req</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ResultPayload</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><h2 id="bench-workflow">Bench Workflow</h2>
<p>A simple configuration file drives the bench workflow. The file specifies the target workflow and the load scenario: how many executions to start per second and the total number of executions.</p>
<p>The bench has four sequential phases:</p>
<ul>
<li><strong>Driver</strong> schedules target workflow executions according to the configured scenarios.</li>
<li><strong>Monitor</strong> queries the Temporal visibility database for open target workflows and waits until they all complete.</li>
<li><strong>Statistic</strong> queries all target workflow executions to collect the time they started and closed. It places each start and each close event to a bucket and builds a histogram.</li>
<li><strong>Query</strong> provides a query for users to retrieve the statistics.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#cf222e">func</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">w</span> <span style="color:#0550ae">*</span><span style="color:#1f2328">benchWorkflow</span><span style="color:#1f2328">)</span> <span style="color:#6639ba">run</span><span style="color:#1f2328">()</span> <span style="color:#cf222e">error</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">startTime</span> <span style="color:#0550ae">:=</span> <span style="color:#1f2328">workflow</span><span style="color:#1f2328">.</span><span style="color:#6639ba">Now</span><span style="color:#1f2328">(</span><span style="color:#1f2328">w</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ctx</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">for</span> <span style="color:#1f2328">i</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">step</span> <span style="color:#0550ae">:=</span> <span style="color:#cf222e">range</span> <span style="color:#1f2328">w</span><span style="color:#1f2328">.</span><span style="color:#1f2328">request</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Steps</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">if</span> <span style="color:#1f2328">err</span> <span style="color:#0550ae">:=</span> <span style="color:#1f2328">w</span><span style="color:#1f2328">.</span><span style="color:#6639ba">executeDriverActivities</span><span style="color:#1f2328">(</span><span style="color:#1f2328">i</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">step</span><span style="color:#1f2328">);</span> <span style="color:#1f2328">err</span> <span style="color:#0550ae">!=</span> <span style="color:#cf222e">nil</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>           <span style="color:#cf222e">return</span> <span style="color:#1f2328">err</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">res</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">err</span> <span style="color:#0550ae">:=</span> <span style="color:#1f2328">w</span><span style="color:#1f2328">.</span><span style="color:#6639ba">executeMonitorStatisicsActivity</span><span style="color:#1f2328">(</span><span style="color:#1f2328">startTime</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">if</span> <span style="color:#1f2328">err</span> <span style="color:#0550ae">!=</span> <span style="color:#cf222e">nil</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">return</span> <span style="color:#1f2328">err</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">if</span> <span style="color:#1f2328">err</span> <span style="color:#1f2328">=</span> <span style="color:#1f2328">w</span><span style="color:#1f2328">.</span><span style="color:#6639ba">setupQueries</span><span style="color:#1f2328">(</span><span style="color:#1f2328">res</span><span style="color:#1f2328">);</span> <span style="color:#1f2328">err</span> <span style="color:#0550ae">!=</span> <span style="color:#cf222e">nil</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">return</span> <span style="color:#1f2328">err</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">return</span> <span style="color:#cf222e">nil</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><h2 id="worker">Worker</h2>
<p>We deploy both target and bench as Temporal workflows to the same cluster. In my setup, they both run on the same worker (a Kubernetes deployment), but they could easily be split to separate workers. They do need to connect to the same Temporal service.</p>
<p>Now that I have my simple AKS cluster, a Temporal server, and workflows deployed to it. I&rsquo;m ready to run my first benchmark!</p>
<h2 id="run-1-10-executions-per-second">Run 1: 10 Executions per Second</h2>
<p>For my first run, I have a test that runs for five minutes and creates ten workflow executions per second (3000 total). Here is the scenario definition for the bench:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&#34;steps&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;count&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0550ae">3000</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;ratePerSecond&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0550ae">10</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&#34;workflow&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;name&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;basic-workflow&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;args&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#0550ae">&#34;sequenceCount&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0550ae">3</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&#34;report&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;intervalInSeconds&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0550ae">10</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>The <code>steps[0].count</code> is calculated as <code>10 wf/sec * 60 sec/min * 5 min = 3,000 wf</code>.</p>
<p>I start the benchmark by running this command (read <a href="https://docs.temporal.io/docs/tctl/">here</a> how to connect the CLI to your cluster):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>tctl wf start --tq temporal-bench --wt bench-workflow --wtt <span style="color:#0550ae">5</span> --et <span style="color:#0550ae">1800</span> --if ./scenarios/run1.json --wid run1
</span></span></code></pre></div><p>Then, I wait for five minutes and query the results.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>tctl wf query --qt histogram_csv --wid run1
</span></span></code></pre></div><p>The response of the query is a CSV file with the bench statistics: see the file here. I can import this file to Google Spreadsheets and visualize it in a chart like this one:</p>
<p><img src="./run1.png" alt="Run 1 Results"></p>
<p>You can see that the test ran for five minutes, and the bench started workflows at the constant rate of 10 per second. They were also closed at the same speed with only a handful of open executions at any moment.</p>
<p>This test clearly shows that my current deployment is sufficient to handle this type of workload.</p>
<h2 id="run-2-100-executions-per-second">Run 2: 100 Executions per Second</h2>
<p>Let&rsquo;s bump the rate to 100 per second (10x compared to Run 1). Here is the scenario file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&#34;steps&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;count&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0550ae">10000</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;ratePerSecond&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0550ae">100</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&#34;workflow&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;name&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;basic-workflow&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;args&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#0550ae">&#34;sequenceCount&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0550ae">3</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&#34;report&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;intervalInSeconds&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0550ae">10</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>Using the same command sequence as above, I get the following chart of started and closed workflows.</p>
<p><img src="./run2rates.png" alt="Run 2 Rates"></p>
<p>You can see that it&rsquo;s not smooth anymore. The bench failed to start 100 workflow executions per second consistently, and the processing rate oscillated around 35 executions per second. This delay caused the backlog to grow while new executions were being added continually. The backlog started to decrease only after the bench stopped scheduling executions.</p>
<p><img src="./run2backlog.png" alt="Run 2 Backlog"></p>
<p>Clearly, the system in its current configuration isn&rsquo;t capable of processing this workload. I would guess this may be caused by contention in the datastore (Cassandra), but that&rsquo;s a topic for another blog post.</p>
<p>Nonetheless, all the executions eventually succeeded, and no work has been lost. Temporal retained its high-reliability guarantees even with a severely underprovisioned cluster. Great job!</p>
<p>I want to emphasize that this test does not show any fundamental limitations of the Temporal server. I ran it in a small cluster with all the default configuration knobs, which means there was a single instance of each Temporal server component and a single worker, for example.</p>
<h2 id="how-you-can-get-started">How You Can Get Started</h2>
<p><a href="https://github.com/temporalio/maru">Maru, a Temporal Load Simulator tool</a> is now open-source. I encourage everyone to give it a try to benchmark their own setups. Please file an issue if you have ideas on how to make the tool better.</p>
<p>I&rsquo;m planning to describe how the benchmark works, tune underperforming clusters, and conduct other experiments in my follow-up blog posts. Stay tuned!</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/temporal" term="temporal" label="Temporal" />
                             
                                <category scheme="https://mikhail.io/tags/workflows" term="workflows" label="Workflows" />
                             
                                <category scheme="https://mikhail.io/tags/performance" term="performance" label="Performance" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Farmer or Pulumi? Why not both!]]></title>
            <link href="https://mikhail.io/2020/12/farmer-or-pulumi-why-not-both/"/>
            <id>https://mikhail.io/2020/12/farmer-or-pulumi-why-not-both/</id>
            
            <published>2020-12-16T00:00:00+00:00</published>
            <updated>2020-12-16T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Azure Infrastucture as Code using F#: combining Pulumi and Farmer</blockquote><p><em>The post is a part of
<a href="https://sergeytihon.com/2020/10/22/f-advent-calendar-in-english-2020/">F# Advent Calendar 2020</a>.</em></p>
<p>You are a proud F# developer. You deploy your applications to Microsoft Azure. You know that you should never right-click-deploy to production. You don&rsquo;t create important Azure resources via the Azure portal. You are awesome.</p>
<p>You want to define your cloud resources with infrastructure as code tools. However, you do NOT enjoy writing large JSON files to deploy with the Azure Resource Manager (ARM) templates.</p>
<p>It turns out there are at least two tools that enable you to use F# to define Azure resources: <a href="https://compositionalit.github.io/farmer/">Farmer</a> and <a href="https://pulumi.com">Pulumi</a>.</p>
<p>In this article, I&rsquo;ll give a quick comparison of these two options. More excitingly, I&rsquo;ll show you how you could use both tools together in the same F# program!</p>
<h2 id="what-is-farmer">What is Farmer?</h2>
<p>Farmer is an F# library for rapidly authoring and deploying Azure architectures. It&rsquo;s a hand-crafted library that provides simple but powerful primitives to produce ARM Templates from F# code. Essentially, the result of a Farmer execution is a JSON ARM template that you can feed into Azure deployment tools.</p>
<p>Farmer relies heavily on F# computation expressions and feels like a DSL but with strong type safety and excellent IDE support. It&rsquo;s open-source, free to use, and it&rsquo;s just a NuGet library to install.</p>
<h2 id="what-is-pulumi">What is Pulumi?</h2>
<p>Pulumi is a more ambitious tool for all aspects of modern infrastructure as code. It&rsquo;s designed to create, deploy, and manage infrastructure on any cloud using several programming languages.</p>
<p>Pulumi comes with its own command-line interface (CLI) tool to orchestrate deployments, so it doesn&rsquo;t rely on ARM templates in any way. Pulumi supports Azure among other cloud providers, and the Azure SDK is automatically generated from formal specifications.</p>
<h2 id="how-are-they-different">How are they different?</h2>
<p>Farmer and Pulumi are different in many ways, but I want to focus on two aspects.</p>
<h3 id="azure-vs-any-cloud">Azure vs. Any Cloud</h3>
<p>Farmer is focused entirely on Azure, while Pulumi supports Azure, Azure Active Directory, AWS, Google Cloud, Kubernetes, Cloudflare, Digital Ocean, and several dozens of other deployment targets. A single Pulumi program can deploy resources to multiple environments, for instance, Azure, Azure AD, Kubernetes, and Cloudflare.</p>
<p>Farmer relies on Azure deployment tools, while Pulumi comes with its own. There are pros and cons to both approaches.</p>
<h3 id="hand-crafted-vs-auto-generated">Hand-crafted vs. Auto-Generated</h3>
<p>Farmer&rsquo;s F# code is designed to be concise and look great. It&rsquo;s opinionated and makes the most common scenarios short and straightforward. More obscure deployments may not be expressible directly with Farmer, but the library provides escape hatches to fall back to JSON.</p>
<p>Pulumi relies heavily on code generation. A resource definition is always a constructor call that accepts a bag of input properties and returns output properties. This means that you have access to the full API surface area, but you work on a low abstraction level.</p>
<h2 id="can-i-use-both">Can I use both?</h2>
<p>Essentially, Farmer is a DSL to build ARM Templates, while Pulumi is a deployment orchestration tool. They operate on a different level and aren&rsquo;t directly interchangeable.</p>
<p>This also means that you may want to use both to combine their strengths and powers. Below, I show exactly this: I deploy a simple Farmer template from a Pulumi program.</p>
<p><strong>Disclaimer</strong>: This is a proof-of-concept and can&rsquo;t be used for production deployments yet. See the &ldquo;How It Works&rdquo; section for details.</p>
<p>Let&rsquo;s write a sample Pulumi-Farmer program to deploy a Web App!</p>
<h3 id="resource-definitions">Resource Definitions</h3>
<p>I created a .NET Core console application and referenced NuGet packages <code>Pulumi.AzureNextGen</code>, <code>Pulumi.FSharp</code>, and <code>Farmer</code>. Now, I can define resources with Farmer builders:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#57606a">// Create a storage account
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">let</span> <span style="color:#953800">myStorageAccount</span> <span style="color:#0550ae">=</span> storageAccount <span style="color:#0550ae">{</span>
</span></span><span style="display:flex;"><span>    name <span style="color:#0a3069">&#34;farmerpulumisa&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#0550ae">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Create a web app with application insights that are connected to the storage account
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">let</span> <span style="color:#953800">myWebApp</span> <span style="color:#0550ae">=</span> webApp <span style="color:#0550ae">{</span>
</span></span><span style="display:flex;"><span>    name <span style="color:#0a3069">&#34;farmerpulumiweb&#34;</span>
</span></span><span style="display:flex;"><span>    setting <span style="color:#0a3069">&#34;storageKey&#34;</span> myStorageAccount<span style="color:#0550ae">.</span>Key
</span></span><span style="display:flex;"><span><span style="color:#0550ae">}</span>
</span></span></code></pre></div><p>These definitions will result in four Azure resources: a Storage Account, an App Service Plan, an Application Insights component, and a Web App (App Service).</p>
<h3 id="deployment">Deployment</h3>
<p>The next step is to define a deployment with a target location and a list of resources to deploy:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#57606a">// Create an ARM template
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">let</span> <span style="color:#953800">deployment</span> <span style="color:#0550ae">=</span> arm <span style="color:#0550ae">{</span>
</span></span><span style="display:flex;"><span>    location <span style="color:#24292e">Location</span><span style="color:#1f2328">.</span>NorthEurope
</span></span><span style="display:flex;"><span>    add_resources <span style="color:#0550ae">[</span>
</span></span><span style="display:flex;"><span>        myStorageAccount
</span></span><span style="display:flex;"><span>        myWebApp
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">]</span>
</span></span><span style="display:flex;"><span><span style="color:#0550ae">}</span>
</span></span></code></pre></div><h3 id="run-pulumi-deployment">Run Pulumi Deployment</h3>
<p>The last step is to declare the application entry point. The application calls a helper function of mine called <code>FarmerDeploy.run</code>, which accepts a resource group name and the deployment object:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#57606a">// Deploy with Pulumi
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#0550ae">[&lt;</span>EntryPoint<span style="color:#0550ae">&gt;]</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">main</span> <span style="color:#0550ae">_</span> <span style="color:#0550ae">=</span> <span style="color:#24292e">FarmerDeploy</span><span style="color:#1f2328">.</span>run <span style="color:#0a3069">&#34;my-resource-group&#34;</span> deployment
</span></span></code></pre></div><p>Now, I navigate to the application folder in a command line and run <code>pulumi up</code> to deploy the program:</p>
<pre tabindex="0"><code>$ pulumi up

Updating (dev)

     Type                                               Name                  Status      
 +   pulumi:pulumi:Stack                                azure-nextgen-fs-dev  created     
 +   ├─ azure-nextgen:storage/v20190401:StorageAccount  farmerpulumisa        created     
 +   ├─ azure-nextgen:web/v20180201:AppServicePlan      farmerpulumiwebFarm   created     
 +   ├─ azure-nextgen:insights/v20150501:Component      farmerpulumiwebAi     created     
 +   └─ azure-nextgen:web/v20160801:WebApp              farmerpulumiweb       created     
 
Resources:
    + 5 created

Duration: 53s
</code></pre><p>Tada! The Farmer-Pulumi application is deployed to Azure!</p>
<h2 id="how-it-works">How It Works</h2>
<p>Here is how this deployment worked:</p>
<ol>
<li>Pulumi orchestrates the deployment, so the project is a Pulumi F# project.</li>
<li><code>FarmerDeploy.run</code> accepts a Farmer deployment and converts it to a raw JSON string.</li>
<li>It sends the JSON to the Pulumi Azure NextGen provider (a plugin installed on my system).</li>
<li>The provider uses <a href="https://github.com/pulumi/arm2pulumi">arm2pulumi</a> to parse the JSON template to the Pulumi resource model.</li>
<li>The resource model is sent back to the F# program, which instantiates resources with proper arguments.</li>
<li>The resources are registered with the Pulumi engine.</li>
<li>The engine orchestrates the deployment and invokes Azure API to create the resources.</li>
</ol>
<p><strong>Note</strong>: The Pulumi Azure NextGen provider is currently in preview. In particular, steps 4 and 5 above and not production-ready yet. This means that the prototype may fall short for more sophisticated Farmer deployments (and, therefore, more sophisticated JSON templates).</p>
<p>You can find the full example <a href="https://github.com/mikhailshilkov/fsharp-advent-pulumi/tree/master/2020">here</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>More than anything, this article is a manifestation of the power of applying general-purpose programming languages to engineering cloud applications. Different tools and approaches may be combined in ways they were not initially designed for.</p>
<p>If you are interested in a production-grade Farmer-in-Pulumi integration, consider leaving a comment below with a scenario you have in mind, or just hit the Heart button on the top-left. Thank you!</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                             
                                <category scheme="https://mikhail.io/tags/fsharp" term="fsharp" label="FSharp" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Get Up and Running with Azure Synapse and Pulumi]]></title>
            <link href="https://mikhail.io/2020/12/get-up-and-running-with-azure-synapse-and-pulumi/"/>
            <id>https://mikhail.io/2020/12/get-up-and-running-with-azure-synapse-and-pulumi/</id>
            
            <published>2020-12-04T00:00:00+00:00</published>
            <updated>2020-12-04T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Use infrastructure as code to automate deployment of an Azure Synapse workspace</blockquote><p>Azure Synapse is an integrated analytics service that combines enterprise data warehousing of Azure SQL Data Warehouse and Big Data analytics of Apache Spark. Azure Synapse is a managed service well integrated with other Azure services for data ingestion and business analytics.</p>
<p>You could use the Azure portal to get started with Azure Synapse, but it can be hard to define sophisticated infrastructure for your analytics pipeline using the portal alone, and many users need to apply version control to their cloud configurations.</p>
<p>The alternative is to use an infrastructure as code tool to automate building and deploying cloud resources. This article demonstrates how to provision an Azure Synapse workspace using Pulumi and general-purpose programming languages like Python and C#.</p>
<h2 id="azure-synapse-components">Azure Synapse Components</h2>
<p>Let&rsquo;s start by introducing the components required to provision a basic Azure Synapse workspace. To follow along with the <a href="https://docs.microsoft.com/en-us/azure/synapse-analytics/get-started">Synapse Getting Started Guide</a>, you need the following key Azure infrastructure components:</p>
<ul>
<li><strong>Resource Group</strong> to contain all other resources.</li>
<li><strong>Storage Account</strong> to store input data and analytics artifacts.</li>
<li><strong>Azure Synapse Workspace</strong>—a collaboration boundary for cloud-based analytics in Azure.</li>
<li><strong>SQL Pool</strong>—a dedicated Synapse SQL pool to run T-SQL based analytics.</li>
<li><strong>Spark Pool</strong> to use Apache Spark analytics.</li>
<li><strong>IP Filters</strong> and <strong>Role Assignments</strong> for secure access control.</li>
</ul>
<h2 id="infrastructure-as-code">Infrastructure as Code</h2>
<p>Let&rsquo;s walk through the steps to build a workspace with all the components mentioned above. We&rsquo;ll use Pulumi to provision the necessary resources. Feel free to pick the language of your choice that will apply to all code snippets.</p>
<p>You can check out the <a href="https://github.com/pulumi/examples/tree/master/aws-ts-lambda-thumbnailer">full source code</a> in the Pulumi Examples.</p>
<h3 id="resource-group">Resource Group</h3>
<p>Let&rsquo;s start by defining a resource group to contain all other resources. Be sure to adjust its name and region to your preferred values.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>resource_group <span style="color:#0550ae">=</span> resources<span style="color:#0550ae">.</span>ResourceGroup<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;resourceGroup&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    resource_group_name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;synapse-rg&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    location<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;westus2&#34;</span><span style="color:#1f2328">)</span>
</span></span></code></pre></div><h3 id="data-lake-storage-account">Data Lake Storage Account</h3>
<p>Synapse workspace will store data in a data lake storage account. We use a Standard Read-Access Geo-Redundant Storage account (SKU <code>Standard_RAGRS</code>) for this purpose. Make sure to change the <code>accountName</code> to your own globally unique name.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>storage_account <span style="color:#0550ae">=</span> storage<span style="color:#0550ae">.</span>StorageAccount<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;storageAccount&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    resource_group_name<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    location<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>location<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    account_name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;yoursynapsesa&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    access_tier<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;Hot&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    enable_https_traffic_only<span style="color:#0550ae">=</span><span style="color:#cf222e">True</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    is_hns_enabled<span style="color:#0550ae">=</span><span style="color:#cf222e">True</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    kind<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;StorageV2&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    sku<span style="color:#0550ae">=</span>storage<span style="color:#0550ae">.</span>SkuArgs<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>        name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;Standard_RAGRS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">))</span>
</span></span></code></pre></div><p>We&rsquo;ll use the <code>users</code> blob container as the analytics file system.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>users <span style="color:#0550ae">=</span> storage<span style="color:#0550ae">.</span>BlobContainer<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;users&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    resource_group_name<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    account_name<span style="color:#0550ae">=</span>storage_account<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    container_name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;users&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    public_access<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;None&#34;</span><span style="color:#1f2328">)</span>
</span></span></code></pre></div><h3 id="synapse-workspace">Synapse Workspace</h3>
<p>It&rsquo;s time to use all of the above to provision an Azure Synapse workspace! Adjust the name and the SQL credentials in the definition below.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>workspace <span style="color:#0550ae">=</span> synapse<span style="color:#0550ae">.</span>Workspace<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;workspace&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    resource_group_name<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    location<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>location<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    workspace_name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;mysynapse&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    default_data_lake_storage<span style="color:#0550ae">=</span>synapse<span style="color:#0550ae">.</span>DataLakeStorageAccountDetailsArgs<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>        account_url<span style="color:#0550ae">=</span>data_lake_storage_account_url<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        filesystem<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;users&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>    identity<span style="color:#0550ae">=</span>synapse<span style="color:#0550ae">.</span>ManagedIdentityArgs<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>        <span style="color:#6639ba">type</span><span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;SystemAssigned&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>    sql_administrator_login<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;sqladminuser&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    sql_administrator_login_password<span style="color:#0550ae">=</span>random<span style="color:#0550ae">.</span>RandomPassword<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;workspacePwd&#34;</span><span style="color:#1f2328">,</span> length<span style="color:#0550ae">=</span><span style="color:#0550ae">12</span><span style="color:#1f2328">)</span><span style="color:#0550ae">.</span>result<span style="color:#1f2328">)</span>
</span></span></code></pre></div><blockquote>
<p>Note that we also defined a system-assigned managed identity for the workspace.</p></blockquote>
<h3 id="security-setup">Security Setup</h3>
<p>You need to allow access to the workspace with a firewall rule. The following is a blank access rule but feel free to restrict it to your target IP range.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>allow_all <span style="color:#0550ae">=</span> synapse<span style="color:#0550ae">.</span>IpFirewallRule<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;allowAll&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    resource_group_name<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    workspace_name<span style="color:#0550ae">=</span>workspace<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    rule_name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;allowAll&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    end_ip_address<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;255.255.255.255&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    start_ip_address<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;0.0.0.0&#34;</span><span style="color:#1f2328">)</span>
</span></span></code></pre></div><p>The following snippet assigns the <strong>Storage Blob Data Contributor</strong> role to the workspace managed identity and your target user. If you use the Azure CLI, run <code>az ad signed-in-user show --query=objectId</code> to look up your user ID.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>subscription_id <span style="color:#0550ae">=</span> resource_group<span style="color:#0550ae">.</span>id<span style="color:#0550ae">.</span>apply<span style="color:#1f2328">(</span><span style="color:#cf222e">lambda</span> <span style="color:#6639ba">id</span><span style="color:#1f2328">:</span> <span style="color:#6639ba">id</span><span style="color:#0550ae">.</span>split<span style="color:#1f2328">(</span><span style="color:#0a3069">&#39;/&#39;</span><span style="color:#1f2328">)[</span><span style="color:#0550ae">2</span><span style="color:#1f2328">])</span>
</span></span><span style="display:flex;"><span>role_definition_id <span style="color:#0550ae">=</span> subscription_id<span style="color:#0550ae">.</span>apply<span style="color:#1f2328">(</span><span style="color:#cf222e">lambda</span> <span style="color:#6639ba">id</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">f</span><span style="color:#0a3069">&#34;/subscriptions/</span><span style="color:#0a3069">{</span><span style="color:#6639ba">id</span><span style="color:#0a3069">}</span><span style="color:#0a3069">/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe&#34;</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>authorization<span style="color:#0550ae">.</span>RoleAssignment<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;storageAccess&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    role_assignment_name<span style="color:#0550ae">=</span>random<span style="color:#0550ae">.</span>RandomUuid<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;roleName&#34;</span><span style="color:#1f2328">)</span><span style="color:#0550ae">.</span>result<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    scope<span style="color:#0550ae">=</span>storage_account<span style="color:#0550ae">.</span>id<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    principal_id<span style="color:#0550ae">=</span>workspace<span style="color:#0550ae">.</span>identity<span style="color:#0550ae">.</span>principal_id<span style="color:#0550ae">.</span>apply<span style="color:#1f2328">(</span><span style="color:#cf222e">lambda</span> v<span style="color:#1f2328">:</span> v <span style="color:#0550ae">or</span> <span style="color:#0a3069">&#34;&lt;preview&gt;&#34;</span><span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>    principal_type<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;ServicePrincipal&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    role_definition_id<span style="color:#0550ae">=</span>role_definition_id<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>authorization<span style="color:#0550ae">.</span>RoleAssignment<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;userAccess&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    role_assignment_name<span style="color:#0550ae">=</span>random<span style="color:#0550ae">.</span>RandomUuid<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;userRoleName&#34;</span><span style="color:#1f2328">)</span><span style="color:#0550ae">.</span>result<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    scope<span style="color:#0550ae">=</span>storage_account<span style="color:#0550ae">.</span>id<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    principal_id<span style="color:#0550ae">=</span>config<span style="color:#0550ae">.</span>get<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;userObjectId&#34;</span><span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>    principal_type<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;User&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    role_definition_id<span style="color:#0550ae">=</span>role_definition_id<span style="color:#1f2328">)</span>
</span></span></code></pre></div><h3 id="sql-and-spark-pools">SQL and Spark Pools</h3>
<p>Finally, let&rsquo;s add two worker pools to the Synapse workspace. A SQL pool for T-SQL analytic queries&hellip;</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>sql_pool <span style="color:#0550ae">=</span> synapse<span style="color:#0550ae">.</span>SqlPool<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;sqlPool&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    resource_group_name<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    location<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>location<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    workspace_name<span style="color:#0550ae">=</span>workspace<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    sql_pool_name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;SQLPOOL1&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    collation<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;SQL_Latin1_General_CP1_CI_AS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    create_mode<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;Default&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    sku<span style="color:#0550ae">=</span>synapse<span style="color:#0550ae">.</span>SkuArgs<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>        name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;DW100c&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">))</span>
</span></span></code></pre></div><p>&hellip; and a Spark pool for Big Data analytics.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>spark_pool <span style="color:#0550ae">=</span> synapse<span style="color:#0550ae">.</span>BigDataPool<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;sparkPool&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    resource_group_name<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    location<span style="color:#0550ae">=</span>resource_group<span style="color:#0550ae">.</span>location<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    workspace_name<span style="color:#0550ae">=</span>workspace<span style="color:#0550ae">.</span>name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    big_data_pool_name<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;Spark1&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    auto_pause<span style="color:#0550ae">=</span>synapse<span style="color:#0550ae">.</span>AutoPausePropertiesArgs<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>        delay_in_minutes<span style="color:#0550ae">=</span><span style="color:#0550ae">15</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        enabled<span style="color:#0550ae">=</span><span style="color:#cf222e">True</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>    auto_scale<span style="color:#0550ae">=</span>synapse<span style="color:#0550ae">.</span>AutoScalePropertiesArgs<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>        enabled<span style="color:#0550ae">=</span><span style="color:#cf222e">True</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        max_node_count<span style="color:#0550ae">=</span><span style="color:#0550ae">3</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        min_node_count<span style="color:#0550ae">=</span><span style="color:#0550ae">3</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>    node_count<span style="color:#0550ae">=</span><span style="color:#0550ae">3</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    node_size<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;Small&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    node_size_family<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;MemoryOptimized&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    spark_version<span style="color:#0550ae">=</span><span style="color:#0a3069">&#34;2.4&#34;</span><span style="color:#1f2328">)</span>
</span></span></code></pre></div><h2 id="ready-to-dive-into-analytics">Ready to Dive into Analytics</h2>
<p>Our resource definitions are ready. Run <code>pulumi up</code> to provision your Azure Synapse infrastructure.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ pulumi up
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>Do you want to perform this update? yes
</span></span><span style="display:flex;"><span>Updating <span style="color:#0550ae">(</span>dev<span style="color:#0550ae">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>     Type                                                            Name                  Plan
</span></span><span style="display:flex;"><span> +   pulumi:pulumi:Stack                                             azure-py-synapse-dev  created
</span></span><span style="display:flex;"><span> +   ├─ azure-nextgen:resources/latest:ResourceGroup                 resourceGroup         created
</span></span><span style="display:flex;"><span> +   ├─ azure-nextgen:storage/latest:StorageAccount                  storageAccount        created
</span></span><span style="display:flex;"><span> +   ├─ azure-nextgen:storage/latest:BlobContainer                   users                 created
</span></span><span style="display:flex;"><span> +   ├─ azure-nextgen:synapse/v20190601preview:Workspace             workspace             created
</span></span><span style="display:flex;"><span> +   ├─ random:index:RandomUuid                                      roleName              created
</span></span><span style="display:flex;"><span> +   └─ azure-nextgen:authorization/v20200401preview:RoleAssignment  storageAccess         created
</span></span><span style="display:flex;"><span> +   ├─ random:index:RandomUuid                                      userRoleName          created
</span></span><span style="display:flex;"><span> +   ├─ azure-nextgen:authorization/v20200401preview:RoleAssignment  userAccess            created
</span></span><span style="display:flex;"><span> +   ├─ azure-nextgen:synapse/v20190601preview:IpFirewallRule        allowAll              created
</span></span><span style="display:flex;"><span> +   ├─ azure-nextgen:synapse/v20190601preview:SqlPool               sqlPool               created
</span></span><span style="display:flex;"><span> +   ├─ azure-nextgen:synapse/v20190601preview:BigDataPool           sparkPool             created
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> Resources:
</span></span><span style="display:flex;"><span>    + <span style="color:#0550ae">12</span> created
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Duration: 10m51s
</span></span></code></pre></div><p>You can now navigate to the <a href="https://docs.microsoft.com/en-us/azure/synapse-analytics/get-started-analyze-sql-pool">Azure Synapse Quickstart, Step 2</a>, and follow along with the data analysis tutorial.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Azure Synapse is a managed analytics service that accelerates time to insight across data warehouses and big data workloads. A Synapse workspace is a critical component of your cloud infrastructure that you should provision with infrastructure as code and other management best practices.</p>
<p>Pulumi and Azure NextGen provider open up full access to all types of Azure resources using your favorite programming languages, including Python, C#, and TypeScript. Navigate to the complete Azure Synapse example in <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-py-synapse">Python</a>, <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-cs-synapse">C#</a>, or <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-ts-synapse">TypeScript</a> and get started today.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Running Container Images in AWS Lambda]]></title>
            <link href="https://mikhail.io/2020/12/aws-lambda-container-support/"/>
            <id>https://mikhail.io/2020/12/aws-lambda-container-support/</id>
            
            <published>2020-12-01T00:00:00+00:00</published>
            <updated>2020-12-01T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>AWS Lambda launches support for packaging and deploying functions as container images</blockquote><p>When AWS Lambda launched in 2014, it pioneered the concept of Function-as-a-Service. Developers could write a function in one of the supported programming languages, upload it to AWS, and Lambda executes the function on every invocation.</p>
<p>Ever since then, a zip archive of application code or binaries has been the only supported deployment option. Even AWS Lambda Layers—reusable components automatically merged into the application code—used the zip packaging format.</p>
<p>Today, AWS announced that AWS Lambda now supports packaging serverless functions as container images. This means that you can deploy a custom Docker or OCI image as an AWS Lambda function.</p>
<h2 id="why-use-container-images-for-aws-lambda">Why Use Container Images for AWS Lambda?</h2>
<p>The first production-ready version of Docker was released in October 2014, just one month before the announcement of AWS Lambda. Container images are now the de-facto standard of application packaging. Containers run in local development loops, in Kubernetes clusters, including Amazon EKS, as well as in Amazon ECS and AWS Fargate. Docker is embraced across the cloud industry, for instance, Google Cloud Run is a serverless offering centered around container images.</p>
<p>With today&rsquo;s launch, AWS Lambda can run functions packaged as container images, enabling customers to build serverless applications using familiar tools, preferred languages, and required dependencies.</p>
<p>Here are essential scenarios enabled and improved by container image deployments:</p>
<ul>
<li><strong>Package binaries and libraries</strong> that may not be available as a NPM (or another package manager) module. Our video processing example below demonstrates this capability.</li>
<li><strong>Code in an arbitrary programming language</strong>. While already possible with Lambda Layers and Runtime API, container images will streamline the experience of developing functions in languages not native to AWS Lambda.</li>
<li><strong>Use custom OS and custom runtime versions</strong> to match requirements and standard practices in a given company.</li>
<li><strong>Deploy large files and packages</strong>. Container images up to 10 GB are supported, as opposed to the previous hard limit of 250 MB of unzipped files.</li>
<li><strong>Reuse existing base images</strong> to bring reliable and battle-tested implementations from the broad community or domain-specific scenarios.</li>
<li><strong>Apply centralized container image building and packaging governance and security requirements</strong> to AWS Lambda deployments. This provides Enterprise customers with a higher level of control.</li>
</ul>
<p>AWS Lambda functions packaged as container images will continue to benefit from the event-driven execution model, consumption-based billing, automatic scaling, high availability, fast start-up, and native integrations with numerous AWS services.</p>
<h2 id="how-it-works">How It Works</h2>
<p>You can get started with deploying containers to AWS Lambda in three steps:</p>
<ol>
<li><strong>Prepare a container definition</strong> that implements the Lambda Runtime Interface as explained below.</li>
<li><strong>Build</strong> the container image and <strong>publish</strong> it to Amazon Elastic Container Registry (ECR).</li>
<li><strong>Deploy an AWS Lambda</strong>, grant it access to the ECR, and point it to the container image.</li>
</ol>
<p><img src="lambdacontainers.png" alt="Lambda Function packaged as a Container"></p>
<p>Your container image has to implement AWS Lambda runtime API. Runtime API is a simple HTTP-based protocol with operations to retrieve invocation data, submit responses, and report errors.</p>
<p>Therefore, not every container image may be deployed to AWS Lambda. You have two main options:</p>
<ul>
<li><strong>Choose a base image provided by AWS</strong>, which already includes the Runtime Interface Client (RIC). AWS provides base images for Node.js, Python, Java, Go, .NET Core, and Ruby.</li>
<li><strong>Use an arbitrary base image</strong> and implement the API with an AWS Lambda runtime client SDK. Using a custom base image, you can leverage open-source AWS Lambda Runtime Interface Client to make the image compatible with Lambda’s runtime API.</li>
</ul>
<h2 id="container-images-in-aws-lambda-vs-aws-fargate">Container Images in AWS Lambda vs. AWS Fargate</h2>
<p>Lambda Container image support further blurs the lines between Lambda and Fargate. It’s important to understand the remaining differences to decide which service to use in a given scenario:</p>
<ul>
<li><strong>Cost structure</strong>. Lambda is charged per execution, while Fargate has a fixed cost per vCPU per hour regardless of the actual workload. Depending on requests per second served by a single CPU, one model can be much more frugal than the other.</li>
<li><strong>Scale to zero</strong>. Consequently, an idle Lambda function costs nothing while Fargate still incurs fixed minimal running costs. Therefore, breaking an application down to many small specialized Lambda functions is much more practical and common than micro Fargate instances.</li>
<li><strong>Burst workloads</strong>. Lambda scales on a per-request basis and can go from zero to a thousand instances in seconds. It’s a great fit for applications with bursty workloads that need to switch from idle to full capacity and back.</li>
<li><strong>Performance</strong>. Fargate runs on more dedicated resources, so overall performance will likely be better than on Lambda. For time-sensitive and critical APIs, Fargate may offer a fast and consistent experience superior to Lambda, especially on high percentiles.</li>
<li><strong>Integration with AWS services</strong>. Lambda comes with native integration with 100+ AWS services. It’s straightforward to trigger a function for incoming SQS messages or new files in an S3 bucket, which requires more wiring for Fargate tasks.</li>
<li><strong>Resource limits</strong>. Lambda executions are limited to 15 minutes and may only consume up to 10 GB of RAM. Fargate may be the only option for long-running resource-demanding jobs.</li>
</ul>
<p>Overall, Lambda shines for unpredictable or inconsistent workloads and applications easily expressed as isolated functions triggered by events in other AWS services.</p>
<h2 id="example-serverless-video-thumbnailer-with-aws-lambda-and-pulumi">Example: Serverless Video Thumbnailer with AWS Lambda and Pulumi</h2>
<p>Let&rsquo;s walk through the steps to build an example application with container-based AWS Lambda. In this scenario, a function runs every time a new video is uploaded to an Amazon S3 bucket. It relies on FFmpeg tools to produce a thumbnail of the uploaded video and uploads the thumbnail back to the same S3 bucket.</p>
<p>We&rsquo;ll use Pulumi to provision the necessary resources. You can check out the <a href="https://github.com/pulumi/examples/tree/master/aws-ts-lambda-thumbnailer">full source code</a> in the Pulumi Examples.</p>
<h3 id="define-a-dockerfile">Define a Dockerfile</h3>
<p>Here are the key features of the container image for our Thumbnailer:</p>
<ul>
<li>Based on the <code>amazon/aws-lambda-nodejs:12</code> image</li>
<li>Installs AWS CLI to copy objects to and from S3</li>
<li>Installs FFmpeg to process video files</li>
<li>Copies the <code>index.js</code> with function implementation</li>
<li>Points the <code>index.handler</code> command</li>
</ul>
<p>You can find the full Dockerfile <a href="https://github.com/pulumi/examples/blob/master/aws-ts-lambda-thumbnailer/docker-ffmpeg-thumb/Dockerfile">here</a>.</p>
<h3 id="create-an-s3-bucket">Create an S3 Bucket</h3>
<p>Now, let&rsquo;s start composing our Pulumi program in TypeScript. The first step is to define an S3 bucket.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">aws</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/aws&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// A bucket to store videos and thumbnails.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">const</span> <span style="color:#1f2328">bucket</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">s3</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Bucket</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;bucket&#34;</span><span style="color:#1f2328">);</span>
</span></span></code></pre></div><h3 id="build-the-container-image-and-publish-it-to-ecr">Build the container image and publish it to ECR</h3>
<p>We can use <a href="https://www.pulumi.com/docs/guides/crosswalk/aws/">Pulumi Crosswalk for AWS</a> to build the Docker image and publish it to a new ECR repository with just three lines of code.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">awsx</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/awsx&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">image</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">awsx</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ecr</span><span style="color:#1f2328">.</span><span style="color:#1f2328">buildAndPushImage</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;image&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">context</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;./docker-ffmpeg-thumb&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>The local <code>docker-ffmpeg-thumb</code> folder contains the application files (<code>Dockerfile</code> and <code>index.js</code>).</p>
<h3 id="setup-a-role">Setup a role</h3>
<p>Next, we define an IAM role and a policy attachment to grant AWS Lambda access to ECR, CloudWatch, and other supporting services.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">role</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">iam</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Role</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;thumbnailerRole&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">assumeRolePolicy</span>: <span style="color:#cf222e">aws.iam.assumeRolePolicyForPrincipal</span><span style="color:#1f2328">({</span> <span style="color:#1f2328">Service</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;lambda.amazonaws.com&#34;</span> <span style="color:#1f2328">}),</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">iam</span><span style="color:#1f2328">.</span><span style="color:#1f2328">RolePolicyAttachment</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;lambdaFullAccess&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">role</span>: <span style="color:#cf222e">role.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">policyArn</span>: <span style="color:#cf222e">aws.iam.ManagedPolicies.AWSLambdaFullAccess</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><h3 id="configure-your-aws-lambda-function">Configure your AWS Lambda function</h3>
<p>It&rsquo;s time to define the AWS Lambda function itself! It&rsquo;s as simple as giving it a name and pointing to the image URI returned from the ECR. Also, we assign the role and increase the timeout to 15 minutes, as video processing may take a while.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">thumbnailer</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">lambda</span><span style="color:#1f2328">.</span><span style="color:#6639ba">Function</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;thumbnailer&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">packageType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Image&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">imageUri</span>: <span style="color:#cf222e">image.imageValue</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">role</span>: <span style="color:#cf222e">role.arn</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">timeout</span>: <span style="color:#cf222e">900</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><h3 id="trigger-on-new-videos">Trigger on new videos</h3>
<p>Finally, we can assign a trigger to the function using the <code>bucket.onObjectCreated</code> helper method. We want to limit the function to only process <code>mp4</code> files.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#57606a">// When a new video is uploaded, run the FFMPEG task on the video file.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#1f2328">bucket</span><span style="color:#1f2328">.</span><span style="color:#1f2328">onObjectCreated</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;onNewVideo&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">thumbnailer</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">filterSuffix</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;.mp4&#34;</span> <span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>And that is it! We run <code>pulumi up</code> to get the thumbnailed service up and running.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Support for container images in AWS Lambda brings the power and usability of industry-standard packaging to serverless functions. It becomes easy to reuse application components packaged with the ubiquitous deployment format.</p>
<p>In this post, we’ve shown how to use Pulumi to build a container image and configure an AWS Lambda to run it. Pulumi makes it easy to create artifacts and provision and manage cloud infrastructure on any cloud using familiar programming languages, including TypeScript, Python, Go and .NET. Docker images, ECR registries, and Lambda functions can be managed within the same infrastructure definition.</p>
<p>Further steps:</p>
<ul>
<li>Check out the full <a href="https://github.com/pulumi/examples/tree/master/aws-ts-lambda-thumbnailer">Lambda + Docker example</a> in the Pulumi Examples.</li>
<li><a href="https://www.pulumi.com/docs/get-started/aws/">Get Started</a> with Pulumi for AWS today.</li>
</ul>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/aws" term="aws" label="AWS" />
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/containers" term="containers" label="Containers" />
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[How to Monitor Your Business with BAM Tools?]]></title>
            <link href="https://mikhail.io/2020/11/how-to-monitor-your-business-with-bam-tools/"/>
            <id>https://mikhail.io/2020/11/how-to-monitor-your-business-with-bam-tools/</id>
            
                    <author>
                        <name>Nadeem Ahamed Riswanbasha</name>
                    </author>
            <published>2020-11-18T00:00:00+00:00</published>
            <updated>2020-11-18T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Guest Post by Nadeem Ahamed Riswanbasha</blockquote><h2 id="introduction">Introduction</h2>
<p>Business Activity Monitoring (BAM) plays a vital role in identifying and resolving issues in the flow of a business process. Also, the concept of BAM is not very new to the world of technology because there are already a handful of organisations leveraging BAM to achieve end-to-end business tracking.</p>
<p>This blog will feed you with everything that you must know about <a href="https://www.serverless360.com/business-activity-monitoring">Business Activity Monitoring</a> starting right from scratch.</p>
<p>Moving forward, we will see terms like business processes, transactions, and stages very often. It would be better to figure out these terms to understand the concept of BAM.</p>
<ul>
<li><strong>Business Process</strong> - A collection of one or more related business transactions</li>
<li><strong>Transaction</strong> - An operation of the business which represents a set of business activities</li>
<li><strong>Stage</strong> - An activity within the transaction. A transaction is made up of one or more stages.</li>
</ul>
<p><img src="./image-1.png" alt="BAM"></p>
<h2 id="what-is-bam">What is BAM?</h2>
<ul>
<li>As mentioned above, BAM is a technology that is used to identify issues and risks in a business process where this increases the efficiency of any application.</li>
<li>It is a process of tracking events and transactions and then presenting the information in the form of dashboards and reports which eventually helps in performing analysis on the collected set of data.</li>
</ul>
<blockquote>
<p><strong>Business Activity Monitoring is used for both on-premise and cloud applications to gain complete visibility.</strong></p></blockquote>
<p>Any BAM tool should cover the following key areas irrespective of the type of application (cloud, on-premise, or hybrid).</p>
<ul>
<li><strong>Aggregation</strong> - This is the process of capturing &amp; collecting data from your business processes.</li>
<li><strong>Analysis</strong> - After that, the data should be analysed by performing various data operations.</li>
<li><strong>Presentation</strong> - The analysed data is represented in the form of dashboards for quick understanding.</li>
</ul>
<h2 id="role-of-bam-in-monitoring-serverless-applications">Role of BAM in Monitoring Serverless Applications</h2>
<p>Imagine a business scenario where mostly more than one Azure resource will be used to define a business process. As the needs of the user keep increasing, the business process keeps growing, which turns out to be complex and makes monitoring business processes hard and challenging.</p>
<p>Here is a real-time example to make you clearly understand what business process is and how Business Activity Monitoring can be used in these situations.</p>
<p>Below is a car booking application consisting of various Azure services like Web Apps, Service Bus, Logic Apps, and Azure Functions where all these are integrated to provide a particular solution.</p>
<ul>
<li>Consider a Business user, who needs message tracking through every stage in the above business activity.</li>
<li>There would also be needs for the user to be informed of any exception in the business transaction along with the reason behind the failure.</li>
<li>A Business development manager would need to have analytic information on the booking trends at various locations.</li>
</ul>
<p><img src="./image-2.png" alt="BAM"></p>
<p>Serverless360 is one tool that can satisfy all the above needs. It has an in-built feature called Business Activity Monitoring to provide complete visibility and end to end tracking on your business process.</p>
<p>The picture below is a sample flow diagram for the car booking application where each stage &amp; message can be tracked very easily, and that would reduce the support overhead.</p>
<p><img src="./image-3.png" alt="BAM"></p>
<h2 id="serverless360-bam">Serverless360 BAM</h2>
<p>This provides business process monitoring solution for cloud architectures, and there are numerous operations supported by BAM to perform on these business processes.</p>
<p>So, now lets look at what Serverless360 BAM is capable of when it comes to providing end to end visibility.</p>
<h3 id="tracking">Tracking</h3>
<p>A graphical designer will facilitate modelling your business process transactions in the form of an activity diagram. You will define the stages of your business transactions here.</p>
<p>In the above-mentioned car booking example, there would be a need to track the Driver Id, User Id &amp; Location, which can be achieved at ease by configuring the required properties in the stages of the business process.</p>
<p><strong>Also, each stage is represented with different colours to make it easy for the support team to identify if they are in success, failure, in progress, or not executed state.</strong></p>
<p><img src="./image-4.png" alt="BAM"></p>
<h3 id="filtering">Filtering</h3>
<p>BAM in Serverless360 provides <strong>advanced filtering</strong> where you can comfortably filter the specified business transactions by using simple <strong>search queries</strong>. In Addition to query filtering, it also supports filtering based on the time interval.</p>
<p>In the car booking scenario, this feature allows you to manage and search for your business transactions using properties like customer id, driver id, location, and even by the status of the transaction.</p>
<p><img src="./image-5.png" alt="BAM"></p>
<h3 id="reprocessing">Reprocessing</h3>
<p>By tracking the business processes, it would be offhand to identify the failed transactions but just identifying will not help the user in any way and that is why BAM in Serverless360 is enriched with a feature called Reprocessing.</p>
<p>It also states the reason for failure, and that makes it easy for the support person to change the required data and reprocess the message to the stage by just clicking the reprocess option just above your business flow diagram.</p>
<p>It supports two types of reprocessing</p>
<ul>
<li><strong>Dynamic reprocessing:</strong> This can be useful when the user wants to reprocess along with the tracked property values as the header.</li>
<li><strong>Bulk reprocessing:</strong> For this, you need to map a reprocess setting of a particular stage to that respective transaction.</li>
</ul>
<p><img src="./image-6.png" alt="BAM"></p>
<h2 id="monitoring">Monitoring</h2>
<p>Serverless360 has several monitors to monitor Azure Serverless applications, and in the same way, it also has a powerful monitor that is the Business Process Monitoring for BAM, which includes Exception Monitoring and Query Monitoring.</p>
<h3 id="business-process-monitoring">Business Process Monitoring</h3>
<ul>
<li>Serverless360 comes with the out of box monitoring solution for <strong>monitoring business processes based on queries and exceptions</strong> called Business Process Monitor.</li>
<li>These monitors support alerting through notification channels when the count of failed transactions goes beyond the certain limit at a location or alert whenever there is an Exception.</li>
<li>It is also possible to view the historical record of alert reports in the calendar view.</li>
</ul>
<p><img src="./image-7.png" alt="BAM"></p>
<h3 id="exception-monitoring">Exception Monitoring</h3>
<ul>
<li>This allows users to log exceptions along with exception code in the stages of a business transaction.</li>
<li>The logged exceptions will be displayed to the users on selecting the transaction instance.</li>
<li>Users will also need alerts immediately whenever an exception gets logged in any of the stages of the transactions.</li>
</ul>
<p><img src="./image-8.png" alt="BAM"></p>
<h3 id="query-monitoring">Query Monitoring</h3>
<ul>
<li>This type of monitoring will alert the configured user when a given query violates the configured threshold values on the business process.</li>
<li>You can manually set the error and warning threshold conditions while creating the query monitor.</li>
</ul>
<p><img src="./image-9.png" alt="BAM"></p>
<h2 id="analytics">Analytics</h2>
<ul>
<li><strong>Dashboards</strong> play a major role in analysing the data as it supports creating widgets and custom dashboards based on the data in your business processes.</li>
<li>It helps the support team to visualise the data in the form of easily understandable charts based on the configured queries.</li>
</ul>
<p><img src="./image-10.png" alt="BAM"></p>
<blockquote>
<p><strong>BAM in Serverless360 supports tracking not only Azure solutions but also other hybrid applications (a combination of cloud and on-premise components).</strong></p></blockquote>
<h2 id="conclusion">Conclusion</h2>
<p>As discussed above, BAM is highly essential for both cloud and on-premise applications as it will reduce the pressure on the support team by identifying the issues in your business transactions. Also, I hope this blog would help you to get a basic idea of how Serverless360 works when it comes to Business Activity Monitoring.</p>
<p>Also read,</p>
<ul>
<li><a href="https://www.serverless360.com/blog/serverless360-bam-quick-walkthrough">Serverless360 BAM – A Quick Walkthrough</a></li>
<li><a href="https://www.serverless360.com/blog/bam-message-tracking-in-azure-serverless-application">BAM Message tracking in Azure Applications</a></li>
<li><a href="https://www.serverless360.com/blog/microsoft-flow-monitoring-using-serverless360-bam">Microsoft Flow Monitoring using Serverless360 BAM</a></li>
<li><a href="https://www.serverless360.com/blog/hybrid-application-tracking-using-serverless360-bam">Hybrid Application Tracking Using Serverless360 BAM</a></li>
<li><a href="https://www.serverless360.com/business-activity-monitoring">Know more features of BAM in Serverless360</a></li>
</ul>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/guest-post" term="guest-post" label="Guest Post" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[How To Deploy Temporal to Azure Kubernetes Service (AKS)]]></title>
            <link href="https://mikhail.io/2020/11/how-to-deploy-temporal-to-azure-kubernetes-aks/"/>
            <id>https://mikhail.io/2020/11/how-to-deploy-temporal-to-azure-kubernetes-aks/</id>
            
            <published>2020-11-11T00:00:00+00:00</published>
            <updated>2020-11-11T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Get up and running with Temporal workflows in Azure and Kubernetes in several CLI commands</blockquote><p>In my article <a href="https://mikhail.io/2020/10/practical-approach-to-temporal-architecture/">A Practical Approach to Temporal Architecture</a>, I outlined the various <a href="https://temporal.io/">Temporal</a> components and how they interact. Today’s blog builds on this knowledge and demonstrates an example of deploying Temporal to Kubernetes and, more specifically, to Azure Kubernetes Service (AKS).</p>
<p>My example is self-contained: it provisions a full environment with all the required Azure resources, Temporal service, and application deployment artifacts. Here is a diagram of the cloud infrastructure:</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="temporal-aks.png"
            alt="Deployment architecture"
             />
        
    
    <figcaption>
        <h4>Deployment architecture</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>This sample deployment is implemented as a <a href="https://pulumi.com/">Pulumi</a> program in TypeScript. You can find the full code in <a href="https://github.com/mikhailshilkov/temporal-samples/tree/main/azure-aks">my GitHub</a>.</p>
<h2 id="application-code">Application Code</h2>
<p>The <code>workflow</code> folder contains all of the application code. The application is written with Go and consists of two source files:</p>
<ol>
<li><code>helloworld.go</code> - defines a workflow &amp; activity</li>
<li><code>main.go</code> - application entry point.</li>
</ol>
<p>The example deploys a “Hello World” Temporal application copied from <a href="https://github.com/temporalio/samples-go/blob/master/helloworld/helloworld.go">this Go sample</a>. Once you get it up and running, you can certainly customize the code with your own workflow and activities.</p>
<p>The <code>main.go</code> file does two things.</p>
<p><strong>First</strong>, it spins up a worker:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#1f2328">w</span> <span style="color:#0550ae">:=</span> <span style="color:#1f2328">worker</span><span style="color:#1f2328">.</span><span style="color:#6639ba">New</span><span style="color:#1f2328">(</span><span style="color:#1f2328">c</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;hello-world&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">worker</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Options</span><span style="color:#1f2328">{})</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">w</span><span style="color:#1f2328">.</span><span style="color:#6639ba">RegisterWorkflow</span><span style="color:#1f2328">(</span><span style="color:#1f2328">helloworld</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Workflow</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">w</span><span style="color:#1f2328">.</span><span style="color:#6639ba">RegisterActivity</span><span style="color:#1f2328">(</span><span style="color:#1f2328">helloworld</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Activity</span><span style="color:#1f2328">)</span>
</span></span></code></pre></div><p><strong>Second</strong>, it launches an HTTP server in the same process. The server exposes endpoints to start workflows. The <code>/async?name=&lt;yourname&gt;</code> endpoint starts a new workflow and immediately returns, while the <code>/sync?name=&lt;yourname&gt;</code> blocks and waits for the result of the execution and returns the response.</p>
<p>You can find the implementation in the <a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/workflow/main.go#L22"><code>start</code></a> function. Note that this simplistic starter is specifc to the &ldquo;Hello World&rdquo; workflow as it expects one argument and one result, both strings.</p>
<h2 id="deployment-structure">Deployment Structure</h2>
<p>My program combines three component resources: a MySQL Database, an AKS cluster, and a Temporal deployment. As a result, the main file deploy all of these resources to a single Azure Resource Group:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">resourceGroup</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">resources</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ResourceGroup</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;resourceGroup&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroupName</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">location</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;WestEurope&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">database</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">MySql</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;mysql&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">location</span>: <span style="color:#cf222e">resourceGroup.location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">administratorLogin</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;mikhail&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">administratorPassword</span>: <span style="color:#cf222e">mysqlPassword</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">cluster</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">AksCluster</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;aks&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">location</span>: <span style="color:#cf222e">resourceGroup.location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">kubernetesVersion</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;1.16.13&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">vmSize</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard_DS2_v2&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">vmCount</span>: <span style="color:#cf222e">3</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">temporal</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">Temporal</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;temporal&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">location</span>: <span style="color:#cf222e">resourceGroup.location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">version</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;1.1.1&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">storage</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">type</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;mysql&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">hostName</span>: <span style="color:#cf222e">database.hostName</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">login</span>: <span style="color:#cf222e">database.administratorLogin</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">password</span>: <span style="color:#cf222e">database.administratorPassword</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">cluster</span>: <span style="color:#cf222e">cluster</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">app</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">namespace</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;temporal&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">folder</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;./workflow&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">port</span>: <span style="color:#cf222e">8080</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">webEndpoint</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">temporal</span><span style="color:#1f2328">.</span><span style="color:#1f2328">webEndpoint</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">starterEndpoint</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">temporal</span><span style="color:#1f2328">.</span><span style="color:#1f2328">starterEndpoint</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><p>The rest of the article gives an overview of the building blocks of these three components.</p>
<h2 id="docker-image">Docker Image</h2>
<p>Since the application is deployed to Kubernetes, we need to produce a custom Docker image. The <a href="https://github.com/mikhailshilkov/temporal-samples/blob/main/azure-aks/workflow/Dockerfile"><code>Dockerfile</code></a> builds the Go application and exposes port <code>8080</code> to the outside world so we can access the starter HTTP endpoints.</p>
<p>Pulumi deploys this <code>Dockerfile</code> to Azure in three steps:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L42-L50">Deploy</a> an Azure Container Registry.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L52-L58">Retrieve</a> the registry’s admin credentials generated by Azure.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L61-L69">Publish</a> the application image to the registry.</li>
</ul>
<h2 id="mysql-database">MySQL Database</h2>
<p>There are several persistence options supported by Temporal. A straightforward option for Azure users is to deploy an instance of Azure Database for MySQL. It’s a fully managed database service where Azure is responsible for uptime and maintenance, and users pay a flat fee per hour.</p>
<p>My example <a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/mysql.ts#L24-L50">provisions</a> an instance of MySQL 5.7 at the Basic tier. The database size is limited to 5 GB.</p>
<p>A final tweak is to <a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/mysql.ts#L52-L58">add</a> a firewall rule for the IP address <code>0.0.0.0</code>, which enables network access to MySQL from any Azure service. Note that this option isn’t secure for production workloads: read more in <a href="https://docs.microsoft.com/en-us/azure/mysql/concepts-firewall-rules#connecting-from-azure">Connecting from Azure</a>.</p>
<h2 id="azure-kubernetes-cluster">Azure Kubernetes Cluster</h2>
<p>The example creates a new AKS cluster and deploys the Temporal service and applications components to that cluster.</p>
<p>The <code>AksCluster</code> component:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/cluster.ts#L23-L39">Sets up</a> an Azure Active Directory Application and a Service Principal.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/cluster.ts#L42-L45">Creates</a> an SSH key for the cluster’s admin user profile.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/cluster.ts#L48-L86">Provisions</a> a managed Kubernetes cluster base on VM Scale Sets node pool. Feel free to adjust the VM size, count, and the Kubernetes version.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/cluster.ts#L88-L96">Builds</a> the Kubeconfig YAML to connect to the cluster and deploy application components.</li>
</ul>
<h2 id="temporal-service-and-web-console">Temporal Service and Web Console</h2>
<p>Next, we deploy the Temporal Service and Temporal Web Console as two Kubernetes services.</p>
<p>We start with sound groundwork:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L71-L78">Declare</a> a custom Pulumi Kubernetes provider and point it to the Kubeconfig string that we retrieved from the managed cluster.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L71-L78">Grant</a> permission for the managed cluster’s service principal to access images from the Azure Container Registry.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L85-L89">Define</a> a new Kubernetes namespace that contains all Temporal deployments and services.</li>
</ul>
<p>Then, we can deploy the Temporal Service:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L91-L103">Stores</a> the MySQL password as a Kubernetes secret.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L136">Refers</a> to the <code>temporalio/auto-setup</code> Docker image provided by Temporal. The image automatically populates the database schema during the first run.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L143-L163">Sets up</a> environment variables to connect to MySQL.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L186-L213">Deploys</a> a <code>ClusterIP</code> service using port <code>7233</code>.</li>
</ul>
<p>The Web Console follows:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L246">Refers</a> to the <code>temporalio/web</code> Docker image provided by Temporal.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L249-L252">Connects</a> to the gRPC endpoint of the Temporal Service.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L275-L301">Deploys</a> a <code>ClusterIP</code> service using port <code>8088</code>.</li>
</ul>
<h2 id="temporal-worker">Temporal Worker</h2>
<p>The final component is a Temporal worker that runs application workflows and activities. In my setup, the worker is a Kubernetes deployment that pulls the custom Docker image from the container registry.</p>
<p>The application component:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L337">Refers</a> to the custom Docker image created above.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L337">Connects</a> to the gRPC endpoint of the Temporal Service.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/7ce8c36eb64628d899ce618339f3691486f37e81/azure-aks/temporal.ts#L337">Deploys</a> a <code>ClusterIP</code> service using port <code>8080</code>.</li>
</ul>
<h2 id="get-started">Get Started</h2>
<p>The Pulumi Command-Line Interface (CLI) runs the deployment. <a href="https://www.pulumi.com/docs/get-started/install/">Install Pulumi</a>, navigate to the folder where you have the example cloned, and run the following commands:</p>
<ol>
<li>Create a new stack (a Pulumi deployment environment):</li>
</ol>
<pre tabindex="0"><code>pulumi stack init dev
</code></pre><ol>
<li>Login to <a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli">Azure CLI</a>:</li>
</ol>
<pre tabindex="0"><code>az login
</code></pre><ol>
<li>Install NPM dependencies:</li>
</ol>
<pre tabindex="0"><code>npm install
</code></pre><ol>
<li>Run <code>pulumi up</code> and confirm when asked if you want to deploy. Azure resources are provisioned:</li>
</ol>
<pre tabindex="0"><code>$ pulumi up...
Performing changes:...

Outputs:
    starterEndpoint: &#34;http://21.55.177.186:8080/async?name=&#34;
    webEndpoint : &#34;http://52.136.6.198:8088&#34;

Resources:+ 27 created
Duration: 6m46s
</code></pre><ol>
<li>The output above prints the endpoints to interact with the application. Run the following command to start a “Hello World” workflow:</li>
</ol>
<pre tabindex="0"><code>curl $(pulumi stack output starterEndpoint)WorldStarted workflow ID=World, RunID=b4f6db00-bb2f-498b-b620-caad81c91a81%
</code></pre><p>Now, open the <code>webEndpoint</code> URL in your browser and find the workflow (it’s probably already in the Completed state).</p>
<h2 id="cost-security-and-further-steps">Cost, Security, and Further Steps</h2>
<p>The deployment above provisions real Azure resources, so be mindful of the related costs. Here is an estimated calculation for the “West US 2” region:</p>
<ul>
<li>Azure Database for MySQL Gen5 Basic with 1 vCore and 5 GB of storage = $25.32/month</li>
<li>Azure Kubernetes Cluster of 3 VMs type Standard_DS2_v2: 3 x $83.22/month = $249.66/month (but feel free to adjust to your needs)</li>
<li>Azure Container Registry Basic = $5.00/month</li>
</ul>
<p>The total cost for this example is approximately $280 per month.</p>
<p>Whenever you are done experimenting, run <code>pulumi destroy</code> to delete the resources. Note that all the data will be lost after destruction.</p>
<p>You can find the full code in <a href="https://github.com/mikhailshilkov/temporal-samples/tree/main/azure-aks">my GitHub</a>.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/temporal" term="temporal" label="Temporal" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/kubernetes" term="kubernetes" label="Kubernetes" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[How To Deploy Temporal to Azure Container Instances]]></title>
            <link href="https://mikhail.io/2020/10/how-to-deploy-temporal-to-azure-container-instances/"/>
            <id>https://mikhail.io/2020/10/how-to-deploy-temporal-to-azure-container-instances/</id>
            
            <published>2020-10-28T00:00:00+00:00</published>
            <updated>2020-10-28T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Get up and running with Temporal workflows in Azure in several CLI commands</blockquote><p>In <a href="https://mikhail.io/2020/10/practical-approach-to-temporal-architecture/">my previous article</a>, I outlined the various components of <a href="https://temporal.io">Temporal</a> and how they interact. Today&rsquo;s blog builds on this knowledge and demonstrates an example Temporal deployment.</p>
<p>It&rsquo;s a minimalistic deployment on Azure which combines a managed MySQL database with Azure Container Instances, suitable for simple experimentation and development. Here is a diagram of the cloud infrastructure:</p>
<p><img src="./azure.png" alt="Azure Diagram"></p>
<p>This sample deployment is implemented as a <a href="https://pulumi.com">Pulumi</a> program in TypeScript. You can find the full code in <a href="https://github.com/mikhailshilkov/temporal-samples/tree/main/azure-aci">my GitHub</a>.</p>
<h2 id="application-code">Application Code</h2>
<p>The <code>workflow</code> folder contains all of the application code. The application is written with Go and consists of two source files:</p>
<ol>
<li><code>helloworld.go</code> - defines a workflow and an activity</li>
<li><code>main.go</code> - application entry point.</li>
</ol>
<p>The example deploys a &ldquo;Hello World&rdquo; Temporal application copied from <a href="https://github.com/temporalio/samples-go/blob/master/helloworld/helloworld.go">this Go sample</a>. Once you get it up and running, you can certainly customize the code with your own workflow and activities.</p>
<p>The <code>main.go</code> file does two things. First, it spins up a worker:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#1f2328">w</span> <span style="color:#0550ae">:=</span> <span style="color:#1f2328">worker</span><span style="color:#1f2328">.</span><span style="color:#6639ba">New</span><span style="color:#1f2328">(</span><span style="color:#1f2328">c</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;hello-world&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">worker</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Options</span><span style="color:#1f2328">{})</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">w</span><span style="color:#1f2328">.</span><span style="color:#6639ba">RegisterWorkflow</span><span style="color:#1f2328">(</span><span style="color:#1f2328">helloworld</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Workflow</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">w</span><span style="color:#1f2328">.</span><span style="color:#6639ba">RegisterActivity</span><span style="color:#1f2328">(</span><span style="color:#1f2328">helloworld</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Activity</span><span style="color:#1f2328">)</span>
</span></span></code></pre></div><p>Second, it launches an HTTP server in the same process. The server exposes endpoints to start workflows. The <code>/async?name=&lt;yourname&gt;</code> endpoint starts a new workflow and immediately returns, while the <code>/sync?name=&lt;yourname&gt;</code> blocks and waits for the result of the execution and returns the response. You can find the implementation in the <a href="https://github.com/mikhailshilkov/temporal-samples/blob/33024f614d4a99a7700eacf2142c8ef2b7cea0fc/azure-aci/workflow/main.go#L22"><code>start</code></a> function.</p>
<h2 id="docker-image">Docker Image</h2>
<p>Since the application is deployed to Azure Container Instances, we need to produce a custom Docker image. <a href="https://github.com/mikhailshilkov/temporal-samples/blob/33024f614d4a99a7700eacf2142c8ef2b7cea0fc/azure-aci/workflow/Dockerfile">The <code>Dockerfile</code></a> builds the Go application and exposes port <code>8080</code> to the outside world so we can access the starter HTTP endpoints.</p>
<p>Pulumi deploys this <code>Dockerfile</code> to Azure in three steps:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/33024f614d4a99a7700eacf2142c8ef2b7cea0fc/azure-aci/temporal.ts#L99-L107">Deploy</a> an Azure Container Registry.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/33024f614d4a99a7700eacf2142c8ef2b7cea0fc/azure-aci/temporal.ts#L109-L115">Retrieve</a> the registry&rsquo;s admin credentials generated by Azure.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/33024f614d4a99a7700eacf2142c8ef2b7cea0fc/azure-aci/temporal.ts#L117-L125">Publish</a> the application image to the registry.</li>
</ul>
<h2 id="mysql-database">MySQL Database</h2>
<p>There are several persistence options supported by Temporal. A straightforward option in Azure is to deploy an instance of Azure Database for MySQL. It&rsquo;s a fully managed database service where Azure is responsible for uptime and maintenance, and users pay a flat fee per hour.</p>
<p>My example <a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/mysql.ts#L24-L50">provisions</a> an instance of MySQL 5.7 at the Basic tier. The database size is limited to 5 GB.</p>
<p>A final tweak is to <a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/mysql.ts#L52-L58">add</a> a firewall rule for the IP address <code>0.0.0.0</code>, which enables network access to MySQL from any Azure service. Note that this option isn&rsquo;t secure for production workloads: read more in <a href="https://docs.microsoft.com/en-us/azure/mysql/concepts-firewall-rules#connecting-from-azure">Connecting from Azure</a>.</p>
<h2 id="temporal-service-and-web-console">Temporal Service and Web Console</h2>
<p>Next, we deploy the Temporal Service and Temporal Web Console as two Azure Container Instances.</p>
<p>The Service container:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L56">Refers</a> to the <code>temporalio/server</code> Docker image provided by Temporal.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L34-L43">Sets up</a> environment variables to connect to MySQL.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L51-L52">Exposes</a> port <code>7233</code> to the outside world. Note that this is not secure for a production environment!</li>
</ul>
<p>The Web Console container:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L82">Refers</a> to the <code>temporalio/web</code> Docker image provided by Temporal.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L91">Connects</a> to the gRPC endpoint gathered from the Service container.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L77-L78">Exposes</a> port <code>8088</code> to the outside world. Note that this is not secure for a production environment!</li>
</ul>
<h2 id="temporal-worker">Temporal Worker</h2>
<p>The final component is a Temporal worker that runs application workflows and activities. In my setup, the worker is another Azure Container Instance that pulls the custom Docker image from the container registry. The worker container:</p>
<ul>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L143">Refers</a> to the custom Docker image created above.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L152">Connects</a> to the gRPC endpoint gathered from the Service container.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L136-L140">Configures</a> registry credentials to access the private Azure Container Registry.</li>
<li><a href="https://github.com/mikhailshilkov/temporal-samples/blob/f17738aff73ae88e1b5f503790e9247f40f88b38/azure-aci/temporal.ts#L77-L78">Exposes</a> the starter endpoints at the port <code>8080</code>.</li>
</ul>
<h2 id="get-started">Get Started</h2>
<p>The Pulumi Command-Line Interface (CLI) runs the deployment. <a href="https://www.pulumi.com/docs/get-started/install/">Install Pulumi</a>, navigate to the folder where you have the example cloned, and run the following commands:</p>
<ol>
<li>
<p>Create a new stack (a Pulumi deployment environment):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pulumi stack init dev
</span></span></code></pre></div></li>
<li>
<p>Login to <a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli">Azure CLI</a>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>az login
</span></span></code></pre></div></li>
<li>
<p>Install NPM dependencies:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install
</span></span></code></pre></div></li>
<li>
<p>Run <code>pulumi up</code> and confirm when asked if you want to deploy. Azure resources are provisioned:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ pulumi up
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>Performing changes:
</span></span><span style="display:flex;"><span>  Type                                                         Name                    Status
</span></span><span style="display:flex;"><span>  pulumi:pulumi:Stack                                          temporal-azure-aci-dev  created     
</span></span><span style="display:flex;"><span>  ├─ my:example:MySql                                          mysql                   created  
</span></span><span style="display:flex;"><span>  │  ├─ azure-nextgen:dbformysql/latest:Server                 mysql                   created
</span></span><span style="display:flex;"><span>  │  └─ azure-nextgen:dbformysql/latest:FirewallRule           mysql-allow-all         created
</span></span><span style="display:flex;"><span>  ├─ my:example:Temporal                                       temporal                created  
</span></span><span style="display:flex;"><span>  │  ├─ docker:image:Image                                     temporal-worker         created
</span></span><span style="display:flex;"><span>  │  ├─ azure-nextgen:containerregistry/latest:Registry        registry                created
</span></span><span style="display:flex;"><span>  │  ├─ azure-nextgen:containerinstance/latest:ContainerGroup  temporal-server         created
</span></span><span style="display:flex;"><span>  │  ├─ azure-nextgen:containerinstance/latest:ContainerGroup  temporal-web            created
</span></span><span style="display:flex;"><span>  │  └─ azure-nextgen:containerinstance/latest:ContainerGroup  temporal-worker         created
</span></span><span style="display:flex;"><span>  ├─ random:index:RandomString                                 resourcegroup-name      created  
</span></span><span style="display:flex;"><span>  ├─ random:index:RandomPassword                               mysql-password          created  
</span></span><span style="display:flex;"><span>  └─ azure-nextgen:resources/latest:ResourceGroup              rg                      created  
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Outputs:
</span></span><span style="display:flex;"><span>    serverEndpoint : <span style="color:#0a3069">&#34;21.55.179.245:7233&#34;</span>
</span></span><span style="display:flex;"><span>    starterEndpoint: <span style="color:#0a3069">&#34;http://21.55.177.186:8080/async?name=&#34;</span>
</span></span><span style="display:flex;"><span>    webEndpoint    : <span style="color:#0a3069">&#34;http://52.136.6.198:8088&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Resources:
</span></span><span style="display:flex;"><span>    + <span style="color:#0550ae">13</span> created
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Duration: 7m48s
</span></span></code></pre></div></li>
<li>
<p>The output above prints the endpoints to interact with the application. Run the following command to start a &ldquo;Hello World&rdquo; workflow:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl <span style="color:#cf222e">$(</span>pulumi stack output starterEndpoint<span style="color:#cf222e">)</span>World
</span></span><span style="display:flex;"><span>Started workflow <span style="color:#953800">ID</span><span style="color:#0550ae">=</span>World, <span style="color:#953800">RunID</span><span style="color:#0550ae">=</span>b4f6db00-bb2f-498b-b620-caad81c91a81%
</span></span></code></pre></div></li>
</ol>
<p>Now, open the <code>webEndpoint</code> URL in your browser and find the workflow (it&rsquo;s probably already in the Completed state).</p>
<h2 id="cost-security-and-further-steps">Cost, Security, and Further Steps</h2>
<p>The deployment above provisions real Azure resources, so be mindful of the related costs. Here is an estimated calculation for the &ldquo;West US 2&rdquo; region:</p>
<ul>
<li>Azure Database for MySQL Gen5 Basic with 1 vCore and 5 GB of storage = $25.32/month</li>
<li>Azure Container Instance with 1 vCPU and 1 GB of RAM: 3 x $32.36/month = $97.08/month</li>
<li>Azure Container Registry Basic = $5.00/month</li>
</ul>
<p>The total cost for this example is approximately $127.40 per month.</p>
<p>Whenever you are done experimenting, run <code>pulumi destroy</code> to delete the resources. Note that all the data will be lost after destruction.</p>
<p>As noted in the sections above, the security setup is minimal and is not suitable for any environment that processes real data. In addition to a secure networking setup, a production environment would need to handle scalability, resilience, backups, observability, and so on.</p>
<p>I plan to address those topics in future blog posts. Stay tuned!</p>
<p>You can find the full code in <a href="https://github.com/mikhailshilkov/temporal-samples/tree/main/azure-aci">my GitHub</a>.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/temporal" term="temporal" label="Temporal" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[A Practical Approach to Temporal Architecture]]></title>
            <link href="https://mikhail.io/2020/10/practical-approach-to-temporal-architecture/"/>
            <id>https://mikhail.io/2020/10/practical-approach-to-temporal-architecture/</id>
            
            <published>2020-10-22T00:00:00+00:00</published>
            <updated>2020-10-22T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>What it takes to get Temporal workflows up and running</blockquote><p><a href="https://temporal.io">Temporal</a> enables developers to build highly reliable applications without having to worry about all the edge cases. If you are new to Temporal, check out my article <a href="https://mikhail.io/2020/10/temporal-open-source-workflows-as-code/">Open Source Workflows as Code</a>. Now that you&rsquo;re excited, I&rsquo;ll cover how you can get up and running with Temporal.</p>
<p>Temporal consists of several components. In this post, I want to outline the primary building blocks and their interactions. By the end, you&rsquo;ll have a broad picture of Temporal and the considerations of deploying to development, staging, and production environments.</p>
<h2 id="workers">Workers</h2>
<p>Workers are compute nodes that run your Temporal application code. You compile your workflows and activities into a worker executable. Next you can run the worker executable in the background and it will listen for new tasks to process.</p>
<p>Your first development environment will probably only need one worker that runs both workflows and activities in a single process. The worker can be hosted anywhere you wish: as a local process on your laptop, as an AWS Fargate task, as a pod in Kubernetes, and so on.</p>
<figure >
    
        <img src="worker.png"
            alt="A Temporal worker executes workflows and activities"
             />
        
    
    <figcaption>
        <h4>A Temporal worker executes workflows and activities</h4>
    </figcaption>
    
</figure>
<p>Production deployments typically run numerous workflows with significant load, so you will likely need to run multiple workers in parallel. Ensuring that nodes are spread out over a compute cluster enables resiliency and scalability.</p>
<figure >
    
        <img src="workers.png"
            alt="Workload scaled out to several Temporal workers"
             />
        
    
    <figcaption>
        <h4>Workload scaled out to several Temporal workers</h4>
    </figcaption>
    
</figure>
<p>Workers operate independently, each crunching through its share of the workload. Workers are logically &ldquo;stateless&rdquo;: they don&rsquo;t keep track of the past and future. Workers don&rsquo;t talk to each other directly, but they coordinate via a central Temporal service—the brain of the system.</p>
<h2 id="temporal-service">Temporal Service</h2>
<p>Temporal Service is a component provided by Temporal. Its purpose is to keep track of workflows, activities, and tasks and coordinate workers&rsquo; execution.</p>
<p>Your early environments may have a single Service instance running in the background. A worker knows the Service&rsquo;s domain name (IP) and port and connects to the Service via gRPC.</p>
<figure >
    
        <img src="service.png"
            alt="Worker interacting with Temporal Service"
             />
        
    
    <figcaption>
        <h4>Worker interacting with Temporal Service</h4>
    </figcaption>
    
</figure>
<p>Temporal Service itself consists of multiple components: a front-end, matching, history, and others. However, it&rsquo;s okay to treat it as a black-box &ldquo;Service&rdquo; for now.</p>
<p>Once you start growing your workloads, you will need to scale the Service components out to multiple instances for high resilience and throughput. So, several workers are now talking to several service instances.</p>
<figure >
    
        <img src="services.png"
            alt="Temporal Cluster running multiple components"
             />
        
    
    <figcaption>
        <h4>Temporal Cluster running multiple components</h4>
    </figcaption>
    
</figure>
<p>Temporal Service can tolerate node failures because it stores its state in an external storage.</p>
<h2 id="data-store">Data Store</h2>
<p>All the workflow data—task queues, execution state, activity logs—are stored in a persistent Data Store. Persistence technology is pluggable with two options currently officially supported: Cassandra and MySQL. More alternatives, including PostgreSQL, are on the way.</p>
<p>A MySQL database completes the simplest Temporal deployment diagram.</p>
<figure >
    
        <img src="mysql.png"
            alt="MySQL as a Data Store for Temporal"
             />
        
    
    <figcaption>
        <h4>MySQL as a Data Store for Temporal</h4>
    </figcaption>
    
</figure>
<p>For heavily distributed applications that experience high load, it may be better to use Cassandra instead. This database could be a cluster managed by yourself or a managed cloud service (like Azure Cosmos DB):</p>
<figure >
    
        <img src="cassandra.png"
            alt="Cassandra as a Data Store for Temporal"
             />
        
    
    <figcaption>
        <h4>Cassandra as a Data Store for Temporal</h4>
    </figcaption>
    
</figure>
<p>Your code never interacts with the Data Store directly. Basically, it&rsquo;s a (rather important) implementation detail of the Temporal Service.</p>
<h2 id="temporal-web-console">Temporal Web Console</h2>
<p>Temporal provides a handy web console to view namespaces, workflows, and activity history. Technically, it&rsquo;s not a required component—but you probably want to have it under your belt for manual inspection and troubleshooting.</p>
<p>You&rsquo;ll likely have other client components, for instance, to start workflow executions.</p>
<h2 id="external-client">External Client</h2>
<p>You need a client to start your very first workflow execution. It can be the Temporal command-line interface <code>tctl</code> or any custom code that you wrote with a call to initiate a workflow using an SDK or raw gRPC.</p>
<p>Each client must connect to a Temporal Service in order to start or terminate workflows, wait for completion, list the results from the history, and so on. For instance, a web application could start a workflow execution every time it handles a particular HTTP endpoint.</p>
<h2 id="put-it-all-together">Put It All Together</h2>
<p>The following picture puts all the pieces covered above together.</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="components.png"
            alt="High-level Temporal architecture"
             />
        
    
    <figcaption>
        <h4>High-level Temporal architecture</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>As you can tell, there are quite a few components that go into making Temporal tick! Temporal is designed to handle millions of workflows reliably with high-performance guarantees while being open-source and portable across different infrastructure options. This dictates the usage of a multi-tier approach.</p>
<p>You can deploy everything to your development machine with the <a href="https://docs.temporal.io/docs/go-run-your-first-app/">getting started guide</a>. After that, you can start moving the components to your favorite cloud environment. Over the next weeks, I&rsquo;ll be working through automation scenarios and accompanying blog posts to help you manage typical deployment needs.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/temporal" term="temporal" label="Temporal" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Temporal: Open Source Workflows as Code]]></title>
            <link href="https://mikhail.io/2020/10/temporal-open-source-workflows-as-code/"/>
            <id>https://mikhail.io/2020/10/temporal-open-source-workflows-as-code/</id>
            
            <published>2020-10-15T00:00:00+00:00</published>
            <updated>2020-10-15T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Temporal reimagines state-dependent service-orchestrated application development</blockquote><p>Regular readers of my blog may recognize me as a big fan of <a href="https://docs.microsoft.com/azure/azure-functions/durable/durable-functions-overview">Azure Durable Functions</a>. Durable Functions are an extension of Azure Functions that lets you write <strong>stateful</strong> functions and <strong>workflows</strong>. The SDK does a lot behind the scenes allowing you to focus exclusively on business logic:</p>
<ul>
<li>State management</li>
<li>Automatic checkpointing</li>
<li>Handles restarts on your behalf</li>
</ul>
<p>However, many people have a hard time understanding what Durable Functions are and, especially, when they are useful. I tried to help spread the ideas: I wrote an extensive article <a href="/2018/12/making-sense-of-azure-durable-functions/">Making Sense of Azure Durable Functions</a>, published <a href="https://github.com/mikhailshilkov/DurableFunctions.FSharp">DurableFunctions.FSharp</a> library, presented a few conference talks.</p>
<h2 id="beyond-the-scope-of-durable-functions">Beyond The Scope of Durable Functions</h2>
<p>While Durable Functions are great and pretty simple to get started with, their reliance on Azure services implies limitations on applicability.</p>
<p>Durable Functions are designed to run inside the Azure cloud. While you don&rsquo;t have to run functions in a serverless compute model, the durability features rely heavily on <strong>Azure Storage</strong>. There was an initiative to bring Redis as an alternative backend, but the attempt has seemingly stagnated.</p>
<p>Ideally, a durable workflow library would not tie into any specific hosting model (Function Apps), programming model (Azure Functions SDK), or storage backend (Azure Storage).</p>
<p>Besides, only a limited set of programming languages is supported. <strong>C#</strong> developers are the primary audience, but Node.js and Python SDKs are also available.</p>
<p>Recently, I&rsquo;ve been hopping between different languages and cloud providers. Are there other solutions that could help address similar challenges of stateful data processing?</p>
<p>In turns out, yes! My friend and tech blogger <a href="https://twitter.com/taillogs">Ryland</a> introduced me to <a href="https://www.temporal.io/">Temporal</a>—an open-source product to build workflows in code and operate resilient applications using developer-friendly primitives.</p>
<p>Before discussing Temporal, let&rsquo;s define what a workflow is.</p>
<h2 id="you-are-already-running-workflows">You Are Already Running Workflows</h2>
<p>The word &ldquo;workflow&rdquo; has this unfortunate connotation coming from business process automation, BizTalk, and BPMN tools. This scope is too narrow and boring compared to what I&rsquo;m thinking of here.</p>
<p>For me, workflows can be anything that matches three criteria:</p>
<ul>
<li><strong>Multi-step</strong>. Several related actions need to run towards a common goal.</li>
<li><strong>Distributed</strong>. Actions run across multiple servers and services, bringing all the hard problems of distributed systems.</li>
<li><strong>Arbitrary long</strong>. While workflows may complete in less than a second, they may also span days or even years. They are not a part of a request-response flow.</li>
</ul>
<p>This definition is very open. Running in the cloud? You probably implement workflows. Doing microservices? I bet you have tons of workflows in there. Event-driven? Aggregating data? Running cron jobs? Anything to do with the money? Queues? Event streams? Automation? Customer relations? Workflows, workflows, workflows all the way.</p>
<p>However, when it comes to implementation, most of these workflows are defined implicitly. Processes are running in the system, but they aren&rsquo;t defined in a single place. Instead, multiple pieces are spread over distributed components that have to do precisely the right thing for the workflow to complete.</p>
<p>The challenges of reliability, consistency, correctness are therefore addressed in an ad-hoc best-effort, poorly structured manner. These responsibilities are everyone&rsquo;s and no one&rsquo;s job at the same time.</p>
<h2 id="temporal-workflow-as-code">Temporal: Workflow as Code</h2>
<p>Temporal is an open-source tool focused on first-class support of workflows in a sense described above. The workflows are functions in a general-purpose programming language. Temporal comes with developer SDKs and a backend service that takes care of state management and event handling.</p>
<p>Each workflow is a cohesive function defining the whole scenario out of provided building blocks. While some learning and onboarding are required, the &ldquo;Workflow as Code&rdquo; concept feels natural to developers.</p>
<p>Let&rsquo;s take a look at an example.</p>
<h2 id="example-subscription-management">Example: Subscription Management</h2>
<p>We all buy subscriptions to online services these days, and many of us had to <strong>implement</strong> subscription management as part of the business applications we develop. An onboarding flow for a SaaS product (Spotify, Netflix, Dropbox, etc.) could look like this:</p>
<pre tabindex="0"><code>function subscribe(customerIdentifier) {
    onboardToFreeTrial(customerIdentifier);

    events.onCancellation(
        processSubscriptionCancellation(customerIdentifier)
        stopWorkflow;
    );

    wait(60 * days);
    upgradeFromTrialToPaid(customerIdentifier);
    forever {
        wait(30 * days);
        chargeMonthlyFee(customerIdentifier);
    }
}
</code></pre><p>I wrote this snippet in pseudocode, and it reflects how a developer might <strong>think</strong> about the workflow. The code relies on several building blocks:</p>
<ul>
<li>Domain-specific actions: <code>onboardToFreeTrial</code>, <code>chargeMonthlyFee</code>, and others;</li>
<li>Time scheduling with <code>wait</code> and <code>forever</code>;</li>
<li>External events to handle user&rsquo;s action with <code>onCancellation</code> and <code>stopWorkflow</code> to stop any further processing.</li>
</ul>
<p>The snippet is concise and easy to read. However, it&rsquo;s seemingly impossible to convert it to real code as-is. Once invoked, this function may need to run for months or years. We can&rsquo;t run a function on the same server for years: it would constantly consume resources and crash on any reboot or hardware failure.</p>
<h3 id="ad-hoc-implementation">Ad-hoc implementation</h3>
<p>Instead, state of the art is to design a distributed asynchronous event-driven bespoke solution. There are multiple options, but here is one of them:</p>
<ul>
<li>Store the status and renewal schedule of each subscription in a <strong>database</strong>.</li>
<li>Have a <strong>cron job</strong> that runs periodically, queries the database for subscriptions due, and sends commands to charge a fee.</li>
<li>The commands are processed by background <strong>workers</strong> triggered off a persistent queue.</li>
<li>A separate <strong>queue</strong> handles cancellation requests and updates subscriptions in the database.</li>
<li>A frontend service provides external <strong>APIs</strong> to interact with the components above.</li>
</ul>
<p><img src="./adhoc-workflow.png" alt="Picture of the ad-hoc solution"></p>
<p>This approach may work, but it&rsquo;s a radical departure from the original snippet in terms of complexity. We have to manage a few durable stores, and several components have to play nicely together. Failure scenarios are numerous, and, anecdotally, not many applications get this 100% right.</p>
<p>More importantly, all the machinery consumes expensive developer&rsquo;s time and yields no business value beyond what would be delivered by the single function perpetually running on a hypothetical error-free and limitless server.</p>
<h3 id="temporal-workflow">Temporal workflow</h3>
<p>Temporal provides a solution where your actual production code looks similar to the original pseudocode. Instead of synchronously running it forever, Temporal SDK would pause the execution when needed, create a checkpoint to store the status, and resume the execution automatically when required.</p>
<p>Temporal currently has two language SDKs: <strong>Go</strong> and <strong>Java</strong>, while more languages are on the way. Meanwhile, here is our workflow in Go:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#cf222e">func</span> <span style="color:#6639ba">SubscriptionWorkflow</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span> <span style="color:#1f2328">workflow</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">customerId</span> <span style="color:#cf222e">string</span><span style="color:#1f2328">)</span> <span style="color:#cf222e">error</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">var</span> <span style="color:#1f2328">err</span> <span style="color:#cf222e">error</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">if</span> <span style="color:#1f2328">err</span> <span style="color:#1f2328">=</span> <span style="color:#1f2328">workflow</span><span style="color:#1f2328">.</span><span style="color:#6639ba">ExecuteActivity</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">OnboardToFreeTrial</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">customerId</span><span style="color:#1f2328">).</span><span style="color:#6639ba">Get</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">nil</span><span style="color:#1f2328">);</span> <span style="color:#1f2328">err</span> <span style="color:#0550ae">!=</span> <span style="color:#cf222e">nil</span> <span style="color:#1f2328">{</span> <span style="color:#cf222e">return</span> <span style="color:#1f2328">err</span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">defer</span> <span style="color:#cf222e">func</span><span style="color:#1f2328">()</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">if</span> <span style="color:#1f2328">err</span> <span style="color:#0550ae">!=</span> <span style="color:#cf222e">nil</span> <span style="color:#0550ae">&amp;&amp;</span> <span style="color:#1f2328">temporal</span><span style="color:#1f2328">.</span><span style="color:#6639ba">IsCanceledError</span><span style="color:#1f2328">(</span><span style="color:#1f2328">err</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>      <span style="color:#1f2328">workflow</span><span style="color:#1f2328">.</span><span style="color:#6639ba">ExecuteActivity</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">ProcessSubscriptionCancellation</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">customerId</span><span style="color:#1f2328">).</span><span style="color:#6639ba">Get</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">nil</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">}()</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">if</span> <span style="color:#1f2328">err</span> <span style="color:#1f2328">=</span> <span style="color:#1f2328">workflow</span><span style="color:#1f2328">.</span><span style="color:#6639ba">Sleep</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span><span style="color:#1f2328">,</span> <span style="color:#0550ae">60</span><span style="color:#0550ae">*</span><span style="color:#0550ae">24</span><span style="color:#0550ae">*</span><span style="color:#1f2328">time</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Hour</span><span style="color:#1f2328">);</span> <span style="color:#1f2328">err</span> <span style="color:#0550ae">!=</span> <span style="color:#cf222e">nil</span> <span style="color:#1f2328">{</span> <span style="color:#cf222e">return</span> <span style="color:#1f2328">err</span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">if</span> <span style="color:#1f2328">err</span> <span style="color:#1f2328">=</span> <span style="color:#1f2328">workflow</span><span style="color:#1f2328">.</span><span style="color:#6639ba">ExecuteActivity</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">UpgradeFromTrialToPaid</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">customerId</span><span style="color:#1f2328">).</span><span style="color:#6639ba">Get</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">nil</span><span style="color:#1f2328">);</span> <span style="color:#1f2328">err</span> <span style="color:#0550ae">!=</span> <span style="color:#cf222e">nil</span> <span style="color:#1f2328">{</span> <span style="color:#cf222e">return</span> <span style="color:#1f2328">err</span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">for</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">if</span> <span style="color:#1f2328">err</span> <span style="color:#1f2328">=</span> <span style="color:#1f2328">workflow</span><span style="color:#1f2328">.</span><span style="color:#6639ba">Sleep</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span><span style="color:#1f2328">,</span> <span style="color:#0550ae">30</span><span style="color:#0550ae">*</span><span style="color:#0550ae">24</span><span style="color:#0550ae">*</span><span style="color:#1f2328">time</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Hour</span><span style="color:#1f2328">);</span> <span style="color:#1f2328">err</span> <span style="color:#0550ae">!=</span> <span style="color:#cf222e">nil</span> <span style="color:#1f2328">{</span> <span style="color:#cf222e">return</span> <span style="color:#1f2328">err</span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">if</span> <span style="color:#1f2328">err</span> <span style="color:#1f2328">=</span> <span style="color:#1f2328">workflow</span><span style="color:#1f2328">.</span><span style="color:#6639ba">ExecuteActivity</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">ChargeMonthlyFee</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">customerId</span><span style="color:#1f2328">).</span><span style="color:#6639ba">Get</span><span style="color:#1f2328">(</span><span style="color:#1f2328">ctx</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">nil</span><span style="color:#1f2328">);</span> <span style="color:#1f2328">err</span> <span style="color:#0550ae">!=</span> <span style="color:#cf222e">nil</span> <span style="color:#1f2328">{</span> <span style="color:#cf222e">return</span> <span style="color:#1f2328">err</span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>And here is the Java version:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#cf222e">public</span><span style="color:#fff"> </span><span style="color:#cf222e">class</span> <span style="color:#1f2328">SubscriptionWorkflowImpl</span><span style="color:#fff"> </span><span style="color:#cf222e">implements</span><span style="color:#fff"> </span>SubscriptionWorkflow<span style="color:#fff"> </span><span style="color:#1f2328">{</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">  </span><span style="color:#cf222e">public</span><span style="color:#fff"> </span><span style="color:#cf222e">void</span><span style="color:#fff"> </span><span style="color:#6639ba">execute</span><span style="color:#1f2328">(</span>String<span style="color:#fff"> </span>customerIdentifier<span style="color:#1f2328">)</span><span style="color:#fff"> </span><span style="color:#1f2328">{</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span>activities<span style="color:#1f2328">.</span><span style="color:#1f2328">onboardToFreeTrial</span><span style="color:#1f2328">(</span>customerIdentifier<span style="color:#1f2328">);</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#cf222e">try</span><span style="color:#fff"> </span><span style="color:#1f2328">{</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">      </span>Workflow<span style="color:#1f2328">.</span><span style="color:#1f2328">sleep</span><span style="color:#1f2328">(</span>Duration<span style="color:#1f2328">.</span><span style="color:#1f2328">ofDays</span><span style="color:#1f2328">(</span>60<span style="color:#1f2328">));</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">      </span>activities<span style="color:#1f2328">.</span><span style="color:#1f2328">upgradeFromTrialToPaid</span><span style="color:#1f2328">(</span>customerIdentifier<span style="color:#1f2328">);</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">      </span><span style="color:#cf222e">while</span><span style="color:#fff"> </span><span style="color:#1f2328">(</span><span style="color:#cf222e">true</span><span style="color:#1f2328">)</span><span style="color:#fff"> </span><span style="color:#1f2328">{</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">        </span>Workflow<span style="color:#1f2328">.</span><span style="color:#1f2328">sleep</span><span style="color:#1f2328">(</span>Duration<span style="color:#1f2328">.</span><span style="color:#1f2328">ofDays</span><span style="color:#1f2328">(</span>30<span style="color:#1f2328">));</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">        </span>activities<span style="color:#1f2328">.</span><span style="color:#1f2328">chargeMonthlyFee</span><span style="color:#1f2328">(</span>customerIdentifier<span style="color:#1f2328">);</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">      </span><span style="color:#1f2328">}</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#1f2328">}</span><span style="color:#fff"> </span><span style="color:#cf222e">catch</span><span style="color:#fff"> </span><span style="color:#1f2328">(</span>CancellationException<span style="color:#fff"> </span>e<span style="color:#1f2328">)</span><span style="color:#fff"> </span><span style="color:#1f2328">{</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">      </span>activities<span style="color:#1f2328">.</span><span style="color:#1f2328">processSubscriptionCancellation</span><span style="color:#1f2328">(</span>customerIdentifier<span style="color:#1f2328">);</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#1f2328">}</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">  </span><span style="color:#1f2328">}</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span><span style="color:#1f2328">}</span><span style="color:#fff">
</span></span></span></code></pre></div><p>It&rsquo;s not the same code as the prototype sketch, but the structure is strikingly similar. Both languages have their ways to handle errors producing some nuance and visual noise, but those ways are native and intimately familiar to developers in respective ecosystems.</p>
<p>The benefits of the Temporal&rsquo;s approach are quite clear:</p>
<ul>
<li>The flow is explicitly defined in a single place and can be formally reasoned through.</li>
<li>There is no bespoke infrastructure to manage beyond a worker running the code and the backend service (more on those below).</li>
<li>Temporal takes care of state management, queueing, resilience, deduplication, and other safety properties.</li>
</ul>
<p>Temporal workflows use the same pause-resume-replay approach as Azure Durable Functions. For example, a call to <code>Workflow.sleep</code> doesn&rsquo;t pause the current thread for 60 days. Instead, the actual Java method call completes, and Temporal schedules another execution in 60 days. The new execution has the history of previous runs, so it replays to the point where the last execution stopped and takes the next step.</p>
<h2 id="how-temporal-works">How Temporal Works</h2>
<p>There&rsquo;s no voodoo here, so a backend service and a data store are required to support the almost-magical workflow execution. Any Temporal environment includes <strong>workers</strong>, a <strong>backend service</strong>, and a <strong>data store</strong>.</p>
<p><img src="components.png" alt="Picture of workers, service, database"></p>
<p>Workers execute the end user&rsquo;s code as the one shown above. The user is responsible for running enough workers, which can be any long-lived compute nodes: VMs, containers, Kubernetes pods, etc.</p>
<p>Each worker connects to the service via a gRPC protocol. The service provides scheduling, queueing, and state manipulation primitives to distribute the workload between workers and ensure liveness and safety guarantees of the system.</p>
<p>The service saves its data in persistent external storage, which currently can be <strong>Cassandra</strong> or <strong>MySQL</strong>.</p>
<p>The service is designed for multi-tenant usage, so your organization only needs a single instance up and running, and multiple applications can benefit. I suspect there will be a managed SaaS option somewhere in the future.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Microservices, serverless functions, cloud-native applications—distributed event-driven business applications are hot, and we will deal with them for years. I worry that the application development industry underestimates the complexity of such systems. Ad-hoc solutions to common problems may rapidly increase the technical debt and slow down the ability to innovate.</p>
<p>I&rsquo;m excited to see tools like Temporal enter the space of open-source workflows, or rather the space of asynchronous data processing. My firm belief is that every developer can benefit from a higher-level framework that helps them focus on business logic. And the business logic is still written in code with languages that they know and love.</p>
<p>In future installments, I plan to explore Temporal in a deeper way, including different usage scenarios, getting up and running in the cloud, SDK features, query APIs, and more. Stay tuned!</p>
<p>If you want to give Temporal a try, head to the <a href="https://docs.temporal.io/docs/overview/">docs site</a>, explore the code at <a href="https://github.com/temporalio/temporal">GitHub</a> or ask questions in <a href="https://community.temporal.io/">Discourse</a> and <a href="https://join.slack.com/t/temporalio/shared_invite/zt-c1e99p8g-beF7~ZZW2HP6gGStXD8Nuw">Slack</a>.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/temporal" term="temporal" label="Temporal" />
                             
                                <category scheme="https://mikhail.io/tags/workflows" term="workflows" label="Workflows" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Announcing Next Generation Pulumi Azure Provider]]></title>
            <link href="https://mikhail.io/2020/09/announcing-nextgen-azure-provider/"/>
            <id>https://mikhail.io/2020/09/announcing-nextgen-azure-provider/</id>
            
            <published>2020-09-21T00:00:00+00:00</published>
            <updated>2020-09-21T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Next Generation Pulumi Azure Provider with 100% API Coverage and Same-Day Feature Support is now available in beta</blockquote><p>I am excited to announce the beta release of a next generation Microsoft Azure provider for Pulumi. Azure has been a rapidly growing cloud platform among Pulumi users over the last year, and with the next generation Azure provider, we are doubling down on providing the best support possible for the Azure platform in Pulumi. We designed the new provider to expose the entire API surface of Azure to developers and operators, now and forever.</p>
<p>The new Azure provider for Pulumi (<code>azure-nextgen</code>) works directly with the Azure Resource Manager (ARM) platform instead of depending on a handwritten layer as with the previous provider. This approach ensures higher quality and higher fidelity with the Azure platform.</p>
<h2 id="full-api-coverage">Full API Coverage</h2>
<p>The next generation Pulumi Azure provider covers 100% of the resources available in Azure Resource Manager. The new provider supports 890 resource types at launch, nearly double the number supported by the previous Pulumi Azure Provider. Every property of each resource is always represented in the SDKs.</p>
<p><img src="resources-properties.png" alt="Resource and Properties comparison"></p>
<p>The provider also contains functions to retrieve keys, secrets, and connection strings from all resources that expose them.</p>
<p>The new SDKs include full coverage for Azure services, including Azure Static Web Apps, Azure Synapse Analytics, Azure Logic Apps, Azure Service Fabric, Azure Blockchain Service, Azure API Management, and dozens of other services.</p>
<p>If you can deploy a resource with ARM Templates, you can deploy it with the next Generation Pulumi Azure provider!</p>
<h2 id="always-up-to-date">Always Up-to-Date</h2>
<p>Unlike the original Azure provider, which requires manual work to keep updated, the new provider is designed to be always up-to-date with additions and changes to Azure APIs.</p>
<p>We generate Pulumi SDKs for <code>azure-nextgen</code> automatically from Azure API specifications published by Microsoft. An automated pipeline releases updated resources within hours after any current API specifications are merged. We publish daily updates via automated builds and cut minor SDK versions every two weeks.</p>
<p>By generating the SDKs directly from the Azure Resource Manager resource model specifications maintained by Azure service teams, the Azure NextGen provider is robust and reliable, with fewer moving parts involved and fewer sources of potential bugs and incompatibilities.</p>
<p>Excited about a new service announced by Microsoft? Chances are it’s already in the latest Azure NextGen package!</p>
<h2 id="api-versions">API Versions</h2>
<p>The Azure Resource Manager API is structured around Resource Providers—high-level groups like &ldquo;storage&rdquo;, &ldquo;compute&rdquo;, or &ldquo;web&rdquo;. We map Resource Providers to top-level modules or namespaces in Pulumi SDKs.</p>
<p>Each resource provider defines one or more API versions, for example, &ldquo;2015-05-01&rdquo;, &ldquo;2020-09-01&rdquo;, or &ldquo;2020-08-01-preview&rdquo;. Every version of every ARM API is available in Pulumi SDKs, and each version has its own module or namespace.</p>
<p><img src="vscode-versions.png" alt="API Versions in VS Code"></p>
<p>In addition, a <code>latest</code> version  in the Pulumi SDK maps to the latest stable API version as determined by the Azure SDK teams. Using a specific version ensures full compatibility, while using the <code>latest</code> version is a quick way to start, but does not guarantee compatibility between minor versions of the provider.</p>
<p>Now, you always have access to the latest and greatest, but at the same time, we don&rsquo;t force you to upgrade your production resources unless you are ready.</p>
<h2 id="every-language">Every Language</h2>
<p>The next generation Azure provider is available in preview today for all Pulumi languages. The new <code>azure-nextgen</code> SDKs are open source on GitHub and available in NPM, NuGet, PyPI, and Go Modules.</p>
<p>Here is an example in TypeScript:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">resources</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/azure-nextgen/resources/latest&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">storage</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/azure-nextgen/storage/latest&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">resourceGroup</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">resources</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ResourceGroup</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;resourceGroup&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;my-rg&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">location</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;WestUS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">storageAccount</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">StorageAccount</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;sa&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">accountName</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;mystorageaccount&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">location</span>: <span style="color:#cf222e">resourceGroup.location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">sku</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard_LRS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">tier</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">kind</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;StorageV2&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>API documentation is available at <a href="https://pulumi.com/docs/reference/pkg/azure-nextgen">Azure NextGen API Reference</a> and includes more than 1,000 resource examples.</p>
<h2 id="integrated-with-azure-ecosystem">Integrated with Azure Ecosystem</h2>
<p>Relying on the shape of the Azure API, we can integrate the NextGen provider into the broader Azure ecosystem. In the coming weeks, we will release several capabilities to simplify the adoption of the new provider:</p>
<ul>
<li>Command-line and web-based tools to convert Azure Resource Manager Templates to Pulumi programs in the language of your choice</li>
<li>A flow to import an existing Azure Resource Group and all its resources to your Pulumi project</li>
<li>A multi-language component resource to embed ARM Templates in Pulumi programs, including per-resource previews</li>
<li>Integrations with ARM Template generation tools like Project Bicep and Farmer</li>
</ul>
<p>Bring your existing resources and assets and start managing them with the NextGen Azure provider!</p>
<h2 id="provider-coexistence">Provider Coexistence</h2>
<p>We will continue to invest in the existing <code>azure</code> provider, and Pulumi users can use either provider or both, side-by-side, in their applications.</p>
<p>A single Pulumi project can use both providers. Each provider must be configured independently, but they accept mostly the same configuration options. The outputs of a resource from one provider can flow to inputs of a resource from the other provider. In both cases, they are the values available from Azure itself, such as the name or id of an Azure resource.</p>
<p>Pulumi has a separate <code>azuread</code> provider, which will continue to manage Azure Active Directory resources.</p>
<p>If you have existing projects using the current Pulumi Azure provider, you can continue to use that provider, and it will be updated indefinitely. You can add new cloud resources using the existing provider or start creating new infrastructure with the new provider.</p>
<p>New Pulumi Azure projects will default to use the <code>azure-nextgen</code> provider once the provider reaches general availability.</p>
<h2 id="getting-started">Getting Started</h2>
<p>New Pulumi templates are available for the new <code>azure-nextgen</code> provider:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ pulumi new azure-nextgen-typescript
</span></span></code></pre></div><p>Several larger examples are available in the Pulumi Examples repo:</p>
<ul>
<li>Web Applications with Azure App Service and Docker: <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-ts-appservice-docker">TypeScript</a>, <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-cs-appservice-docker">C#</a>, <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-py-appservice-docker">Python</a>, <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-go-appservice-docker">Go</a></li>
<li>Azure AKS cluster: <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-ts-aks">TypeScript</a>, <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-cs-aks">C#</a>, <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-py-aks">Python</a>, <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-go-aks">Go</a></li>
<li>Web Application with Azure Container Instances: <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-ts-aci">TypeScript</a>, <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-cs-aci">C#</a>, <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-py-aci">Python</a>, <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-go-aci">Go</a></li>
<li>Web Server Using Azure Virtual Machine: <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-ts-webserver">TypeScript</a>, <a href="https://github.com/pulumi/examples/tree/master/azure-nextgen-py-webserver">Python</a></li>
</ul>
<p>You can browse <a href="https://www.pulumi.com/docs/reference/pkg/azure-nextgen/">API reference docs</a> with inline examples or explore the <a href="https://github.com/pulumi/pulumi-azure-nextgen">Pulumi Azure NextGen SDKs</a> repository.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[How to Drain a List of .NET Tasks to Completion]]></title>
            <link href="https://mikhail.io/2020/09/how-to-drain-dotnet-tasks-to-completion/"/>
            <id>https://mikhail.io/2020/09/how-to-drain-dotnet-tasks-to-completion/</id>
            
            <published>2020-09-03T00:00:00+00:00</published>
            <updated>2020-09-03T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Custom await logic for a dynamic list of .NET tasks, fast and on-time</blockquote><p>The Pulumi .NET SDK operates with an unusual asynchronicity model. Resource declarations are synchronous calls and complete instantaneously.</p>
<p>Yet, they kick off the actual operations of resource creation as background tasks. An end-user does not see these tasks and does not await them.</p>
<p>Nonetheless, Pulumi must not stop the program execution until all the tasks are completed. The SDK should collect all open tasks and reliably await them.</p>
<p>The following model illustrates the goal:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">static</span> <span style="color:#cf222e">async</span> Task Main<span style="color:#1f2328">()</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">for</span> <span style="color:#1f2328">(</span><span style="color:#cf222e">int</span> i <span style="color:#1f2328">=</span> <span style="color:#0550ae">0</span><span style="color:#1f2328">;</span> i <span style="color:#1f2328">&lt;</span> N<span style="color:#1f2328">;</span> i<span style="color:#1f2328">++)</span>
</span></span><span style="display:flex;"><span>        DoWork<span style="color:#1f2328">(</span><span style="color:#0a3069">$&#34;Job {i}&#34;</span><span style="color:#1f2328">);</span> <span style="color:#57606a">// creates a Task and returns immediately</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">await</span> WaitForAllOpenTasksToComplete<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">static</span> <span style="color:#cf222e">void</span> DoWork<span style="color:#1f2328">(</span><span style="color:#cf222e">string</span> message<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    RegisterTask<span style="color:#1f2328">(</span>message<span style="color:#1f2328">,</span> RunAsync<span style="color:#1f2328">());</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">async</span> Task RunAsync<span style="color:#1f2328">()</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">var</span> delay <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> Random<span style="color:#1f2328">().</span>Next<span style="color:#1f2328">(</span><span style="color:#0550ae">1000</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">await</span> Task<span style="color:#1f2328">.</span>Delay<span style="color:#1f2328">(</span>delay<span style="color:#1f2328">).</span>ConfigureAwait<span style="color:#1f2328">(</span><span style="color:#cf222e">false</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        Log<span style="color:#1f2328">(</span><span style="color:#0a3069">$&#34;{message} finishing&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>The <code>DoWork</code> method creates a <code>Task</code> that pauses for a random delay below one second. It passes the task to a <code>RegisterTask</code> method (to be defined) and returns without awaiting.</p>
<p>In our real use case, tasks may also produce new tasks, which can produce even more tasks, and so on. I emulate this by extending the <code>DoWork</code> method with an extra parameter asking to schedule more work:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">static</span> <span style="color:#cf222e">void</span> DoWork<span style="color:#1f2328">(</span><span style="color:#cf222e">string</span> message<span style="color:#1f2328">,</span> <span style="color:#cf222e">bool</span> moreWork <span style="color:#1f2328">=</span> <span style="color:#cf222e">true</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    RegisterTask<span style="color:#1f2328">(</span>message<span style="color:#1f2328">,</span> RunAsync<span style="color:#1f2328">());</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">async</span> Task RunAsync<span style="color:#1f2328">()</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">var</span> delay <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> Random<span style="color:#1f2328">().</span>Next<span style="color:#1f2328">(</span><span style="color:#0550ae">1000</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">await</span> Task<span style="color:#1f2328">.</span>Delay<span style="color:#1f2328">(</span>delay<span style="color:#1f2328">).</span>ConfigureAwait<span style="color:#1f2328">(</span><span style="color:#cf222e">false</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        Log<span style="color:#1f2328">(</span><span style="color:#0a3069">$&#34;{message} finishing&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">if</span> <span style="color:#1f2328">(</span>moreWork<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#cf222e">for</span> <span style="color:#1f2328">(</span><span style="color:#cf222e">int</span> i <span style="color:#1f2328">=</span> <span style="color:#0550ae">0</span><span style="color:#1f2328">;</span> i <span style="color:#1f2328">&lt;</span> N<span style="color:#1f2328">;</span> i<span style="color:#1f2328">++)</span>
</span></span><span style="display:flex;"><span>                DoWork<span style="color:#1f2328">(</span><span style="color:#0a3069">$&#34;{message}.{i}&#34;</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">false</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>The <code>Main</code> method creates initial work items and needs to await the completion of all of the tasks, immediate or descendant ones, with <code>WaitForAllOpenTasksToComplete</code>.</p>
<p>Let&rsquo;s look at several options on how to implement such processing.</p>
<h2 id="registering-tasks">Registering Tasks</h2>
<p>First thing, I need to store the in-flight tasks somewhere, so I&rsquo;ll allocate a static collection for that. I want to keep a description with each task, so my collection is a dictionary:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">static</span> <span style="color:#cf222e">readonly</span> Dictionary<span style="color:#1f2328">&lt;</span>Task<span style="color:#1f2328">,</span> <span style="color:#cf222e">string</span><span style="color:#1f2328">&gt;</span> _inFlightTasks <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> Dictionary<span style="color:#1f2328">&lt;</span>Task<span style="color:#1f2328">,</span> <span style="color:#cf222e">string</span><span style="color:#1f2328">&gt;();</span>
</span></span></code></pre></div><p>Now I can implement a method to register a new task:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">static</span> <span style="color:#cf222e">void</span> RegisterTask<span style="color:#1f2328">(</span><span style="color:#cf222e">string</span> description<span style="color:#1f2328">,</span> Task task<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">lock</span> <span style="color:#1f2328">(</span>_inFlightTasks<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#57606a">// Duplicates may happen if we try registering things like Task.CompletedTask.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#57606a">// We&#39;ll ignore duplicates for now.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">if</span> <span style="color:#1f2328">(!</span>_inFlightTasks<span style="color:#1f2328">.</span>ContainsKey<span style="color:#1f2328">(</span>task<span style="color:#1f2328">))</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            _inFlightTasks<span style="color:#1f2328">.</span>Add<span style="color:#1f2328">(</span>task<span style="color:#1f2328">,</span> description<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>Now, how do we implement the awaiting of these tasks?</p>
<h2 id="awaiting-the-tasks">Awaiting the Tasks</h2>
<p>We can&rsquo;t wait for the tasks just once, because new tasks may be coming over time. Therefore, the <code>WaitForAllOpenTasksToComplete</code> will have a loop to wait until all the in-flight tasks are drained. Here is a draft without the actual awaiting yet:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">private</span> <span style="color:#cf222e">static</span> <span style="color:#cf222e">async</span> Task WaitForAllOpenTasksToComplete_Draft<span style="color:#1f2328">()</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">// Keep looping as long as there are outstanding tasks that are still running.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">while</span> <span style="color:#1f2328">(</span><span style="color:#cf222e">true</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">var</span> tasks <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> List<span style="color:#1f2328">&lt;</span>Task<span style="color:#1f2328">&gt;();</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">lock</span> <span style="color:#1f2328">(</span>_inFlightTasks<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#cf222e">if</span> <span style="color:#1f2328">(</span>_inFlightTasks<span style="color:#1f2328">.</span>Count <span style="color:#1f2328">==</span> <span style="color:#0550ae">0</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                <span style="color:#57606a">// No more tasks in flight: exit the loop.</span>
</span></span><span style="display:flex;"><span>                <span style="color:#cf222e">return</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#57606a">// Grab all the tasks we currently have running.</span>
</span></span><span style="display:flex;"><span>            tasks<span style="color:#1f2328">.</span>AddRange<span style="color:#1f2328">(</span>_inFlightTasks<span style="color:#1f2328">.</span>Keys<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#57606a">// TODO: await tasks, then proceed to the next iteration</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>How do we await these tasks?</p>
<h2 id="use-whenall">Use WhenAll</h2>
<p>The obvious option is to use <code>WhenAll</code> and then remove all the tasks from the in-flight collections:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#57606a">// ... we are inside the loop</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">await</span> Task<span style="color:#1f2328">.</span>WhenAll<span style="color:#1f2328">(</span>tasks<span style="color:#1f2328">).</span>ConfigureAwait<span style="color:#1f2328">(</span><span style="color:#cf222e">false</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span><span style="color:#cf222e">lock</span> <span style="color:#1f2328">(</span>_inFlightTasks<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">foreach</span> <span style="color:#1f2328">(</span><span style="color:#cf222e">var</span> task <span style="color:#cf222e">in</span> tasks<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        Log<span style="color:#1f2328">(</span><span style="color:#0a3069">$&#34;{_inFlightTasks[task]} handled&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        _inFlightTasks<span style="color:#1f2328">.</span>Remove<span style="color:#1f2328">(</span>task<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// ... the loop continues</span>
</span></span></code></pre></div><p>Let&rsquo;s run the program for <code>N</code> = 2 (two parent tasks, each creating two child tasks) and log the messages and timings:</p>
<pre tabindex="0"><code>0.048: Job 0 finishing
0.464: Job 0.0 finishing
0.539: Job 0.1 finishing
0.660: Job 1 finishing
0.661: Job 0 handled
0.661: Job 1 handled
0.910: Job 1.1 finishing
1.372: Job 1.0 finishing
1.372: Job 0.0 handled
1.372: Job 0.1 handled
1.372: Job 1.0 handled
1.372: Job 1.1 handled
1.373: Done!
</code></pre><p>The awaiting loop works, and the program quits correctly.</p>
<p>There is a downside, though. After a task completes, we may not respond to its completion until all the other tasks of the same batch complete.</p>
<p>This is not desirable for Pulumi&rsquo;s scenario when cloud operations may run for minutes. If two resources are created in parallel, we want to report success as soon as it happens. Even more importantly, we want to handle the errors as they occur.</p>
<p>How can we respond to tasks one-by-one?</p>
<h2 id="use-whenany">Use WhenAny</h2>
<p>The next option is to use <code>Task.WhenAny</code> to handle the completion of tasks one-by-one. <code>WhenAny</code> accepts a collection of tasks and returns the first one that completes. After the <code>await</code> operator returns the first completed task, we can log it and exclude it from the in-flight tasks list. Then, we call <code>WhenAny</code> again with the list of all remaining tasks.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">var</span> task <span style="color:#1f2328">=</span> <span style="color:#cf222e">await</span> Task<span style="color:#1f2328">.</span>WhenAny<span style="color:#1f2328">(</span>tasks<span style="color:#1f2328">).</span>ConfigureAwait<span style="color:#1f2328">(</span><span style="color:#cf222e">false</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>                
</span></span><span style="display:flex;"><span><span style="color:#cf222e">lock</span> <span style="color:#1f2328">(</span>_inFlightTasks<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    Log<span style="color:#1f2328">(</span><span style="color:#0a3069">$&#34;{_inFlightTasks[task]} handled&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>    _inFlightTasks<span style="color:#1f2328">.</span>Remove<span style="color:#1f2328">(</span>task<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Now actually await the returned task and realize any exceptions it may have thrown.</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">await</span> task<span style="color:#1f2328">.</span>ConfigureAwait<span style="color:#1f2328">(</span><span style="color:#cf222e">false</span><span style="color:#1f2328">);</span>
</span></span></code></pre></div><p>Let&rsquo;s run the modified program and log messages again:</p>
<pre tabindex="0"><code>0308: Job 0 finishing
0312: Job 0 handled
0440: Job 0.1 finishing
0440: Job 0.1 handled
0899: Job 1 finishing
0900: Job 1 handled
0948: Job 1.0 finishing
0948: Job 1.0 handled
0957: Job 0.0 finishing
0957: Job 0.0 handled
0979: Job 1.1 finishing
0979: Job 1.1 handled
0979: Done!
</code></pre><p>This looks great! The code responds to each completion immediately, which meets our goal.</p>
<p>The code works perfectly for the small number of tasks, but does it scale?</p>
<h2 id="stress-test">Stress Test</h2>
<p>We ran the test with <code>N=2</code> to create 6 tasks in total. No matter which <code>N</code> we choose, there are only two sequential tasks (a parent task and its child task). As each task completes within 1 second, the full test should finish in less than 2 seconds in theory.</p>
<p>However, this does not hold for the <code>WhenAny</code>-based program. The plot below shows the test completion time, depending on the total number of tasks.</p>
<figure >
    
        <img src="timing.png"
            alt="Time to completion grows rapidly as the total number of tasks increases"
             />
        
    
    <figcaption>
        <h4>Time to completion grows rapidly as the total number of tasks increases</h4>
    </figcaption>
    
</figure>
<p>The 2-second rule holds until ~8.000 tasks, but then the completion time is clearly quadratic from the number of tasks.</p>
<p>The logs confirm that the last completed task is always at the 2 seconds mark, while the handler loop becomes excessively busy with awaiting.</p>
<p>This makes sense. <code>WhenAny</code> doesn&rsquo;t have a constant complexity but at least <code>O(N)</code> complexity. Executing it in a loop gives us <code>O(N^2)</code>, demonstrated by the chart above.</p>
<p>This means that large Pulumi programs managing thousands of resources would tend to complete notably slower than desired.</p>
<p>Predictably, the implementation based on <code>WhenAll</code> doesn&rsquo;t have this problem: all the thousands of tasks complete in 2 seconds.</p>
<p>How do we combine the benefits of both approaches?</p>
<h2 id="custom-await-logic">Custom Await Logic</h2>
<p>It looks like the standard library doesn&rsquo;t have a method that satisfies the requirements. The custom implementation consists of multiple parts.</p>
<p>First, there is a <code>TaskCompletionSource</code> that tracks the overall task completion:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">private</span> <span style="color:#cf222e">static</span> TaskCompletionSource<span style="color:#1f2328">&lt;</span><span style="color:#cf222e">int</span><span style="color:#1f2328">&gt;</span> Tcs <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> TaskCompletionSource<span style="color:#1f2328">&lt;</span><span style="color:#cf222e">int</span><span style="color:#1f2328">&gt;(</span>TaskCreationOptions<span style="color:#1f2328">.</span>RunContinuationsAsynchronously<span style="color:#1f2328">);</span>
</span></span></code></pre></div><p>Now, all that the new <code>WaitForAllOpenTasksToComplete</code> does is waiting for this completion source to return:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">private</span> <span style="color:#cf222e">static</span> <span style="color:#cf222e">async</span> Task WaitForAllOpenTasksToComplete<span style="color:#1f2328">()</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">await</span> Tcs<span style="color:#1f2328">.</span>Task<span style="color:#1f2328">.</span>ConfigureAwait<span style="color:#1f2328">(</span><span style="color:#cf222e">false</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>It becomes the responsibility of the <code>RegisterTask</code> method to initiate the completion of the task:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">static</span> <span style="color:#cf222e">void</span> RegisterTask<span style="color:#1f2328">(</span><span style="color:#cf222e">string</span> description<span style="color:#1f2328">,</span> Task task<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">lock</span> <span style="color:#1f2328">(</span>_inFlightTasks<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#57606a">// Duplicates may happen if we try registering things like Task.CompletedTask.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#57606a">// We&#39;ll ignore duplicates for now.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">if</span> <span style="color:#1f2328">(!</span>_inFlightTasks<span style="color:#1f2328">.</span>ContainsKey<span style="color:#1f2328">(</span>task<span style="color:#1f2328">))</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            _inFlightTasks<span style="color:#1f2328">.</span>Add<span style="color:#1f2328">(</span>task<span style="color:#1f2328">,</span> description<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    HandleCompletion<span style="color:#1f2328">(</span>task<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>All the actual logic resides in <code>HandleCompletion</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">static</span> <span style="color:#cf222e">async</span> <span style="color:#cf222e">void</span> HandleCompletion<span style="color:#1f2328">(</span>Task task<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">try</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#57606a">// Wait for the task completion.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">await</span> task<span style="color:#1f2328">.</span>ConfigureAwait<span style="color:#1f2328">(</span><span style="color:#cf222e">false</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        Log<span style="color:#1f2328">(</span><span style="color:#f6f8fa;background-color:#82071e">$</span><span style="color:#0a3069">&#34;{_inFlightTasks[task]} handled);
</span></span></span><span style="display:flex;"><span><span style="color:#0a3069"></span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">catch</span> <span style="color:#1f2328">(</span>OperationCanceledException<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        Tcs<span style="color:#1f2328">.</span>TrySetCanceled<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">catch</span> <span style="color:#1f2328">(</span>Exception ex<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        Tcs<span style="color:#1f2328">.</span>TrySetException<span style="color:#1f2328">(</span>ex<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">finally</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#57606a">// Once finished, remove the task from the set of tasks that are running.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">lock</span> <span style="color:#1f2328">(</span>_inFlightTasks<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            _inFlightTasks<span style="color:#1f2328">.</span>Remove<span style="color:#1f2328">(</span>task<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#57606a">// Check if all the tasks are completed and signal the completion source if so.</span>
</span></span><span style="display:flex;"><span>            <span style="color:#cf222e">if</span> <span style="color:#1f2328">(</span>_inFlightTasks<span style="color:#1f2328">.</span>Count <span style="color:#1f2328">==</span> <span style="color:#0550ae">0</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                Tcs<span style="color:#1f2328">.</span>TrySetResult<span style="color:#1f2328">(</span><span style="color:#0550ae">0</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><h2 id="testing-it-out">Testing It Out</h2>
<p>Let&rsquo;s make sure that the custom await logic works as desired. Here is the output for the N=2 case:</p>
<pre tabindex="0"><code>0549: Job 1 finishing
0555: Job 1 handled
0805: Job 0 finishing
0805: Job 0 handled
0851: Job 1.0 finishing
0851: Job 1.0 handled
1473: Job 1.1 finishing
1474: Job 1.1 handled
1582: Job 0.0 finishing
1582: Job 0.0 handled
1627: Job 0.1 finishing
1627: Job 0.1 handled
1629: Done!
</code></pre><p>As desired, the handling happens immediately after finishing a job. What about a large number of tasks?</p>
<p>Even 100.000 tasks complete in 2 seconds! This looks great! Mission accomplished!</p>
<h2 id="conclusion">Conclusion</h2>
<p>.NET standard library comes with great primitives to handle common patterns around tasks. However, sometimes one has to understand their limitations and create a new pattern implementation.</p>
<p>Do you see a problem with the approach above? Do you know a more straightforward way to achieve both goals? Please respond below!</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/.net" term=".net" label=".NET" />
                             
                                <category scheme="https://mikhail.io/tags/csharp" term="csharp" label="CSharp" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[The Emerging Landscape of Edge-Computing]]></title>
            <link href="https://mikhail.io/2020/07/emerging-landscape-of-edge-computing/"/>
            <id>https://mikhail.io/2020/07/emerging-landscape-of-edge-computing/</id>
            
            <published>2020-07-14T00:00:00+00:00</published>
            <updated>2020-07-14T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>What is edge computing, and what are the primary use cases in the world today? (a paper review)</blockquote><p>While I have a good understanding of what cloud computing is, &ldquo;edge computing&rdquo; has remained vague. What <em>is</em> edge computing, and what are the primary use cases in the world today?</p>
<p>As with other buzzwords, it&rsquo;s hard to give a single definite answer. Anyway, I found a paper that presents a consistent view of the topic. <a href="https://www.microsoft.com/en-us/research/uploads/prod/2020/02/GetMobile__Edge_BW.pdf">The Emerging Landscape of Edge-Computing</a> summarizes the advantages, use cases, and challenges of edge computing in 2020.</p>
<p>It turns out, I participated in edge computing projects in the past!</p>
<h2 id="consumer-edge-the-vision-that-never-happened">Consumer Edge: The Vision That Never Happened</h2>
<p>The term <strong>Edge Computing</strong> was introduced more than a decade ago. The cloud was still in infancy: a handful of providers were at the start of the global IT crusade.</p>
<p>At the same time, mobile devices were all the rage. Smartphones have changed the world, and wearables were around the corner. Still, mobile devices had minimal computing power, so they were incapable of advanced tasks like fast image processing or machine learning inference.</p>
<p>Cloud has been rapidly increasing its capacity and introducing specialized hardware like tensor-processing units (TPUs). However, the latency between a mobile device and a cloud data center would be too high (100s of milliseconds) for most interactive applications.</p>
<p>The edge computing was born to fix this problem and provide nearby powerful machines accessible from lightweight devices over a low-latency, high-bandwidth network. A stark example would be a wearable device like Google Glass that overlays real-time guidance on a heads-up display by streaming video to a TPU in a nearby edge location.</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="cyber-foraging.png"
            alt="Wearable devices migrating between edge locations for real-life experience augmentation"
             />
        
    
    <figcaption>
        <h4>Wearable devices migrating between edge locations for real-life experience augmentation</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>This vision has been described as <strong>cyber foraging</strong>. Edge computing research focused on enabling interactive applications on mobile devices. Future technology had to acquire several crucial capabilities:</p>
<ul>
<li><strong>Multi-tenant compute</strong> to serve legions of consumers on the same hardware.</li>
<li><strong>Millisecond-level latency</strong> required for interactive applications.</li>
<li><strong>Migration</strong> from one edge location to the other as the devices move through a physical environment.</li>
</ul>
<p>These were the goals five to ten years ago. Meanwhile, mobile devices grew powerful: A13 Bionic was unthinkable during the era of the first smartphones. Besides, cloud regions spread all over the planet and integrated into the global network. Today, sub-100ms consumer-to-cloud latencies are very common.</p>
<p>The authors of &ldquo;The Emerging Landscape&rdquo; argue that the cyber foraging vision hasn&rsquo;t been realized en-mass. Instead, the industry picked up edge computing for a different purpose. None of the three goals above are relevant for the actual edge applications, while new challenges arose.</p>
<h2 id="industrial-edge-reality-of-today">Industrial Edge: Reality of Today</h2>
<p>It turns out that edge computing&rsquo;s ideas found relevance in business and industrial applications, for example:</p>
<ol>
<li>
<p><strong>Industrial plants</strong> deploy numerous sensors that continuously monitor mechanical equipment, worker safety, and production workflows to ensure issues are spotted and mitigated promptly. They use edge computing because the internet connectivity at remote industrial sites may be unreliable and low-bandwidth.</p>
</li>
<li>
<p><strong>Railway industry</strong> uses high-definition cameras along the track to detect cracks in train wheels.  Cracks can cause the wheel to break and derail the entire train. The bandwidth and compute demand for this case is very dynamic: when a train passes a camera, the system generates GBs of data over several seconds. The analysis must be completed reliably within minutes to avoid severe casualties.</p>
</li>
<li>
<p><strong>Cities</strong> have deployed millions of cameras and sensors: at intersections, in parking lots, and in construction zones. The same cameras can be used to control the traffic flow across the city and alert drivers to avoid fatal accidents. Since many scenarios are centered around safety, continuous operation is critical and must not depend on the global network.</p>
</li>
<li>
<p><strong>Restaurants</strong> run prediction platforms to forecast when more food needs to be cooked. They use sensors to predict the number of customers entering the store.</p>
</li>
</ol>
<p>Many of today’s edge deployments are best described as <strong>edge-sites</strong> for long-running applications, such as industrial sensing and video analytics. These sites are single-tenant, and they rarely (if ever) host transient jobs for mobile devices.</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="edge-site.png"
            alt="Typical edge-site deployment in enterprise environments"
             />
        
    
    <figcaption>
        <h4>Typical edge-site deployment in enterprise environments</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>Somewhat surprisingly, these data-processing applications are not bound by the strict latency requirements of cyber-foraging applications such as cognitive assistance. The cloud is close enough, but it has other issues.</p>
<h2 id="why-not-cloud">Why Not Cloud?</h2>
<p>With such relatively high latency tolerance, and the high availability, scalability, and low-cost  computation offered by the cloud, why do these applications run on the edge rather than offloading to the cloud?</p>
<p>It turns out there are two main reasons:</p>
<ol>
<li>
<p><strong>Bandwidth to data centers is insufficient</strong>. The volume of generated data is immense, but the existing uplink internet channels are orders of magnitude slower than required. Furthermore, bandwidth needs may be intermittent and dynamic, causing huge spikes at the peaks.</p>
</li>
<li>
<p><strong>Connectivity is unreliable</strong>. When coupled with the mission-critical nature of the applications, even brief connectivity outages have a detrimental impact on safety and financial viability, so any downtime is unacceptable.</p>
</li>
</ol>
<p>Therefore, the dominant reasons for adopting edge computing are the need to tolerate cloud outages and the scarcity of network bandwidth.</p>
<h2 id="is-this-just-plain-old-on-prem">Is This Just Plain Old On-Prem?</h2>
<p>Why do we call these deployments an edge? Isn&rsquo;t this plain old on-premises hosting?</p>
<p>They are not. The edge-sites are still connected to the cloud for processing outside the critical path.</p>
<p>The  cloud  is a large pool of well-maintained resources with lower management overhead imposed on the user. It provides better resource efficiency by multiplexing across many users, high scalability, high availability, and low cost. Thus, it is preferable to utilize the cloud whenever possible.</p>
<p>Edge sites are a burden. The users happily offload the workloads to the cloud whenever feasible. But many scenarios are blocked because of connectivity issues and the criticality of applications.</p>
<h2 id="edge-cloud-collaboration">Edge-Cloud Collaboration</h2>
<p>As the edge and cloud are bound to coexist, they need to interact smoothly. A framework should enable developers to utilize both edge compute and cloud in multiple dimensions:</p>
<ol>
<li>
<p><strong>Graceful adaptation</strong> would allow applications to function optimally in the presence of disconnections, drops in bandwidth, or workload spikes. Adaptive applications can utilize the cloud when network conditions  permit but remain operational even in the face of network issues.</p>
</li>
<li>
<p><strong>Collaborative and application-aware orchestration</strong>. It is common for multiple applications of a single enterprise to share the edge cluster, e.g., run both fridge monitoring and customer tracking applications in a retail store. Not all apps have equal priority and criticality, but they share some degree of trust.</p>
</li>
<li>
<p><strong>Test and verification</strong>.  Debugging and testing an edge-site application is extremely difficult. As edge-site conditions can be challenging to recreate before deployment, incorrect behavior can arise from unanticipated interactions among adaptation strategies.</p>
</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p><a href="https://www.microsoft.com/en-us/research/uploads/prod/2020/02/GetMobile__Edge_BW.pdf">The Emerging Landscape of Edge-Computing</a> presents several use cases for today&rsquo;s edge deployments. These applications are not end-user interactive mobile applications opportunistically using the edge as originally envisioned.</p>
<p>Instead, they are geographically constrained, mission-critical, industrial or enterprise applications. They rely primarily on edge computing and opportunistically use the cloud. Across these scenarios, network bandwidth and reliability drive the use of edge computing, especially given the high volume of generated data, limited and intermittent connectivity to the cloud.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/edge" term="edge" label="Edge" />
                             
                                <category scheme="https://mikhail.io/tags/cloud" term="cloud" label="Cloud" />
                             
                                <category scheme="https://mikhail.io/tags/paper-review" term="paper-review" label="Paper Review" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[The Best Interview is No Interview: How I Get Jobs Without Applying]]></title>
            <link href="https://mikhail.io/2020/07/best-interview-is-no-interview-get-jobs-without-applying/"/>
            <id>https://mikhail.io/2020/07/best-interview-is-no-interview-get-jobs-without-applying/</id>
            
            <published>2020-07-01T00:00:00+00:00</published>
            <updated>2020-07-01T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>My humble story of getting (or not) a job at Amazon, Qualcomm, Jet.com, Pulumi, and more</blockquote><h2 id="interviews">Interviews</h2>
<p>I think I&rsquo;m reasonably good at passing job interviews.</p>
<p>When I was open for a job change a couple of years ago, I interviewed at eight local companies here in the Netherlands. I got job offers from seven of them. Of course, I have enough experience, and the market was scorching and candidate-friendly.</p>
<p>Besides, Dutch interviews are not as extensive compared to major tech companies in the U.S. For me, it took from 40 minutes to 4 hours of conversations to get to offer negotiation. There would typically be no whiteboard coding or take-home assignments.</p>
<p>I had two interviews at the U.S. companies too. I got offers from both of them.</p>
<p>Amazon flew me to Stockholm for a hiring event. Before jumping on a plane, I spent a day or two studying interviews at Amazon and an algorithms book. Amazon hired a hotel room and hosted four 1-hour sessions with me. There was a whiteboard standing, and every new interviewer would give me a coding or design challenge to solve within that hour. I wasn&rsquo;t well-calibrated for such interviews, and couldn&rsquo;t quite tell if I&rsquo;m performing well. Apparently, I was okay, because I got an open offer for Canada, U.S., Luxembourg, and Germany to work on some Amazon projects.</p>
<p>I also had an interview with Jet.com—Amazon&rsquo;s competitor in retail—for a position in Dublin. Two online live-coding interviews, four onsite interviews with whiteboards: a tad stressful but also fun. Coincidentally, the onsite in Dublin was two weeks after the Amazon interview, and the offers came literally on the same day.</p>
<h2 id="no-interviews">No-Interviews</h2>
<p>There&rsquo;s one big problem with this narrative. I declined all of those nine offers.</p>
<p>I didn&rsquo;t feel like these were enough of a step forward for me. Offers from Amazon and Jet were cool, but not revolutionary enough to relocate away from a well-arranged life.</p>
<p>There were more declined offers in the past. In fact, in my whole 18-years (oh gosh) career as a software developer, I only accepted a single job offer after an interview process. I left that company nine months later, despite being already promoted in my role.</p>
<p>How did I get other jobs? Honestly, it felt like they happened by chance.</p>
<p>I got my first paid gig from my father when I was an 18-year-old student. He showed me screenshots of the applications from work and asked me if I can create that kind of apps. He knew nothing about software development but needed a bunch of utilities to calibrate hardware that they were working on.</p>
<p>Sure, I knew Delphi and C++ Builder, so I could make form-based apps. I prototyped one and got my first $100 paycheck. I started working for the company and spent the next three years learning the tech and taking over existing code. By the end of that engagement, I was writing all software in their stack from C code on microcontrollers with directly addressable memory to 60-screen Windows apps.</p>
<p>There wasn&rsquo;t much to learn there anymore. Still being a student, I hopped on a three-week bootcamp organized by a software consultancy company. They were teaching free classes to promote the best students to FTEs. I scored a second place on the overall leaderboard and got hired at the double my previous salary.</p>
<p>I spent five years there, learning from many amazing people. I tried myself in different roles from an individual coder to a technology &ldquo;expert&rdquo; to a team lead to a project manager. The company has been doing contracting projects for European customers (&ldquo;outsourcing&rdquo;).</p>
<p>In the end, I quit the company to try contracting on my own. A friend of mine connected me to somebody in the Netherlands who needed help with his ongoing projects. The guy just gave me the first task, I completed it, he paid. The second task, the first lengthy project, the second project, and I got rolling.</p>
<p>I&rsquo;ve been working in my bedroom for the next four years. My contact in the Netherlands got hired by Qualcomm and switched me to their projects. Eventually, Qualcomm decided to hire me full-time, and my family moved from Russia to the Netherlands.</p>
<p>Most certainly, Qualcomm had an extensive process for hiring, so I did fly over for an onsite &ldquo;interview&rdquo;. The interview lasted 5 minutes: I said hi to everyone and then worked on the project for the rest of the day.</p>
<h2 id="follow-the-passion">Follow The Passion</h2>
<p>The other day, I interviewed somebody for a developer role at Pulumi, where I currently work. They had a screening interview first, and now going through five rounds of tech interviews. Live-coding, design questions, debugging problems, tell-me-about-the-time-when situations. Very common for anyone applying for a software engineering job in the U.S.</p>
<p>Except, I never had a single interview at Pulumi. I jumped on their product as a user as soon as the first beta went public. I enjoyed the product, published blog posts, used it for my pet projects, gave one of the first Pulumi talks in Europe.</p>
<p>When I was in Seattle for the Microsoft MVP summit, I pinged Pulumi Slack and grabbed a beer with Luke, our CTO. Soon after, I started doing paid contracting for Pulumi: writing blogs, docs, examples, then fixing tiny issues here and there. Eventually, I went to Seattle for the Pulumi 1.0 launch and a week onsite. Finally, after 12 months as a user and 6 months as a contractor, I got the contract signed for full-time employment.</p>
<p>I had no interviews. When I first started, I had zero experience with Go and very limited TypeScript, our main programming languages. I&rsquo;m not a compiler guru, not ingrained into DevOps ecosystem, never worked on developer tooling or open-source SDKs.</p>
<p>However, I thoroughly enjoy my job. It means a lot to me. I love learning from exceptional people, and I have a lot of freedom.</p>
<h2 id="off-the-beaten-track">Off The Beaten Track</h2>
<p>There were two categories of companies that I interacted with. The first group invited me for an interview, and the second group allowed me to collaborate without a formal hiring process. I consistently chose to work in the latter group and enjoyed my time there. Why?</p>
<p>Every such job meant going out of a local maximum, out of comfort zone. Working on calibration software without any experience with metrology. Jumping between consulting projects, teams, and roles. Relocating to another country to join a multi-national company. Becoming one of the two non-U.S. employees at a DevOps tooling startup without ever seeing bash before.</p>
<p>None of these were &ldquo;the next logical step&rdquo; in my career. Maybe I could pass interviews at those companies, perhaps I wouldn&rsquo;t. That&rsquo;s mostly irrelevant because there wouldn&rsquo;t be a sequence of events to land me at a business-as-usual interview at one of those companies.</p>
<h2 id="expand-your-circle">Expand Your Circle</h2>
<p>There&rsquo;s a lot of arguments about the interview process being broken. A candidate spends several hours solving computer science puzzles on a whiteboard to get a job of fixing bugs in a React app. Companies may be too strict at not accepting great candidates and hiring unfit candidates at the same time. Perhaps that&rsquo;s all true.</p>
<p>I&rsquo;m actually making a different point. A successful job search doesn&rsquo;t have to be centered around an interview. If my experience generalizes to others at all, here are some activities that may help you get the next great job:</p>
<ol>
<li>
<p><strong>Build a strong professional network</strong>. And not just a bunch of hiring managers. Diverse connections might perform even better since you&rsquo;d get access to a broad set of opportunities. You don&rsquo;t know what you are looking for anyway.</p>
</li>
<li>
<p><strong>Keep learning and be curious</strong>, even a topic seems to bring no immediate value. The goal is expanding your surface area of interest, curiosity, and skills.</p>
</li>
<li>
<p><strong>Take part-time gigs</strong>, contracting, one-off apps. They probably won&rsquo;t make you rich on their own. Still, successful low-risk engagement with somebody may lead to longer happy and fulfilling relationships.</p>
</li>
<li>
<p><strong>Build a public profile</strong>. Publish your code, write blogs, share your interests, design, run, and publish experiments. More than once was I surprised about the way this helped me with jobs, networking, learning, or getting a consulting gig.</p>
</li>
</ol>
<p>And if you are wearing a hiring hat, try to give your potential future workers a chance to engage without full-blown employment commitment. Asking me to code a simple but useful real-life application. Running a free class for software engineering students. Hiring temporary contractors from distant countries. Those companies get me as their employee.</p>
<p>Enough about myself! I&rsquo;m keen to learn about your experience. Whether you can relate to my story or disagree with my take, please leave a comment below.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/jobs" term="jobs" label="Jobs" />
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Eliminate Cold Starts by Predicting Invocations of Serverless Functions]]></title>
            <link href="https://mikhail.io/2020/06/eliminate-cold-starts-by-predicting-invocations-of-serverless-functions/"/>
            <id>https://mikhail.io/2020/06/eliminate-cold-starts-by-predicting-invocations-of-serverless-functions/</id>
            
            <published>2020-06-18T00:00:00+00:00</published>
            <updated>2020-06-18T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Azure Functions introduce a data-driven strategy to pre-warm serverless applications right before the next request comes in</blockquote><p>Developers and decision-makers often mention <a href="/serverless/coldstarts/">cold starts</a> as a significant drawback of serverless functions. Cloud providers continually invest in reducing the latency of a cold start, but they haven&rsquo;t done much to prevent them altogether. The most common technique is to keep a worker alive for 10-20 minutes after each request, hoping that another request comes in and benefits from the warm instance.</p>
<p>This simple strategy works to some extent, but it&rsquo;s both wasteful in terms of resource utilization and not particularly helpful for low-usage applications. Is there an alternative strategy that could adapt to the workload, reduce the frequency of cold starts, <em>and</em> be more efficient?</p>
<p>In <a href="/2020/05/serverless-in-the-wild-azure-functions-usage-stats">Azure Functions Production Usage Statistics</a>, I reviewed the first part of the <a href="https://arxiv.org/pdf/2003.03423.pdf">Serverless in the Wild</a> paper, which outlines statistics of Azure Functions running in production. Actually, the ultimate goal of that paper is to suggest an improvement to the cold start mitigation policy and validate the proposed strategy based on the data.</p>
<p>This article is my second installment of the paper review. I focus on the idea of predicting future invocations and pre-warming of serverless workers. I describe the current state of cold starts, then explain the suggested improvements from the paper. Finally, I present my own take on those ideas.</p>
<p>The new policy is definitely worth studying because it will be applied to your Azure Functions soon!</p>
<h2 id="challenges">Challenges</h2>
<p>As the statistics show, many Azure Function Apps are called very infrequently. Let&rsquo;s consider a concrete example: an HTTP-triggered function that runs approximately once per hour and returns current data for a report.</p>
<p>Currently, every invocation of such a function would hit a cold start. Specifically, Azure uses a fixed “keep-alive” policy that retains the resources in memory for 20 minutes after execution. This isn&rsquo;t helpful in our scenario since requests come every 60 minutes.</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="scenario.png"
            alt="Warm instances are recycled after 20 minutes, so an hourly request hits a cold start"
             />
        
    
    <figcaption>
        <h4>Warm instances are recycled after 20 minutes, so an hourly request hits a cold start</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>In this specific case, the fixed policy is problematic for everyone:</p>
<ul>
<li>Users hit a cold start every time, which significantly increases the response time.</li>
<li>Azure wastes resources on keeping a warm instance in memory for 20 minutes every hour without any benefits.</li>
</ul>
<p>Indeed, cloud providers seek to achieve high function performance at the lowest possible resource cost. They can&rsquo;t keep all functions in memory all the time: as the stats show, most functions run very seldom, so keeping all of them warm would be a massive waste of resources.</p>
<p>What if the cloud provider could observe each application and adapt the policy for each application workload according to its actual invocation frequency and pattern?</p>
<h2 id="optimal-strategy-predicts-the-future">Optimal Strategy Predicts The Future</h2>
<p>Let&rsquo;s start with a hypothetical ideal solution for our specific one-call-per-hour example. Instead of keeping a warm instance for a fixed period after each invocation, an efficient policy would shut down the instance immediately after each execution. Then, it would boot a new instance right before the next invocation is about to come in.</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="prewarming.png"
            alt="A new instance is pre-warmed right before the request comes in and recycled immediately after the execution is complete"
             />
        
    
    <figcaption>
        <h4>A new instance is pre-warmed right before the request comes in and recycled immediately after the execution is complete</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>This solves both problems above. Now, all requests are served quickly because Azure creates a fresh instance <em>before</em> a request comes in. Also, Azure saves resources because it can pre-warm the instance shortly before each request and shut it down immediately after the invocation is completed.</p>
<p>This ideal case only works in the world of perfect information. Of course, Azure can&rsquo;t perfectly predict the future and spin up a new instance right before the next HTTP request. Instead, it would need to learn the workload and try to predict the next invocation probabilistically. That&rsquo;s the essence of the policy that the authors of &ldquo;Serverless in the Wild&rdquo; suggest.</p>
<p>Predicting arbitrary workloads can be pretty hard. Every application is unique, and invocations are caused by external events like human behavior or actions in business processes.</p>
<p>Also, there are limits to resources that a prediction policy may consume. The policy calculation should be efficient and not have a significant impact on the system&rsquo;s overall performance. A practical policy would have low overhead both in terms of the size of data structures and the CPU usage overhead.</p>
<h2 id="proposed-policy">Proposed Policy</h2>
<p>The paper suggests a practical policy that tries to predict future invocations without being too expensive.</p>
<p>Let&rsquo;s start at the point when a new application has just been deployed. Azure knows nothing about its invocation patterns yet. The new policy would default to the traditional fixed &ldquo;keep-alive&rdquo; interval but would keep an instance running for a generous 4 hours. Simultaneously, it starts learning the workload.</p>
<p>Every time a new request comes in, the policy calculates how many minutes passed since the end of the previous invocation and records this value in a histogram. The histogram&rsquo;s bins have one-minute granularity. So, in my example, if an invocation came 60 minutes after the previous one, the value for the bin <code>60</code> will increase by one.</p>
<p>At some point, the policy would decide that it knows enough to start predicting future invocations. The histogram for my application may look like this:</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="histogram.png"
            alt="Every column shows how many requests landed on minute X after a previous execution"
             />
        
    
    <figcaption>
        <h4>Every column shows how many requests landed on minute X after a previous execution</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>The new adaptive policy kicks in. Now, it shuts down the active instance after each request, because it knows that the next invocation is unlikely to come any time soon. Then, it uses two cut-off points to plan the warming strategy:</p>
<ul>
<li>The <strong>head cut-off</strong> point is when a new warm instance should be ready. Calculated as 5th percentile minus 10% margin. Approximately 53 minutes in my example.</li>
<li>The <strong>tail cut-off</strong> point is when to kill the warm instance if no request comes in. Calculated as 95th percentile plus 10% margin. Approximately 67 minutes in my example.</li>
</ul>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="cutoff.png"
            alt="An adaptive policy pre-warms an instance at the head cut-off and keeps it until a request comes or until the tail cut-off"
             />
        
    
    <figcaption>
        <h4>An adaptive policy pre-warms an instance at the head cut-off and keeps it until a request comes or until the tail cut-off</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>Because my specific workload is highly predictable, it would enjoy the absence of cold starts. Would the policy work for other scenarios?</p>
<h2 id="scenarios">Scenarios</h2>
<p>The suggested policy makes sense for the example we considered so far. However, real-life workloads are very diverse. Let&rsquo;s try to generalize and consider several possible scenarios and how the policy handles them.</p>
<h4 id="regular-cadence">Regular cadence</h4>
<p>Consistent intervals between invocations are the ideal match for the suggested policy. If a histogram is well-shaped and relatively narrow, both head and tail cut-offs are easy to identify. These distributions produce the ideal situation: long shutdown periods and short keep-alive windows.</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="regular.png"
            alt="Well-shaped narrow distribution produces clear cut-off points"
             />
        
    
    <figcaption>
        <h4>Well-shaped narrow distribution produces clear cut-off points</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>My example above falls into this category.</p>
<h4 id="frequent-invocations">Frequent invocations</h4>
<p>Many applications would invoke functions frequently, so, many measured intervals would be close to zero.</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="frequent.png"
            alt="In this case, instances are not unloaded after a request is executed and wait for another one, or the tail cut-off moment"
             />
        
    
    <figcaption>
        <h4>In this case, instances are not unloaded after a request is executed and wait for another one, or the tail cut-off moment</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>In these cases, the head cut-off is rounded down to zero. The policy does not shut down the application after a function execution but keeps it alive until the next execution, or until the tail cut-off.</p>
<h4 id="inconsistent-invocations-or-not-enough-data">Inconsistent invocations or not enough data</h4>
<p>The policy needs a certain amount of quality data to start being useful in predicting the next invocation.
The application may be recently deployed and may not have enough points yet:</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="sparse.png"
            alt="Too few data to make reliable predictions: wait and learn while using the default policy"
             />
        
    
    <figcaption>
        <h4>Too few data to make reliable predictions: wait and learn while using the default policy</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>Alternatively, data points might not come in a well-shaped cluster:</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="random.png"
            alt="No definite shape of the histogram: fall back to the default policy"
             />
        
    
    <figcaption>
        <h4>No definite shape of the histogram: fall back to the default policy</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>In both of these cases, the policy reverts to a default approach: no shutdown and a long keep-alive window. This puts an extra burden on the cloud provider, but the idea is that these scenarios would be rare enough to allow the policy to stay practical.</p>
<h4 id="invocations-beyond-4-hours">Invocations beyond 4 hours</h4>
<p>The policy defines a maximum value for histogram data to limit the storage capacity for the histogram. The paper suggests a maximum of 4 hours. All intervals beyond that threshold are recorded in a special overflow bin.</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="outofbounds.png"
            alt="Values beyond the 4-hour limit end up in a special &#34;everything else&#34; overflow bin"
             />
        
    
    <figcaption>
        <h4>Values beyond the 4-hour limit end up in a special &#34;everything else&#34; overflow bin</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>If a lot of values start to fall into that range, the bin-based policy can&rsquo;t perform well anymore. For this category of applications, the paper suggests switching to time-series analysis to predict the next interval duration. They mention the <a href="https://en.wikipedia.org/wiki/Autoregressive_integrated_moving_average">autoregressive integrated moving average</a> model without providing many details.</p>
<p>As invocations of this type are infrequent, the overall overhead of such a model would stay relatively low.</p>
<h2 id="evaluation">Evaluation</h2>
<p>The authors evaluated the policy based on recorded <a href="/2020/05/serverless-in-the-wild-azure-functions-usage-stats">production usage statistics</a>. The evaluation consists of two parts:</p>
<ul>
<li>A simulation that iterates through the data, calculates the policy models, and evaluates whether each invocation would yield a cold starts with the new policy.</li>
<li>A replay of the recorded invocation trace on a modified version of Apache OpenWhisk, an open-source FaaS platform. The controller was modified to use the new policy while managing workers. The trace was scaled down by randomly selecting applications with mid-range popularity.</li>
</ul>
<p>Both parts showed similarly promising results of reducing the cold start frequency and resource waste. The new policy reduced the average and 99-percentile function execution time 33% and 82%, respectively, while also reducing the average memory consumption of workers by 16%.</p>
<p>The overhead of the policy looks manageable too. The controller&rsquo;s policy implementation adds less than 1 ms to the end-to-end latency and only a 5% increase in controller&rsquo;s CPU utilization.</p>
<p>By tuning the parameters of the policy (default window, cut-off points, granularity), its implementation can achieve the same number of cold starts at much lower resource cost, or keep the same resource cost but reduce the frequency of cold starts significantly.</p>
<h2 id="production-implementation">Production Implementation</h2>
<p>Encouraged by the position evaluation, the team implemented the policy in Azure Functions for HTTP-triggered applications, it will be rolling out to production in stages starting this month (June 2020).</p>
<p>Here are some implementation details:</p>
<ul>
<li>Each histogram contains data for 4 hours, filling up 960 bytes per application.</li>
<li>The histogram is stored in memory and backed up to a database once per hour.</li>
<li>A new histogram is started every day with a history of previous values stored for two weeks.</li>
<li>Worker warming is scheduled for the calculated head cut-off point minus 90 seconds.</li>
<li>Pre-warming loads function dependencies and performs JIT where possible.</li>
<li>All policy decisions are asynchronous, off the critical path to minimize the latency impact on the invocation.</li>
</ul>
<p>I look forward to testing this new policy once it&rsquo;s rolled out. Expect a follow-up blog post!</p>
<h2 id="open-questions">Open Questions</h2>
<p>Everything above is my summary of the &ldquo;Serverless in the Wild&rdquo; paper&rsquo;s ideas and findings. I want to close the blog post with some questions that I still have, personally.</p>
<p>Whether true in practice or not, the public opinion strongly perceives cold starts as a notable obstacle to serverless adoption. It&rsquo;s great to see some concrete suggestions driven by data that may improve cold starts in serverless functions.</p>
<p>The sweet spot of the suggested policy is applications with relatively regular intervals between invocations below several hours. I think the practical effect of the strategy might still be limited. Here are some concerns that I see.</p>
<p><em>The authors reviewed my questions and provided their answers, which I&rsquo;m including in the text below.</em></p>
<h4 id="who-benefits">Who benefits?</h4>
<p>Not every serverless function is sensitive to cold starts.</p>
<p>The policy favors functions with predictable intervals. Timer-based schedules (whether functions with timer trigger or external timers sending requests to the app or IoT devices communicating periodically) may produce perfect histograms. And yet, those functions are not damaged by a cold start and may entirely live with it.</p>
<p>On the other side, human-initiated requests that care about cold starts are likely to be less predictable. Does it mean they don&rsquo;t enjoy much improvement with the new policy?</p>
<p>The Azure Functions team is rolling the policy out for HTTP functions, so they do expect it to be useful. We will know soon!</p>
<h5 id="response-from-the-authors">Response from the authors:</h5>
<blockquote>
<p>There is a large fraction of the applications that will likely benefit. Because for many applications the keep-alive interval can drastically reduce, the provider can afford to increase the keep-alive for other applications. About 50% of the applications have average inter-invocation times of 30 minutes of more. These incur many cold starts in the fixed policy, and will likely see a reduction in cold starts because their keep-alive intervals will be able to grow. Applications with idle times longer than 4 hours can also benefit because of the ARIMA time-series prediction.</p></blockquote>
<h4 id="is-it-flexible-enough">Is it flexible enough?</h4>
<p>The policy assumes that the interval distribution is stable over time. How will this hold in practice over more extended periods?</p>
<p>Imagine an application that is only used during business hours. Or, an application that is mostly idle but is sporadically applied for a specific business process. Or, a demo app that you want to show to your colleagues during a planning meeting. Likely, all of them would still hit cold starts at the beginning of a session.</p>
<p>Maybe that&rsquo;s why the production implementation begins with a clean histogram every day. They will watch and learn, and may use data for the last two weeks to improve later on.</p>
<h5 id="response-from-the-authors-1">Response from the authors:</h5>
<blockquote>
<p>The policy does not have to assume a stationary distribution of arrival patterns. In the traces analyzed in the paper there was not a significant enough variation in the distributions to enable a deep investigation. With the production deployment, we will investigate different policies to decay information from previous histograms.</p></blockquote>
<h4 id="what-about-function-warming">What about function warming?</h4>
<p>There&rsquo;s the elephant in the room of cold starts: function &ldquo;warming&rdquo;. Warming is a trick when a developer adds an extra timer-based function to their Function App so that the timer triggers every few minutes. This way, the runtime would never unload it, and an instance would always be ready.</p>
<p>I suspect that this simple trick still outperforms the suggested histogram-based policy in terms of cold-start prevention. Obviously, it doesn&rsquo;t help the cloud provider to save costs.</p>
<p>Is there a way to combine two approaches and get the benefits of both?</p>
<h5 id="response-from-the-authors-2">Response from the authors:</h5>
<blockquote>
<p>The results in the paper take into account user-generated warm-up functions that are present in the data. If the hybrid policy is successful in preventing cold starts, there will be no need for users to create periodic warm-up functions, which represent extra effort and higher costs.</p></blockquote>
<h4 id="what-about-the-scale-out-cold-start">What about the scale-out cold start?</h4>
<p>The paper focuses on applications that are invoked rarely. However, even applications with higher utilization may still hit cold starts when scaled out on multiple instances. Every new instance would need to boot, and the requests would still be waiting.</p>
<p>The suggested policy does not address cold starts beyond the first instance, even though they do occur and potentially have a more significant impact on latency percentiles of real-world human-facing applications.</p>
<h5 id="response-from-the-authors-3">Response from the authors:</h5>
<blockquote>
<p>We didn’t address scale-out cold starts in the paper. We can pre-warm the scale-out instances by forcing the scale out to happen slightly before it normally would. For example, when the scale out is triggered by a threshold number of concurrent invocations, we can lower the threshold slightly. We will be experimenting with such techniques in our implementation in Azure Functions.</p></blockquote>
<h2 id="conclusion">Conclusion</h2>
<p>Despite several open questions above, I&rsquo;m delighted that the paper was published. I welcome any structured effort that focuses on cold start optimization. The paper highlights usage statistics, the challenges of the cold start problem, and suggests several improvements.</p>
<p>It&rsquo;s even more exciting to see the finding being applied in Azure in production!</p>
<p>If you want to learn more, you can read the full paper here: <a href="https://arxiv.org/pdf/2003.03423.pdf">Serverless in the Wild: Characterizing and Optimizing the Serverless Workload at a Large Cloud Provider</a>.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                             
                                <category scheme="https://mikhail.io/tags/paper-review" term="paper-review" label="Paper review" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Unit Testing Cloud Deployments with Pulumi in C#]]></title>
            <link href="https://mikhail.io/2020/05/unit-testing-cloud-deployments-with-pulumi-in-csharp/"/>
            <id>https://mikhail.io/2020/05/unit-testing-cloud-deployments-with-pulumi-in-csharp/</id>
            
            <published>2020-05-21T00:00:00+00:00</published>
            <updated>2020-05-21T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Developing infrastructure programs in C# with unit tests, TDD, and mocks</blockquote><p>Because Pulumi uses general-purpose programming languages to provision cloud resources, you can take advantage of native tools and perform automated tests of your infrastructure. The full power of each language is available, including access to libraries and frameworks for testing.</p>
<p>This blog post takes a deeper dive into mock-based unit testing of Pulumi programs written in C#. You can find an F# version of this blog post <a href="/2020/05/unit-testing-cloud-deployments-with-pulumi-in-fsharp/">here</a>.</p>
<h2 id="how-unit-testing-works-in-pulumi">How Unit Testing Works in Pulumi</h2>
<p>Let&rsquo;s start with a picture showing how Pulumi components interact during a typical deployment process.</p>
<p><img src="./engine.png" alt="Pulumi Components Interaction"></p>
<p>Whenever a new resource is instantiated in code, the program makes a remote call to the engine. The engine receives the resource&rsquo;s input values, validates them, and translates the request to an invocation of a cloud API. The API returns some data, which are translated to resource outputs and sent back to the program.</p>
<p>The remote calls may be slow, unreliable, and non-deterministic, which makes testing of these interactions hard.</p>
<p><a href="https://www.pulumi.com/docs/guides/testing/">Pulumi testing guide</a> outlines several testing methods, but today I want to focus on unit testing.</p>
<p>The Pulumi SDK provides a hook to replace all the remote calls with mocks. Mocks run in the same process and can respond immediately with hard-coded or calculated on-the-fly data.</p>
<p><img src="./mocks.png" alt="Pulumi with Mocks"></p>
<p>We can write fully deterministic and blazingly fast automated tests as all remote calls and uncertainty are eliminated. There is no cloud to respond to resource creation, so it&rsquo;s a developer&rsquo;s responsibility to mimic the cloud behavior with mocks adequately.</p>
<p>This blog post walks you through an example of unit testing and mocking with the .NET SDK.</p>
<h2 id="define-the-base-structure">Define the Base Structure</h2>
<p>In this article, I build a program that deploys a static website to Azure. I use TDD to add new tests and deployment components bit-by-bit.</p>
<p>Let&rsquo;s start with an empty project—go ahead and create a .NET Core Console Application with the .NET CLI or your favorite IDE.</p>
<h3 id="install-nuget-packages">Install NuGet packages</h3>
<p>You are free to choose your unit testing frameworks, mocking and assertions libraries. I&rsquo;m using NUnit with FluentAssertions, and my program tests Azure resources, so this is my project file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#0550ae">&lt;Project</span> <span style="color:#1f2328">Sdk=</span><span style="color:#0a3069">&#34;Microsoft.NET.Sdk&#34;</span><span style="color:#0550ae">&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&lt;PropertyGroup&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;OutputType&gt;</span>Exe<span style="color:#0550ae">&lt;/OutputType&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;TargetFramework&gt;</span>netcoreapp3.1<span style="color:#0550ae">&lt;/TargetFramework&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;Nullable&gt;</span>enable<span style="color:#0550ae">&lt;/Nullable&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&lt;/PropertyGroup&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&lt;ItemGroup&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;PackageReference</span> <span style="color:#1f2328">Include=</span><span style="color:#0a3069">&#34;FluentAssertions&#34;</span> <span style="color:#1f2328">Version=</span><span style="color:#0a3069">&#34;5.10.2&#34;</span> <span style="color:#0550ae">/&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;PackageReference</span> <span style="color:#1f2328">Include=</span><span style="color:#0a3069">&#34;Microsoft.NET.Test.Sdk&#34;</span> <span style="color:#1f2328">Version=</span><span style="color:#0a3069">&#34;16.5.0&#34;</span> <span style="color:#0550ae">/&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;PackageReference</span> <span style="color:#1f2328">Include=</span><span style="color:#0a3069">&#34;NUnit&#34;</span> <span style="color:#1f2328">Version=</span><span style="color:#0a3069">&#34;3.12.0&#34;</span> <span style="color:#0550ae">/&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;PackageReference</span> <span style="color:#1f2328">Include=</span><span style="color:#0a3069">&#34;NUnit3TestAdapter&#34;</span> <span style="color:#1f2328">Version=</span><span style="color:#0a3069">&#34;3.16.1&#34;</span> <span style="color:#0550ae">/&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;PackageReference</span> <span style="color:#1f2328">Include=</span><span style="color:#0a3069">&#34;Pulumi.Azure&#34;</span> <span style="color:#1f2328">Version=</span><span style="color:#0a3069">&#34;3.*&#34;</span> <span style="color:#0550ae">/&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#0550ae">&lt;/ItemGroup&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#0550ae">&lt;/Project&gt;</span>
</span></span></code></pre></div><h3 id="stack">Stack</h3>
<p>A Pulumi stack is the &ldquo;unit&rdquo; of our testing. Every test instantiates a stack, retrieves the resources that the stack defines, and makes assertions about them.</p>
<p>Here is my starting point in <code>WebsiteStack.cs</code> file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">System.IO</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">Pulumi</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">Pulumi.Azure.Core</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">Storage</span> <span style="color:#1f2328">=</span> Pulumi<span style="color:#1f2328">.</span>Azure<span style="color:#1f2328">.</span>Storage<span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">public</span> <span style="color:#cf222e">class</span> <span style="color:#1f2328">WebsiteStack</span> <span style="color:#1f2328">:</span> Stack
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">public</span> WebsiteStack<span style="color:#1f2328">()</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#57606a">// &lt;-- Cloud resources go here</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>This class is precisely what Pulumi expects to start deploying your resources to the cloud. Once the test suite is ready and green, you can deploy the stack with <code>pulumi up</code>.</p>
<h3 id="mocks">Mocks</h3>
<p>As I explained above, unit tests replace the real Pulumi engine with mocks. Mocks get all calls and respond with predefined values.</p>
<p>A mock class has to implement two methods of <code>Pulumi.Testing.IMocks</code> interface. <code>NewResourceAsync</code> is called whenever a new resource is defined, while <code>CallAsync</code> is invoked when our program retrieves information about existing cloud resources. We&rsquo;ll focus on the former in this post, but we still need to define both.</p>
<p>While you are free to use your favorite mocking library, I&rsquo;ll keep it simple and define the mocks as a plain class.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">System.Collections.Immutable</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">System.Threading.Tasks</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">Pulumi</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">Pulumi.Testing</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">namespace</span> <span style="color:#24292e">UnitTesting</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">class</span> <span style="color:#1f2328">Mocks</span> <span style="color:#1f2328">:</span> IMocks
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">public</span> Task<span style="color:#1f2328">&lt;(</span><span style="color:#cf222e">string</span> id<span style="color:#1f2328">,</span> <span style="color:#cf222e">object</span> state<span style="color:#1f2328">)&gt;</span> NewResourceAsync<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>            <span style="color:#cf222e">string</span> type<span style="color:#1f2328">,</span> <span style="color:#cf222e">string</span> name<span style="color:#1f2328">,</span> ImmutableDictionary<span style="color:#1f2328">&lt;</span><span style="color:#cf222e">string</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">object</span><span style="color:#1f2328">&gt;</span> inputs<span style="color:#1f2328">,</span> <span style="color:#cf222e">string?</span> provider<span style="color:#1f2328">,</span> <span style="color:#cf222e">string?</span> id<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#cf222e">var</span> outputs <span style="color:#1f2328">=</span> ImmutableDictionary<span style="color:#1f2328">.</span>CreateBuilder<span style="color:#1f2328">&lt;</span><span style="color:#cf222e">string</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">object</span><span style="color:#1f2328">&gt;();</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#57606a">// Forward all input parameters as resource outputs, so that we could test them.</span>
</span></span><span style="display:flex;"><span>            outputs<span style="color:#1f2328">.</span>AddRange<span style="color:#1f2328">(</span>inputs<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#57606a">// &lt;-- We&#39;ll customize the mocks here</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#57606a">// Default the resource ID to `{name}_id`.</span>
</span></span><span style="display:flex;"><span>            id <span style="color:#1f2328">??=</span> <span style="color:#0a3069">$&#34;{name}_id&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#cf222e">return</span> Task<span style="color:#1f2328">.</span>FromResult<span style="color:#1f2328">((</span>id<span style="color:#1f2328">,</span> <span style="color:#1f2328">(</span><span style="color:#cf222e">object</span><span style="color:#1f2328">)</span>outputs<span style="color:#1f2328">));</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">public</span> Task<span style="color:#1f2328">&lt;</span><span style="color:#cf222e">object</span><span style="color:#1f2328">&gt;</span> CallAsync<span style="color:#1f2328">(</span><span style="color:#cf222e">string</span> token<span style="color:#1f2328">,</span> ImmutableDictionary<span style="color:#1f2328">&lt;</span><span style="color:#cf222e">string</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">object</span><span style="color:#1f2328">&gt;</span> inputs<span style="color:#1f2328">,</span> <span style="color:#cf222e">string?</span> provider<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#57606a">// We don&#39;t use this method in this particular test suite.</span>
</span></span><span style="display:flex;"><span>            <span style="color:#57606a">// Default to returning whatever we got as input.</span>
</span></span><span style="display:flex;"><span>            <span style="color:#cf222e">return</span> Task<span style="color:#1f2328">.</span>FromResult<span style="color:#1f2328">((</span><span style="color:#cf222e">object</span><span style="color:#1f2328">)</span>inputs<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>Both methods receive several parameters and need to return a result. Notably, they get the list of <code>inputs</code>—the arguments that our program defined for the given resource. The goal of the mocks is to return the list of outputs, similarly to what a cloud provider would do.</p>
<p>My default implementation returns the outputs that include all the inputs and nothing else. That&rsquo;s a sensible default: usually, real outputs are a superset of inputs. We&rsquo;ll extend this behavior as we need later on.</p>
<h3 id="test-fixture">Test Fixture</h3>
<p>The next class to add is the container for my future unit tests. I named the file <code>WebsiteStackTests.cs</code> and it defines a test container, called a &ldquo;test fixture&rdquo; in NUnit:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">System.Linq</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">System.Threading.Tasks</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">FluentAssertions</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">NUnit.Framework</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">Pulumi</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">Pulumi.Azure.Core</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">using</span> <span style="color:#24292e">Storage</span> <span style="color:#1f2328">=</span> Pulumi<span style="color:#1f2328">.</span>Azure<span style="color:#1f2328">.</span>Storage<span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">namespace</span> <span style="color:#24292e">UnitTesting</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">	[TestFixture]</span>
</span></span><span style="display:flex;"><span>	<span style="color:#cf222e">public</span> <span style="color:#cf222e">class</span> <span style="color:#1f2328">WebserverStackTests</span>
</span></span><span style="display:flex;"><span>	<span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#57606a">// &lt;-- Tests go here</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#cf222e">private</span> <span style="color:#cf222e">static</span> Task<span style="color:#1f2328">&lt;</span>ImmutableArray<span style="color:#1f2328">&lt;</span>Resource<span style="color:#1f2328">&gt;&gt;</span> TestAsync<span style="color:#1f2328">()</span>
</span></span><span style="display:flex;"><span>		<span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>			<span style="color:#cf222e">return</span> Deployment<span style="color:#1f2328">.</span>TestAsync<span style="color:#1f2328">&lt;</span>WebsiteStack<span style="color:#1f2328">&gt;(</span><span style="color:#cf222e">new</span> Mocks<span style="color:#1f2328">(),</span> <span style="color:#cf222e">new</span> TestOptions <span style="color:#1f2328">{</span>IsPreview <span style="color:#1f2328">=</span> <span style="color:#cf222e">false</span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>		<span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>I defined a helper method <code>TestAsync</code> that points Pulumi&rsquo;s <code>Deployment.TestAsync</code> to our stack and mock classes.</p>
<p>As we progress through the article, I will add tests to this class one-by-one.</p>
<h3 id="retrieve-output-values">Retrieve output values</h3>
<p>Finally, I need a small extension method to extract values from outputs. Every Pulumi resource returns values wrapped inside an <code>Output&lt;T&gt;</code> container. While the real engine runs, those values may be known or unknown, but my mock-based tests always return known values. It&rsquo;s safe to get those values and convert them to a <code>Task&lt;T&gt;</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#cf222e">public</span> <span style="color:#cf222e">static</span> <span style="color:#cf222e">class</span> <span style="color:#1f2328">TestingExtensions</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">public</span> <span style="color:#cf222e">static</span> Task<span style="color:#1f2328">&lt;</span>T<span style="color:#1f2328">&gt;</span> GetValueAsync<span style="color:#1f2328">&lt;</span>T<span style="color:#1f2328">&gt;(</span><span style="color:#cf222e">this</span> Output<span style="color:#1f2328">&lt;</span>T<span style="color:#1f2328">&gt;</span> output<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">var</span> tcs <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> TaskCompletionSource<span style="color:#1f2328">&lt;</span>T<span style="color:#1f2328">&gt;();</span>
</span></span><span style="display:flex;"><span>        output<span style="color:#1f2328">.</span>Apply<span style="color:#1f2328">(</span>v <span style="color:#1f2328">=&gt;</span> <span style="color:#1f2328">{</span> tcs<span style="color:#1f2328">.</span>SetResult<span style="color:#1f2328">(</span>v<span style="color:#1f2328">);</span> <span style="color:#cf222e">return</span> v<span style="color:#1f2328">;</span> <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">return</span> tcs<span style="color:#1f2328">.</span>Task<span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>To learn more about outputs, read <a href="https://www.pulumi.com/docs/intro/concepts/programming-model/#stack-outputs%22">this article</a>.</p>
<h2 id="first-test">First Test</h2>
<p>The setup is done, time to write my first unit test! True to the TDD spirit, I write the tests before I define any resources.</p>
<p>Every Azure resource has to live inside a resource group, so my first test checks that a new resource group is defined.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#1f2328">[Test]</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">public</span> <span style="color:#cf222e">async</span> Task SingleResourceGroupExists<span style="color:#1f2328">()</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> resources <span style="color:#1f2328">=</span> <span style="color:#cf222e">await</span> TestAsync<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> resourceGroups <span style="color:#1f2328">=</span> resources<span style="color:#1f2328">.</span>OfType<span style="color:#1f2328">&lt;</span>ResourceGroup<span style="color:#1f2328">&gt;().</span>ToList<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>    resourceGroups<span style="color:#1f2328">.</span>Count<span style="color:#1f2328">.</span>Should<span style="color:#1f2328">().</span>Be<span style="color:#1f2328">(</span><span style="color:#0550ae">1</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;a single resource group is expected&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>This code is great to illustrate the overall structure of each test:</p>
<ol>
<li>Call the <code>TestAsync</code> method to evaluate the stack.</li>
<li>Find the target resource in the collection of created resources.</li>
<li>Assert a property of the target resource.</li>
</ol>
<p>In this case, the test validates that there is one and only one resource group defined.</p>
<p>I can run <code>dotnet test</code> and see the expected test failure:</p>
<pre tabindex="0"><code>$ dotnet test
...

X SingleResourceGroupExists [238ms]
  Error Message:
   Expected resourceGroups.Count to be 1 because a single resource group is expected, but found 0.
...
Total tests: 1
     Failed: 1
 Total time: 0.9270 Seconds
</code></pre><p>I go ahead and add the following definition to the <code>WebsiteStack</code> constructor.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#cf222e">var</span> resourceGroup <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> ResourceGroup<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;www-prod-rg&#34;</span><span style="color:#1f2328">);</span>
</span></span></code></pre></div><p>This change is enough to make the tests green!</p>
<pre tabindex="0"><code>$ dotnet test
...
Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 0.8462 Seconds
</code></pre><h2 id="tags">Tags</h2>
<p>For billing and lifecycle management, I want all my resource groups to be tagged. Therefore, my second test validates that the resource group has an <code>Environment</code> tag on it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#1f2328">[Test]</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">public</span> <span style="color:#cf222e">async</span> Task ResourceGroupHasEnvironmentTag<span style="color:#1f2328">()</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> resources <span style="color:#1f2328">=</span> <span style="color:#cf222e">await</span> TestAsync<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> resourceGroup <span style="color:#1f2328">=</span> resources<span style="color:#1f2328">.</span>OfType<span style="color:#1f2328">&lt;</span>ResourceGroup<span style="color:#1f2328">&gt;().</span>First<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> tags <span style="color:#1f2328">=</span> <span style="color:#cf222e">await</span> resourceGroup<span style="color:#1f2328">.</span>Tags<span style="color:#1f2328">.</span>GetValueAsync<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>    tags<span style="color:#1f2328">.</span>Should<span style="color:#1f2328">().</span>NotBeNull<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;Tags must be defined&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>    tags<span style="color:#1f2328">.</span>Should<span style="color:#1f2328">().</span>ContainKey<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;Environment&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>The test finds the resource group and then checks that the <code>Tags</code> dictionary is not null and contains a tag with the name <code>Environment</code>. Predictably, the test fails:</p>
<pre tabindex="0"><code>$ dotnet test
...
  X ResourceGroupHasEnvironmentTag [240ms]
  Error Message:
   Expected tags not to be &lt;null&gt; because Tags must be defined.
...
Test Run Failed.
</code></pre><p>Now, I edit the stack class and change the resource group definition.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#cf222e">var</span> resourceGroup <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> ResourceGroup<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;www-prod-rg&#34;</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">new</span> ResourceGroupArgs
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    Tags <span style="color:#1f2328">=</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">{</span> <span style="color:#0a3069">&#34;Environment&#34;</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;production&#34;</span> <span style="color:#1f2328">}</span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>This change makes the test suite green again, and we are good to proceed.</p>
<h2 id="storage-account">Storage Account</h2>
<p>It&rsquo;s time to add a test for a second resource in the stack: a Storage Account. To make things more interesting, I want to check that the storage account belongs to our resource group.</p>
<p>There&rsquo;s no direct link between a storage account object and a resource group object. Instead, we need to check the <code>ResourceGroupName</code> property of the account. The test below expects the account to belong to a resource group called <code>www-prod-rg</code>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#1f2328">[Test]</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">public</span> <span style="color:#cf222e">async</span> Task StorageAccountBelongsToResourceGroup<span style="color:#1f2328">()</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> resources <span style="color:#1f2328">=</span> <span style="color:#cf222e">await</span> TestAsync<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> storageAccount <span style="color:#1f2328">=</span> resources<span style="color:#1f2328">.</span>OfType<span style="color:#1f2328">&lt;</span>Storage<span style="color:#1f2328">.</span>Account<span style="color:#1f2328">&gt;().</span>SingleOrDefault<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>    storageAccount<span style="color:#1f2328">.</span>Should<span style="color:#1f2328">().</span>NotBeNull<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;Storage account not found&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> resourceGroupName <span style="color:#1f2328">=</span> <span style="color:#cf222e">await</span> storageAccount<span style="color:#1f2328">.</span>ResourceGroupName<span style="color:#1f2328">.</span>GetValueAsync<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>    resourceGroupName<span style="color:#1f2328">.</span>Should<span style="color:#1f2328">().</span>Be<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;www-prod-rg&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>Of course, the test fails. I try to fix it with what I think is the minimal change to make the test pass by adding a new resource to the stack and pointing it to the resource group.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#cf222e">var</span> storageAccount <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> Storage<span style="color:#1f2328">.</span>Account<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;wwwprodsa&#34;</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">new</span> Storage<span style="color:#1f2328">.</span>AccountArgs
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    ResourceGroupName <span style="color:#1f2328">=</span> resourceGroup<span style="color:#1f2328">.</span>Name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>However, when I rerun the test suite, all three tests fail. They complain about the missing required properties <code>AccountReplicationType</code> and <code>AccountTier</code> on the storage account, so I have to add those.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#cf222e">var</span> storageAccount <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> Storage<span style="color:#1f2328">.</span>Account<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;wwwprodsa&#34;</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">new</span> Storage<span style="color:#1f2328">.</span>AccountArgs
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    ResourceGroupName <span style="color:#1f2328">=</span> resourceGroup<span style="color:#1f2328">.</span>Name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    AccountTier <span style="color:#1f2328">=</span> <span style="color:#0a3069">&#34;Standard&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    AccountReplicationType <span style="color:#1f2328">=</span> <span style="color:#0a3069">&#34;LRS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    StaticWebsite <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> Storage<span style="color:#1f2328">.</span>Inputs<span style="color:#1f2328">.</span>AccountStaticWebsiteArgs
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        IndexDocument <span style="color:#1f2328">=</span> <span style="color:#0a3069">&#34;index.html&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>I defined the <code>StaticWebsite</code> property as well: I&rsquo;ll leave testing these values as an exercise for the reader.</p>
<p>Two resource group tests are back to green again, but the storage account test fails with a new error message.</p>
<pre tabindex="0"><code>X StorageAccountBelongsToResourceGroup [84ms]
  Error Message:
   Expected resourceGroupName to be &#34;www-prod-rg&#34;, but found &lt;null&gt;.
</code></pre><p>What&rsquo;s going on, and why is it <code>null</code>?</p>
<p>I defined the account name like this: <code>ResourceGroupName = resourceGroup.Name</code>. However, if we look closely, the <code>resourceGroup</code> resource doesn&rsquo;t have an input property <code>Name</code> defined. <code>www-prod-rg</code> is a logical name for Pulumi deployment, not the physical name of the resource.</p>
<p>Under normal circumstances, the Pulumi engine would use the logical name to produce the physical name of the resource group automatically (see <a href="https://www.pulumi.com/docs/intro/concepts/programming-model/#names">resource names</a> for details). However, my mocks don&rsquo;t do that.</p>
<p>That&rsquo;s a good reason to change the <code>Mocks</code> implementation. I add the following lines to the <code>NewResourceAsync</code> method.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#57606a">// Set the name to resource name if it&#39;s not set explicitly in inputs.</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">if</span> <span style="color:#1f2328">(!</span>inputs<span style="color:#1f2328">.</span>ContainsKey<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;name&#34;</span><span style="color:#1f2328">))</span>
</span></span><span style="display:flex;"><span>    outputs<span style="color:#1f2328">.</span>Add<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;name&#34;</span><span style="color:#1f2328">,</span> name<span style="color:#1f2328">);</span>
</span></span></code></pre></div><p>Note that mocks operate on weakly-typed dictionaries, so I need to get the property name right. Pulumi SDKs are open source, so I looked <a href="https://github.com/pulumi/pulumi-azure/blob/1fdcab88065175ced768d900e7dcedf3b1d1b0a7/sdk/dotnet/Core/ResourceGroup.cs#L90">here</a> to double-check the exact value.</p>
<p>After this change in <code>Mocks</code>, my tests go green again.</p>
<pre tabindex="0"><code>Test Run Successful.
Total tests: 3
     Passed: 3
</code></pre><h2 id="website-files">Website Files</h2>
<p>The next step is to upload some files to the static website. Well, instead, to write an automated test that validates the upload with mocks.</p>
<p>I create a <code>wwwroot</code> folder with two HTML files in it and copy the folder to the build&rsquo;s output. Here is my change in the project file.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#0550ae">&lt;ItemGroup&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;None</span> <span style="color:#1f2328">Update=</span><span style="color:#0a3069">&#34;wwwroot\**\*.*&#34;</span><span style="color:#0550ae">&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&lt;CopyToOutputDirectory&gt;</span>PreserveNewest<span style="color:#0550ae">&lt;/CopyToOutputDirectory&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;/None&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#0550ae">&lt;/ItemGroup&gt;</span>
</span></span></code></pre></div><p>Now, the test is straightforward: it expects two <code>Blob</code> resources in the stack.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#1f2328">[Test]</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">public</span> <span style="color:#cf222e">async</span> Task UploadsTwoFiles<span style="color:#1f2328">()</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> resources <span style="color:#1f2328">=</span> <span style="color:#cf222e">await</span> TestAsync<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> files <span style="color:#1f2328">=</span> resources<span style="color:#1f2328">.</span>OfType<span style="color:#1f2328">&lt;</span>Storage<span style="color:#1f2328">.</span>Blob<span style="color:#1f2328">&gt;().</span>ToList<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>    files<span style="color:#1f2328">.</span>Count<span style="color:#1f2328">.</span>Should<span style="color:#1f2328">().</span>Be<span style="color:#1f2328">(</span><span style="color:#0550ae">2</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;Should have uploaded files from `wwwroot`&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>As intended, the test fails immediately.</p>
<pre tabindex="0"><code>X UploadsTwoFiles [95ms]
  Error Message:
   Expected files.Count to be 2 because Should have uploaded files from `wwwroot`, but found 0.
</code></pre><p>I extend my stack with the following loop that navigates through the files and creates a storage blob for each of them.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#cf222e">var</span> files <span style="color:#1f2328">=</span> Directory<span style="color:#1f2328">.</span>GetFiles<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;wwwroot&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">foreach</span> <span style="color:#1f2328">(</span><span style="color:#cf222e">var</span> file <span style="color:#cf222e">in</span> files<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> blob <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> Storage<span style="color:#1f2328">.</span>Blob<span style="color:#1f2328">(</span>file<span style="color:#1f2328">,</span> <span style="color:#cf222e">new</span> Storage<span style="color:#1f2328">.</span>BlobArgs
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        ContentType <span style="color:#1f2328">=</span> <span style="color:#0a3069">&#34;application/html&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        Source <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> FileAsset<span style="color:#1f2328">(</span>file<span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>        StorageAccountName <span style="color:#1f2328">=</span> storageAccount<span style="color:#1f2328">.</span>Name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        StorageContainerName <span style="color:#1f2328">=</span> <span style="color:#0a3069">&#34;$web&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        Type <span style="color:#1f2328">=</span> <span style="color:#0a3069">&#34;Block&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>However, this change breaks the stack and all tests in the suite:</p>
<pre tabindex="0"><code>Error Message:
   Pulumi.RunException : Running program failed with an unhandled exception:
System.InvalidOperationException: Unsupported value when converting to protobuf: Pulumi.FileAsset
   at Pulumi.Serialization.Serializer.CreateValue(Object value)
...
</code></pre><p>Once again, our mocks fail to represent the behavior of the engine accurately. The Pulumi engine knows about the <code>FileAsset</code> class pointing to a file on the disk and how to convert it to an uploaded blob. But, the engine doesn&rsquo;t copy this property to outputs. I need to adjust the mocks again.</p>
<p>I&rsquo;m not particularly interested in testing the binary contents of the files now, so I&rsquo;ll change the <code>Mocks</code> class to ignore the <code>source</code> property and not to include it into the output dictionary.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#cf222e">if</span> <span style="color:#1f2328">(</span>type <span style="color:#1f2328">==</span> <span style="color:#0a3069">&#34;azure:storage/blob:Blob&#34;</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">// Assets can&#39;t directly go through the engine.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">// We don&#39;t need them in the test, so blank out the property for now.</span>
</span></span><span style="display:flex;"><span>    outputs<span style="color:#1f2328">.</span>Remove<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;source&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>This change makes my test suite happy again.</p>
<pre tabindex="0"><code>Test Run Successful.
Total tests: 4
     Passed: 4
</code></pre><h2 id="validate-website-url">Validate Website URL</h2>
<p>So far, I&rsquo;ve been validating the input parameters of resources. What if I want to test something that is usually done by the cloud provider?</p>
<p>For instance, when Azure creates a static website, it automatically assigns a public endpoint. The endpoint is then available in the <code>PrimaryWebEndpoint</code> property after the Pulumi program ran and resources are created. I may want to export this value from stack outputs and validate it in a unit test.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#1f2328">[Test]</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">public</span> <span style="color:#cf222e">async</span> Task StackExportsWebsiteUrl<span style="color:#1f2328">()</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> resources <span style="color:#1f2328">=</span> <span style="color:#cf222e">await</span> TestAsync<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> stack <span style="color:#1f2328">=</span> resources<span style="color:#1f2328">.</span>OfType<span style="color:#1f2328">&lt;</span>WebsiteStack<span style="color:#1f2328">&gt;().</span>First<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> endpoint <span style="color:#1f2328">=</span> <span style="color:#cf222e">await</span> stack<span style="color:#1f2328">.</span>Endpoint<span style="color:#1f2328">.</span>GetValueAsync<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>    endpoint<span style="color:#1f2328">.</span>Should<span style="color:#1f2328">().</span>Be<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;https://wwwprodsa.web.core.windows.net&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>This test doesn&rsquo;t even compile yet, so I have to define the <code>Endpoint</code> property and assign a value to it. Here is the complete implementation of the stack with the output property defined at the end.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#cf222e">public</span> <span style="color:#cf222e">class</span> <span style="color:#1f2328">WebsiteStack</span> <span style="color:#1f2328">:</span> Stack
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">public</span> WebsiteStack<span style="color:#1f2328">()</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">var</span> resourceGroup <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> ResourceGroup<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;www-prod-rg&#34;</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">new</span> ResourceGroupArgs
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            Tags <span style="color:#1f2328">=</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">{</span> <span style="color:#0a3069">&#34;Environment&#34;</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;production&#34;</span> <span style="color:#1f2328">}</span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">var</span> storageAccount <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> Storage<span style="color:#1f2328">.</span>Account<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;wwwprodsa&#34;</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">new</span> Storage<span style="color:#1f2328">.</span>AccountArgs
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            ResourceGroupName <span style="color:#1f2328">=</span> resourceGroup<span style="color:#1f2328">.</span>Name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            AccountTier <span style="color:#1f2328">=</span> <span style="color:#0a3069">&#34;Standard&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            AccountReplicationType <span style="color:#1f2328">=</span> <span style="color:#0a3069">&#34;LRS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            StaticWebsite <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> Storage<span style="color:#1f2328">.</span>Inputs<span style="color:#1f2328">.</span>AccountStaticWebsiteArgs
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                IndexDocument <span style="color:#1f2328">=</span> <span style="color:#0a3069">&#34;index.html&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">var</span> files <span style="color:#1f2328">=</span> Directory<span style="color:#1f2328">.</span>GetFiles<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;wwwroot&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">foreach</span> <span style="color:#1f2328">(</span><span style="color:#cf222e">var</span> file <span style="color:#cf222e">in</span> files<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#cf222e">var</span> blob <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> Storage<span style="color:#1f2328">.</span>Blob<span style="color:#1f2328">(</span>file<span style="color:#1f2328">,</span> <span style="color:#cf222e">new</span> Storage<span style="color:#1f2328">.</span>BlobArgs
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                ContentType <span style="color:#1f2328">=</span> <span style="color:#0a3069">&#34;application/html&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                Source <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> FileAsset<span style="color:#1f2328">(</span>file<span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>                StorageAccountName <span style="color:#1f2328">=</span> storageAccount<span style="color:#1f2328">.</span>Name<span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                StorageContainerName <span style="color:#1f2328">=</span> <span style="color:#0a3069">&#34;$web&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                Type <span style="color:#1f2328">=</span> <span style="color:#0a3069">&#34;Block&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">this</span><span style="color:#1f2328">.</span>Endpoint <span style="color:#1f2328">=</span> storageAccount<span style="color:#1f2328">.</span>PrimaryWebEndpoint<span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328">    [Output]</span> <span style="color:#cf222e">public</span> Output<span style="color:#1f2328">&lt;</span><span style="color:#cf222e">string</span><span style="color:#1f2328">&gt;</span> Endpoint <span style="color:#1f2328">{</span> <span style="color:#cf222e">get</span><span style="color:#1f2328">;</span> <span style="color:#cf222e">set</span><span style="color:#1f2328">;</span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>Now, the test compiles but fails when executed:</p>
<pre tabindex="0"><code>X StackExportsWebsiteUrl [85ms]
  Error Message:
   Expected endpoint to be &#34;https://wwwprodsa.web.core.windows.net&#34;, but found &lt;null&gt;.
</code></pre><p>That&rsquo;s because there is no call to Azure to populate the endpoint. It&rsquo;s time to make the last change to my <code>Mocks</code> class to emulate that assignment.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#57606a">// For a Storage Account...</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">if</span> <span style="color:#1f2328">(</span>type <span style="color:#1f2328">==</span> <span style="color:#0a3069">&#34;azure:storage/account:Account&#34;</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">// ... set its web endpoint property.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">// Normally this would be calculated by Azure, so we have to mock it.</span>
</span></span><span style="display:flex;"><span>    outputs<span style="color:#1f2328">.</span>Add<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;primaryWebEndpoint&#34;</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">$&#34;https://{name}.web.core.windows.net&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>And that&rsquo;s it! My static website is ready and tested!</p>
<pre tabindex="0"><code>Test Run Successful.
Total tests: 5
     Passed: 5
 Total time: 0.9338 Seconds
</code></pre><p>Note that it still takes less than a second to run my test suite so that I can iterate very quickly.</p>
<h2 id="get-started">Get Started</h2>
<p>The tests above cover the basics of unit testing with Pulumi .NET SDK. You can take it from here and apply the techniques and practices that you use while testing the application code. You may also try more advanced practices like property-based testing or behavior-driven development—we believe that the mocks enable many testing styles.</p>
<p>Here are several useful pointers to get started with testing in Pulumi:</p>
<ul>
<li><a href="https://www.pulumi.com/docs/guides/testing/">Testing Guide</a>.</li>
<li><a href="https://github.com/pulumi/examples/tree/72c9480f4c1240f795f6020f50801733fbef37f2/testing-unit-cs-mocks">Full code for this blog post</a>.</li>
<li><a href="https://github.com/pulumi/examples/tree/de060e659e1bb4af15d895fe4de7a3f10218b669/testing-unit-cs">Another example of unit testing in C#</a></li>
</ul>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                             
                                <category scheme="https://mikhail.io/tags/unit-testing" term="unit-testing" label="Unit Testing" />
                             
                                <category scheme="https://mikhail.io/tags/tdd" term="tdd" label="TDD" />
                             
                                <category scheme="https://mikhail.io/tags/csharp" term="csharp" label="CSharp" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Unit Testing Cloud Deployments with Pulumi in F#]]></title>
            <link href="https://mikhail.io/2020/05/unit-testing-cloud-deployments-with-pulumi-in-fsharp/"/>
            <id>https://mikhail.io/2020/05/unit-testing-cloud-deployments-with-pulumi-in-fsharp/</id>
            
            <published>2020-05-21T00:00:00+00:00</published>
            <updated>2020-05-21T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Developing infrastructure programs in F# with unit tests, TDD, and mocks</blockquote><p>Because Pulumi uses general-purpose programming languages to provision cloud resources, you can take advantage of native tools and perform automated tests of your infrastructure. The full power of each language is available, including access to libraries and frameworks for testing.</p>
<p>This blog post takes a deeper dive into mock-based unit testing of Pulumi programs written in F#. You can find a C# version of this blog post <a href="/2020/05/unit-testing-cloud-deployments-with-pulumi-in-csharp/">here</a>.</p>
<h2 id="how-unit-testing-works-in-pulumi">How Unit Testing Works in Pulumi</h2>
<p>Let&rsquo;s start with a picture showing how Pulumi components interact during a typical deployment process.</p>
<p><img src="./engine.png" alt="Pulumi Components Interaction"></p>
<p>Whenever a new resource is instantiated in code, the program makes a remote call to the engine. The engine receives the resource&rsquo;s input values, validates them, and translates the request to an invocation of a cloud API. The API returns some data, which are translated to resource outputs and sent back to the program.</p>
<p>The remote calls may be slow, unreliable, and non-deterministic, which makes testing of these interactions hard.</p>
<p>Our <a href="https://www.pulumi.com/docs/guides/testing/">testing guide</a> outlines several testing methods, but today I want to focus on unit testing.</p>
<p>The Pulumi SDK provides a hook to replace all the remote calls with mocks. Mocks run in the same process and can respond immediately with hard-coded or calculated on-the-fly data.</p>
<p><img src="./mocks.png" alt="Pulumi with Mocks"></p>
<p>We can write fully deterministic and blazingly fast automated tests as all remote calls and uncertainty are eliminated. There is no cloud to respond to resource creation, so it&rsquo;s a developer&rsquo;s responsibility to mimic the cloud behavior with mocks adequately.</p>
<p>This blog post walks you through an example of unit testing and mocking with the .NET SDK.</p>
<h2 id="define-the-base-structure">Define the Base Structure</h2>
<p>In this article, I build a program that deploys a static website to Azure. I use TDD to add new tests and deployment components bit-by-bit.</p>
<p>Let&rsquo;s start with an empty project—go ahead and create a .NET Core Console Application with the .NET CLI or your favorite IDE.</p>
<h3 id="install-nuget-packages">Install NuGet packages</h3>
<p>You are free to choose your unit testing frameworks, mocking and assertions libraries. I&rsquo;m using NUnit with FluentAssertions, and my program tests Azure resources, so this is my project file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#0550ae">&lt;Project</span> <span style="color:#1f2328">Sdk=</span><span style="color:#0a3069">&#34;Microsoft.NET.Sdk&#34;</span><span style="color:#0550ae">&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;PropertyGroup&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&lt;OutputType&gt;</span>Exe<span style="color:#0550ae">&lt;/OutputType&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&lt;TargetFramework&gt;</span>netcoreapp3.1<span style="color:#0550ae">&lt;/TargetFramework&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&lt;Nullable&gt;</span>enable<span style="color:#0550ae">&lt;/Nullable&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;/PropertyGroup&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;ItemGroup&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&lt;PackageReference</span> <span style="color:#1f2328">Include=</span><span style="color:#0a3069">&#34;FluentAssertions&#34;</span> <span style="color:#1f2328">Version=</span><span style="color:#0a3069">&#34;5.10.2&#34;</span> <span style="color:#0550ae">/&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&lt;PackageReference</span> <span style="color:#1f2328">Include=</span><span style="color:#0a3069">&#34;Microsoft.NET.Test.Sdk&#34;</span> <span style="color:#1f2328">Version=</span><span style="color:#0a3069">&#34;16.5.0&#34;</span> <span style="color:#0550ae">/&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&lt;PackageReference</span> <span style="color:#1f2328">Include=</span><span style="color:#0a3069">&#34;NUnit&#34;</span> <span style="color:#1f2328">Version=</span><span style="color:#0a3069">&#34;3.12.0&#34;</span> <span style="color:#0550ae">/&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&lt;PackageReference</span> <span style="color:#1f2328">Include=</span><span style="color:#0a3069">&#34;NUnit3TestAdapter&#34;</span> <span style="color:#1f2328">Version=</span><span style="color:#0a3069">&#34;3.16.1&#34;</span> <span style="color:#0550ae">/&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&lt;PackageReference</span> <span style="color:#1f2328">Include=</span><span style="color:#0a3069">&#34;Pulumi.Azure&#34;</span> <span style="color:#1f2328">Version=</span><span style="color:#0a3069">&#34;3.*&#34;</span> <span style="color:#0550ae">/&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&lt;PackageReference</span> <span style="color:#1f2328">Include=</span><span style="color:#0a3069">&#34;Pulumi.FSharp&#34;</span> <span style="color:#1f2328">Version=</span><span style="color:#0a3069">&#34;2.*&#34;</span> <span style="color:#0550ae">/&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;/ItemGroup&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;ItemGroup&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&lt;Compile</span> <span style="color:#1f2328">Include=</span><span style="color:#0a3069">&#34;Testing.fs&#34;</span> <span style="color:#0550ae">/&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&lt;Compile</span> <span style="color:#1f2328">Include=</span><span style="color:#0a3069">&#34;WebsiteStack.fs&#34;</span> <span style="color:#0550ae">/&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&lt;Compile</span> <span style="color:#1f2328">Include=</span><span style="color:#0a3069">&#34;WebsiteStackTests.fs&#34;</span> <span style="color:#0550ae">/&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;/ItemGroup&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#0550ae">&lt;/Project&gt;</span>
</span></span></code></pre></div><h3 id="stack">Stack</h3>
<p>A Pulumi stack is the &ldquo;unit&rdquo; of our testing. Every test instantiates a stack, retrieves the resources that the stack defines, and makes assertions about them.</p>
<p>Here is my starting point in <code>WebsiteStack.fs</code> file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">namespace</span> UnitTesting
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">Pulumi</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">Pulumi.FSharp</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">Pulumi.Azure.Core</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">type</span> <span style="color:#1f2328">WebsiteStack</span><span style="color:#6a737d">()</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">inherit</span> Stack<span style="color:#6a737d">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">// &lt;-- Cloud resources go here
</span></span></span></code></pre></div><p>This class is precisely what Pulumi expects to start deploying your resources to the cloud. Once the test suite is ready and green, you can deploy the stack with <code>pulumi up</code>.</p>
<h3 id="mocks">Mocks</h3>
<p>As I explained above, unit tests replace the real Pulumi engine with mocks. Mocks get all calls and respond with predefined values.</p>
<p>A mock class has to implement two methods of <code>Pulumi.Testing.IMocks</code> interface. <code>NewResourceAsync</code> is called whenever a new resource is defined, while <code>CallAsync</code> is invoked when our program retrieves information about existing cloud resources. We&rsquo;ll focus on the former in this post, but we still need to define both.</p>
<p>While you are free to use your favorite mocking library, I&rsquo;ll keep it simple and define the mocks as a plain class.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">namespace</span> UnitTesting
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">System</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">System.Collections.Immutable</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">System.Threading.Tasks</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">Pulumi.Testing</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">type</span> <span style="color:#1f2328">Mocks</span><span style="color:#6a737d">()</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">interface</span> IMocks <span style="color:#cf222e">with</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">member</span> this<span style="color:#1f2328">.</span><span style="color:#6639ba">NewResourceAsync</span><span style="color:#0550ae">(</span>typeName<span style="color:#0550ae">:</span> <span style="color:#cf222e">string</span><span style="color:#0550ae">,</span> name<span style="color:#0550ae">:</span> <span style="color:#cf222e">string</span><span style="color:#0550ae">,</span> inputs<span style="color:#0550ae">:</span> ImmutableDictionary<span style="color:#0550ae">&lt;</span><span style="color:#cf222e">string</span><span style="color:#0550ae">,</span> <span style="color:#cf222e">obj</span><span style="color:#0550ae">&gt;,</span> provider<span style="color:#0550ae">:</span> <span style="color:#cf222e">string</span><span style="color:#0550ae">,</span> id<span style="color:#0550ae">:</span> <span style="color:#cf222e">string</span><span style="color:#0550ae">):</span> Task<span style="color:#0550ae">&lt;</span>ValueTuple<span style="color:#0550ae">&lt;</span><span style="color:#cf222e">string</span><span style="color:#0550ae">,</span><span style="color:#cf222e">obj</span><span style="color:#0550ae">&gt;&gt;</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>            <span style="color:#57606a">// Forward all input parameters as resource outputs, so that we could test them.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>            <span style="color:#cf222e">let</span> <span style="color:#953800">dict</span> <span style="color:#0550ae">=</span> inputs<span style="color:#0550ae">.</span>ToImmutableDictionary<span style="color:#6a737d">()</span> <span style="color:#0550ae">:&gt;</span> <span style="color:#cf222e">obj</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#57606a">// &lt;-- We&#39;ll customize the mocks here
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>
</span></span><span style="display:flex;"><span>            <span style="color:#57606a">// Default the resource ID to `{name}_id`.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>            <span style="color:#cf222e">let</span> <span style="color:#953800">id</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">if</span> id <span style="color:#0550ae">=</span> <span style="color:#cf222e">null</span> <span style="color:#cf222e">then</span> sprintf <span style="color:#0a3069">&#34;%s_id&#34;</span> name <span style="color:#cf222e">else</span> id
</span></span><span style="display:flex;"><span>            <span style="color:#24292e">Task</span><span style="color:#1f2328">.</span>FromResult<span style="color:#0550ae">(</span><span style="color:#cf222e">struct</span> <span style="color:#0550ae">(</span>id<span style="color:#0550ae">,</span> outputs <span style="color:#0550ae">:&gt;</span> <span style="color:#cf222e">obj</span><span style="color:#0550ae">))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">member</span> this<span style="color:#1f2328">.</span><span style="color:#6639ba">CallAsync</span><span style="color:#0550ae">(</span>token<span style="color:#0550ae">:</span> <span style="color:#cf222e">string</span><span style="color:#0550ae">,</span> inputs<span style="color:#0550ae">:</span> ImmutableDictionary<span style="color:#0550ae">&lt;</span><span style="color:#cf222e">string</span><span style="color:#0550ae">,</span> <span style="color:#cf222e">obj</span><span style="color:#0550ae">&gt;,</span> provider<span style="color:#0550ae">:</span> <span style="color:#cf222e">string</span><span style="color:#0550ae">):</span> Task<span style="color:#0550ae">&lt;</span><span style="color:#cf222e">obj</span><span style="color:#0550ae">&gt;</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>            <span style="color:#57606a">// We don&#39;t use this method in this particular test suite.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>            <span style="color:#57606a">// Default to returning whatever we got as input.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>            <span style="color:#24292e">Task</span><span style="color:#1f2328">.</span>FromResult<span style="color:#0550ae">(</span><span style="color:#cf222e">null</span> <span style="color:#0550ae">:&gt;</span> <span style="color:#cf222e">obj</span><span style="color:#0550ae">)</span>
</span></span></code></pre></div><p>Both methods receive several parameters and need to return a result. Notably, they get the list of <code>inputs</code>—the arguments that our program defined for the given resource. The goal of the mocks is to return the list of outputs, similarly to what a cloud provider would do.</p>
<p>My default implementation returns the outputs that include all the inputs and nothing else. That&rsquo;s a sensible default: usually, real outputs are a superset of inputs. We&rsquo;ll extend this behavior as we need later on.</p>
<h3 id="test-fixture">Test Fixture</h3>
<p>The next class to add is the container for my future unit tests. I named the file <code>WebsiteStackTests.fs</code> and it defines a test container, called a &ldquo;test fixture&rdquo; in NUnit:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">namespace</span> UnitTesting
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">System</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">System.Collections.Immutable</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">System.Threading.Tasks</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">NUnit.Framework</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">FluentAssertions</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">Pulumi</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">Pulumi.Azure.Core</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">Pulumi.Azure.Storage</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">open</span> <span style="color:#24292e">Pulumi.Testing</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#0550ae">[&lt;</span>TestFixture<span style="color:#0550ae">&gt;]</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">type</span> <span style="color:#1f2328">WebserverStackTests</span><span style="color:#6a737d">()</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">runTest</span><span style="color:#6a737d">()</span><span style="color:#0550ae">:</span> ImmutableArray<span style="color:#0550ae">&lt;</span>Resource<span style="color:#0550ae">&gt;</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">let</span> <span style="color:#953800">options</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> TestOptions<span style="color:#0550ae">(</span>IsPreview <span style="color:#0550ae">=</span> Nullable<span style="color:#0550ae">&lt;</span><span style="color:#cf222e">bool</span><span style="color:#0550ae">&gt;</span> <span style="color:#cf222e">false</span><span style="color:#0550ae">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#24292e">Deployment</span><span style="color:#1f2328">.</span>TestAsync<span style="color:#0550ae">&lt;</span>WebsiteStack<span style="color:#0550ae">&gt;(</span><span style="color:#cf222e">new</span> Mocks<span style="color:#6a737d">()</span><span style="color:#0550ae">,</span> options<span style="color:#0550ae">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Async</span><span style="color:#1f2328">.</span>AwaitTask
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Async</span><span style="color:#1f2328">.</span>RunSynchronously
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">// &lt;-- Tests go here
</span></span></span></code></pre></div><p>I defined a helper method <code>TestAsync</code> that points Pulumi&rsquo;s <code>Deployment.TestAsync</code> to our stack and mock classes.</p>
<p>As we progress through the article, I will add tests to this class one-by-one.</p>
<h3 id="retrieve-output-values">Retrieve output values</h3>
<p>Finally, I need a small extension method to extract values from outputs. Every Pulumi resource returns values wrapped inside an <code>Output&lt;T&gt;</code> container. While the real engine runs, those values may be known or unknown, but my mock-based tests always return known values. It&rsquo;s safe to get those values and convert them to a <code>Task&lt;T&gt;</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">getValue</span><span style="color:#0550ae">(</span>output<span style="color:#0550ae">:</span> Output<span style="color:#0550ae">&lt;</span><span style="color:#cf222e">&#39;</span>a<span style="color:#0550ae">&gt;):</span> <span style="color:#cf222e">&#39;</span>a <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">tcs</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> TaskCompletionSource<span style="color:#0550ae">&lt;</span><span style="color:#cf222e">&#39;</span>a<span style="color:#0550ae">&gt;</span><span style="color:#6a737d">()</span>
</span></span><span style="display:flex;"><span>    output<span style="color:#0550ae">.</span>Apply<span style="color:#0550ae">(</span><span style="color:#cf222e">fun</span> v <span style="color:#0550ae">-&gt;</span> tcs<span style="color:#0550ae">.</span>SetResult<span style="color:#0550ae">(</span>v<span style="color:#0550ae">);</span> v<span style="color:#0550ae">)</span> <span style="color:#0550ae">|&gt;</span> ignore
</span></span><span style="display:flex;"><span>    tcs<span style="color:#0550ae">.</span>Task<span style="color:#0550ae">.</span>Result
</span></span></code></pre></div><p>To learn more about outputs, read <a href="https://www.pulumi.com/docs/intro/concepts/programming-model/#stack-outputs">this article</a>.</p>
<h2 id="first-test">First Test</h2>
<p>The setup is done, time to write my first unit test! True to the TDD spirit, I write the tests before I define any resources.</p>
<p>Every Azure resource has to live inside a resource group, so my first test checks that a new resource group is defined.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#0550ae">[&lt;</span>Test<span style="color:#0550ae">&gt;]</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">member</span> this<span style="color:#1f2328">.</span><span style="color:#6639ba">SingleResourceGroupExists</span><span style="color:#6a737d">()</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">resources</span> <span style="color:#0550ae">=</span> runTest<span style="color:#6a737d">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">resourceGroupCount</span> <span style="color:#0550ae">=</span> resources<span style="color:#0550ae">.</span>OfType<span style="color:#0550ae">&lt;</span>ResourceGroup<span style="color:#0550ae">&gt;</span><span style="color:#6a737d">()</span> <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Seq</span><span style="color:#1f2328">.</span>length
</span></span><span style="display:flex;"><span>    resourceGroupCount<span style="color:#0550ae">.</span>Should<span style="color:#6a737d">()</span><span style="color:#0550ae">.</span>Be<span style="color:#0550ae">(</span>1<span style="color:#0550ae">,</span> <span style="color:#0a3069">&#34;a single resource group is expected&#34;</span><span style="color:#0550ae">)</span> <span style="color:#0550ae">|&gt;</span> ignore
</span></span></code></pre></div><p>This code is great to illustrate the overall structure of each test:</p>
<ol>
<li>Call the <code>TestAsync</code> method to evaluate the stack.</li>
<li>Find the target resource in the collection of created resources.</li>
<li>Assert a property of the target resource.</li>
</ol>
<p>In this case, the test validates that there is one and only one resource group defined.</p>
<p>I can run <code>dotnet test</code> and see the expected test failure:</p>
<pre tabindex="0"><code>$ dotnet test
...

X SingleResourceGroupExists [238ms]
  Error Message:
   Expected resourceGroups.Count to be 1 because a single resource group is expected, but found 0.
...
Total tests: 1
     Failed: 1
 Total time: 0.9270 Seconds
</code></pre><p>I go ahead and add the following definition to the <code>WebsiteStack</code> constructor.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">resourceGroup</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> ResourceGroup<span style="color:#0550ae">(</span><span style="color:#0a3069">&#34;www-prod-rg&#34;</span><span style="color:#0550ae">)</span>
</span></span></code></pre></div><p>This change is enough to make the tests green!</p>
<pre tabindex="0"><code>$ dotnet test
...
Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 0.8462 Seconds
</code></pre><h2 id="tags">Tags</h2>
<p>For billing and lifecycle management, I want all my resource groups to be tagged. Therefore, my second test validates that the resource group has an <code>Environment</code> tag on it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#0550ae">[&lt;</span>Test<span style="color:#0550ae">&gt;]</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">member</span> this<span style="color:#1f2328">.</span><span style="color:#6639ba">ResourceGroupHasEnvironmentTag</span><span style="color:#6a737d">()</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">resources</span> <span style="color:#0550ae">=</span> runTest<span style="color:#6a737d">()</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">resourceGroup</span> <span style="color:#0550ae">=</span> resources<span style="color:#0550ae">.</span>OfType<span style="color:#0550ae">&lt;</span>ResourceGroup<span style="color:#0550ae">&gt;</span><span style="color:#6a737d">()</span> <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Seq</span><span style="color:#1f2328">.</span>head
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">tags</span> <span style="color:#0550ae">=</span> getValue resourceGroup<span style="color:#0550ae">.</span>Tags
</span></span><span style="display:flex;"><span>    tags<span style="color:#0550ae">.</span>Should<span style="color:#6a737d">()</span><span style="color:#0550ae">.</span>NotBeNull<span style="color:#0550ae">(</span><span style="color:#0a3069">&#34;Tags must be defined&#34;</span><span style="color:#0550ae">)</span> <span style="color:#0550ae">|&gt;</span> ignore
</span></span><span style="display:flex;"><span>    tags<span style="color:#0550ae">.</span>Should<span style="color:#6a737d">()</span><span style="color:#0550ae">.</span>ContainKey<span style="color:#0550ae">(</span><span style="color:#0a3069">&#34;Environment&#34;</span><span style="color:#0550ae">,</span> <span style="color:#cf222e">null</span><span style="color:#0550ae">)</span> <span style="color:#0550ae">|&gt;</span> ignore
</span></span></code></pre></div><p>The test finds the resource group and then checks that the <code>Tags</code> dictionary is not null and contains a tag with the name <code>Environment</code>. Predictably, the test fails:</p>
<pre tabindex="0"><code>$ dotnet test
...
  X ResourceGroupHasEnvironmentTag [240ms]
  Error Message:
   Expected tags not to be &lt;null&gt; because Tags must be defined.
...
Test Run Failed.
</code></pre><p>Now, I edit the stack class and change the resource group definition.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">resourceGroup</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">new</span> ResourceGroup<span style="color:#0550ae">(</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0a3069">&#34;www-prod-rg&#34;</span><span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">new</span> ResourceGroupArgs<span style="color:#0550ae">(</span>Tags <span style="color:#0550ae">=</span> inputMap<span style="color:#0550ae">([</span><span style="color:#0a3069">&#34;Environment&#34;</span><span style="color:#0550ae">,</span> input <span style="color:#0a3069">&#34;production&#34;</span><span style="color:#0550ae">])))</span>
</span></span></code></pre></div><p>This change makes the test suite green again, and we are good to proceed.</p>
<h2 id="storage-account">Storage Account</h2>
<p>It&rsquo;s time to add a test for a second resource in the stack: a Storage Account. To make things more interesting, I want to check that the storage account belongs to our resource group.</p>
<p>There&rsquo;s no direct link between a storage account object and a resource group object. Instead, we need to check the <code>ResourceGroupName</code> property of the account. The test below expects the account to belong to a resource group called <code>www-prod-rg</code>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#0550ae">[&lt;</span>Test<span style="color:#0550ae">&gt;]</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">member</span> this<span style="color:#1f2328">.</span><span style="color:#6639ba">StorageAccountBelongsToResourceGroup</span><span style="color:#6a737d">()</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">resources</span> <span style="color:#0550ae">=</span> runTest<span style="color:#6a737d">()</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">storageAccount</span> <span style="color:#0550ae">=</span> resources<span style="color:#0550ae">.</span>OfType<span style="color:#0550ae">&lt;</span>Account<span style="color:#0550ae">&gt;</span><span style="color:#6a737d">()</span> <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Seq</span><span style="color:#1f2328">.</span>tryHead <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Option</span><span style="color:#1f2328">.</span>toObj
</span></span><span style="display:flex;"><span>    storageAccount<span style="color:#0550ae">.</span>Should<span style="color:#6a737d">()</span><span style="color:#0550ae">.</span>NotBeNull<span style="color:#0550ae">(</span><span style="color:#0a3069">&#34;Storage account not found&#34;</span><span style="color:#0550ae">)</span> <span style="color:#0550ae">|&gt;</span> ignore
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">resourceGroupName</span> <span style="color:#0550ae">=</span> getValue storageAccount<span style="color:#0550ae">.</span>ResourceGroupName
</span></span><span style="display:flex;"><span>    resourceGroupName<span style="color:#0550ae">.</span>Should<span style="color:#6a737d">()</span><span style="color:#0550ae">.</span>Be<span style="color:#0550ae">(</span><span style="color:#0a3069">&#34;www-prod-rg&#34;</span><span style="color:#0550ae">,</span> <span style="color:#cf222e">null</span><span style="color:#0550ae">)</span> <span style="color:#0550ae">|&gt;</span> ignore
</span></span></code></pre></div><p>Of course, the test fails. I try to fix it with what I think is the minimal change to make the test pass by adding a new resource to the stack and pointing it to the resource group.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">storageAccount</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">new</span> Account<span style="color:#0550ae">(</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0a3069">&#34;wwwprodsa&#34;</span><span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">new</span> AccountArgs<span style="color:#0550ae">(</span>ResourceGroupName <span style="color:#0550ae">=</span> io resourceGroup<span style="color:#0550ae">.</span>Name<span style="color:#0550ae">))</span>
</span></span></code></pre></div><p>However, when I rerun the test suite, all three tests fail. They complain about the missing required properties <code>AccountReplicationType</code> and <code>AccountTier</code> on the storage account, so I have to add those.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">storageAccount</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">new</span> Account<span style="color:#0550ae">(</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0a3069">&#34;wwwprodsa&#34;</span><span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">new</span> AccountArgs<span style="color:#0550ae">(</span>
</span></span><span style="display:flex;"><span>            ResourceGroupName <span style="color:#0550ae">=</span> io resourceGroup<span style="color:#0550ae">.</span>Name<span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>            AccountTier <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;Standard&#34;</span><span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>            AccountReplicationType <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;LRS&#34;</span><span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>            StaticWebsite <span style="color:#0550ae">=</span> input <span style="color:#0550ae">(</span><span style="color:#cf222e">new</span> AccountStaticWebsiteArgs<span style="color:#0550ae">(</span>IndexDocument <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;index.html&#34;</span><span style="color:#0550ae">))))</span>
</span></span></code></pre></div><p>I defined the <code>StaticWebsite</code> property as well: I&rsquo;ll leave testing these values as an exercise for the reader.</p>
<p>Two resource group tests are back to green again, but the storage account test fails with a new error message.</p>
<pre tabindex="0"><code>X StorageAccountBelongsToResourceGroup [84ms]
  Error Message:
   Expected resourceGroupName to be &#34;www-prod-rg&#34;, but found &lt;null&gt;.
</code></pre><p>What&rsquo;s going on, and why is it <code>null</code>?</p>
<p>I defined the account name like this: <code>ResourceGroupName = resourceGroup.Name</code>. However, if we look closely, the <code>resourceGroup</code> resource doesn&rsquo;t have an input property <code>Name</code> defined. <code>www-prod-rg</code> is a logical name for Pulumi deployment, not the physical name of the resource.</p>
<p>Under normal circumstances, the Pulumi engine would use the logical name to produce the physical name of the resource group automatically (see <a href="https://www.pulumi.com/docs/intro/concepts/programming-model/#names">resource names</a> for details). However, my mocks don&rsquo;t do that.</p>
<p>That&rsquo;s a good reason to change the <code>Mocks</code> implementation. I add the following lines to the <code>NewResourceAsync</code> method.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">inputKVs</span> <span style="color:#0550ae">=</span> inputs <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Seq</span><span style="color:#1f2328">.</span>map<span style="color:#0550ae">(</span><span style="color:#cf222e">fun</span> kv <span style="color:#0550ae">-&gt;</span> kv<span style="color:#0550ae">.</span>Key<span style="color:#0550ae">,</span> kv<span style="color:#0550ae">.</span>Value<span style="color:#0550ae">)</span> <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Seq</span><span style="color:#1f2328">.</span>toList
</span></span><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">nameKVs</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">if</span> inputs<span style="color:#0550ae">.</span>ContainsKey<span style="color:#0550ae">(</span><span style="color:#0a3069">&#34;name&#34;</span><span style="color:#0550ae">)</span> <span style="color:#cf222e">then</span> <span style="color:#6a737d">[]</span> <span style="color:#cf222e">else</span> <span style="color:#0550ae">[(</span><span style="color:#0a3069">&#34;name&#34;</span><span style="color:#0550ae">,</span> name <span style="color:#0550ae">:&gt;</span> <span style="color:#cf222e">obj</span><span style="color:#0550ae">)]</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">outputs</span> <span style="color:#0550ae">=</span> <span style="color:#0550ae">[</span>inputKVs<span style="color:#0550ae">;</span> nameKVs<span style="color:#0550ae">]</span> <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Seq</span><span style="color:#1f2328">.</span>concat <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Seq</span><span style="color:#1f2328">.</span>map KeyValuePair
</span></span><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">dict</span> <span style="color:#0550ae">=</span> outputs<span style="color:#0550ae">.</span>ToImmutableDictionary<span style="color:#6a737d">()</span> <span style="color:#0550ae">:&gt;</span> <span style="color:#cf222e">obj</span>
</span></span></code></pre></div><p>Note that mocks operate on weakly-typed dictionaries, so I need to get the property name right. Pulumi SDKs are open source, so I looked <a href="https://github.com/pulumi/pulumi-azure/blob/1fdcab88065175ced768d900e7dcedf3b1d1b0a7/sdk/dotnet/Core/ResourceGroup.cs#L90">here</a> to double-check the exact value.</p>
<p>After this change in <code>Mocks</code>, my tests go green again.</p>
<pre tabindex="0"><code>Test Run Successful.
Total tests: 3
     Passed: 3
</code></pre><h2 id="website-files">Website Files</h2>
<p>The next step is to upload some files to the static website. Well, instead, to write an automated test that validates the upload with mocks.</p>
<p>I create a <code>wwwroot</code> folder with two HTML files in it and copy the folder to the build&rsquo;s output. Here is my change in the project file.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#0550ae">&lt;ItemGroup&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;None</span> <span style="color:#1f2328">Update=</span><span style="color:#0a3069">&#34;wwwroot\**\*.*&#34;</span><span style="color:#0550ae">&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&lt;CopyToOutputDirectory&gt;</span>PreserveNewest<span style="color:#0550ae">&lt;/CopyToOutputDirectory&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&lt;/None&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#0550ae">&lt;/ItemGroup&gt;</span>
</span></span></code></pre></div><p>Now, the test is straightforward: it expects two <code>Blob</code> resources in the stack.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#0550ae">[&lt;</span>Test<span style="color:#0550ae">&gt;]</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">member</span> this<span style="color:#1f2328">.</span><span style="color:#6639ba">UploadsTwoFiles</span><span style="color:#6a737d">()</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">resources</span> <span style="color:#0550ae">=</span> runTest<span style="color:#6a737d">()</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">filesCount</span> <span style="color:#0550ae">=</span> resources<span style="color:#0550ae">.</span>OfType<span style="color:#0550ae">&lt;</span>Blob<span style="color:#0550ae">&gt;</span><span style="color:#6a737d">()</span> <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Seq</span><span style="color:#1f2328">.</span>length
</span></span><span style="display:flex;"><span>    filesCount<span style="color:#0550ae">.</span>Should<span style="color:#6a737d">()</span><span style="color:#0550ae">.</span>Be<span style="color:#0550ae">(</span>2<span style="color:#0550ae">,</span> <span style="color:#0a3069">&#34;Should have uploaded files from `wwwroot`&#34;</span><span style="color:#0550ae">)</span> <span style="color:#0550ae">|&gt;</span> ignore
</span></span></code></pre></div><p>As intended, the test fails immediately.</p>
<pre tabindex="0"><code>X UploadsTwoFiles [95ms]
  Error Message:
   Expected files.Count to be 2 because Should have uploaded files from `wwwroot`, but found 0.
</code></pre><p>I extend my stack with the following loop that navigates through the files and creates a storage blob for each of them.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">files</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">fileNames</span> <span style="color:#0550ae">=</span> <span style="color:#24292e">Directory</span><span style="color:#1f2328">.</span>GetFiles<span style="color:#0550ae">(</span><span style="color:#0a3069">&#34;wwwroot&#34;</span><span style="color:#0550ae">)</span>
</span></span><span style="display:flex;"><span>    fileNames
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Seq</span><span style="color:#1f2328">.</span>map<span style="color:#0550ae">(</span><span style="color:#cf222e">fun</span> file <span style="color:#0550ae">-&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">new</span> Blob<span style="color:#0550ae">(</span>
</span></span><span style="display:flex;"><span>            file<span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#cf222e">new</span> BlobArgs<span style="color:#0550ae">(</span>
</span></span><span style="display:flex;"><span>                ContentType <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;application/html&#34;</span><span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                Source <span style="color:#0550ae">=</span> input <span style="color:#0550ae">(</span><span style="color:#cf222e">new</span> FileAsset<span style="color:#0550ae">(</span>file<span style="color:#0550ae">)</span> <span style="color:#0550ae">:&gt;</span> AssetOrArchive<span style="color:#0550ae">),</span>
</span></span><span style="display:flex;"><span>                StorageAccountName <span style="color:#0550ae">=</span> io storageAccount<span style="color:#0550ae">.</span>Name<span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                StorageContainerName <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;$web&#34;</span><span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                Type <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;Block&#34;</span><span style="color:#0550ae">)))</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">List</span><span style="color:#1f2328">.</span>ofSeq
</span></span></code></pre></div><p>However, this change breaks the stack and all tests in the suite:</p>
<pre tabindex="0"><code>Error Message:
   Pulumi.RunException : Running program failed with an unhandled exception:
System.InvalidOperationException: Unsupported value when converting to protobuf: Pulumi.FileAsset
   at Pulumi.Serialization.Serializer.CreateValue(Object value)
...
</code></pre><p>Once again, our mocks fail to represent the behavior of the engine accurately. The Pulumi engine knows about the <code>FileAsset</code> class pointing to a file on the disk and how to convert it to an uploaded blob. But, the engine doesn&rsquo;t copy this property to outputs. I need to adjust the mocks again.</p>
<p>I&rsquo;m not particularly interested in testing the binary contents of the files now, so I&rsquo;ll change the <code>Mocks</code> class to ignore the <code>source</code> property and not to include it into the output dictionary.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">outputs</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">[</span>inputKVs<span style="color:#0550ae">;</span> nameKVs<span style="color:#0550ae">]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Seq</span><span style="color:#1f2328">.</span>concat
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Seq</span><span style="color:#1f2328">.</span>filter <span style="color:#0550ae">(</span><span style="color:#cf222e">fun</span> <span style="color:#0550ae">(</span>k<span style="color:#0550ae">,</span> <span style="color:#0550ae">_)</span> <span style="color:#0550ae">-&gt;</span> typeName <span style="color:#0550ae">&lt;&gt;</span> <span style="color:#0a3069">&#34;azure:storage/blob:Blob&#34;</span> <span style="color:#0550ae">||</span> k <span style="color:#0550ae">&lt;&gt;</span> <span style="color:#0a3069">&#34;source&#34;</span><span style="color:#0550ae">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Seq</span><span style="color:#1f2328">.</span>map KeyValuePair
</span></span></code></pre></div><p>This change makes my test suite happy again.</p>
<pre tabindex="0"><code>Test Run Successful.
Total tests: 4
     Passed: 4
</code></pre><h2 id="validate-website-url">Validate Website URL</h2>
<p>So far, I&rsquo;ve been validating the input parameters of resources. What if I want to test something that is usually done by the cloud provider?</p>
<p>For instance, when Azure creates a static website, it automatically assigns a public endpoint. The endpoint is then available in the <code>PrimaryWebEndpoint</code> property after the Pulumi program ran and resources are created. I may want to export this value from stack outputs and validate it in a unit test.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#0550ae">[&lt;</span>Test<span style="color:#0550ae">&gt;]</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">member</span> this<span style="color:#1f2328">.</span><span style="color:#6639ba">StackExportsWebsiteUrl</span><span style="color:#6a737d">()</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">resources</span> <span style="color:#0550ae">=</span> runTest<span style="color:#6a737d">()</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">stack</span> <span style="color:#0550ae">=</span> resources<span style="color:#0550ae">.</span>OfType<span style="color:#0550ae">&lt;</span>WebsiteStack<span style="color:#0550ae">&gt;</span><span style="color:#6a737d">()</span> <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Seq</span><span style="color:#1f2328">.</span>head
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">endpoint</span> <span style="color:#0550ae">=</span> getValue stack<span style="color:#0550ae">.</span>Endpoint
</span></span><span style="display:flex;"><span>    endpoint<span style="color:#0550ae">.</span>Should<span style="color:#6a737d">()</span><span style="color:#0550ae">.</span>Be<span style="color:#0550ae">(</span><span style="color:#0a3069">&#34;https://wwwprodsa.web.core.windows.net&#34;</span><span style="color:#0550ae">,</span> <span style="color:#cf222e">null</span><span style="color:#0550ae">)</span> <span style="color:#0550ae">|&gt;</span> ignore
</span></span></code></pre></div><p>This test doesn&rsquo;t even compile yet, so I have to define the <code>Endpoint</code> property and assign a value to it. Here is the complete implementation of the stack with the output property defined at the end.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">type</span> <span style="color:#1f2328">WebsiteStack</span><span style="color:#6a737d">()</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">inherit</span> Stack<span style="color:#6a737d">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">resourceGroup</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">new</span> ResourceGroup<span style="color:#0550ae">(</span>
</span></span><span style="display:flex;"><span>            <span style="color:#0a3069">&#34;www-prod-rg&#34;</span><span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#cf222e">new</span> ResourceGroupArgs<span style="color:#0550ae">(</span>Tags <span style="color:#0550ae">=</span> inputMap<span style="color:#0550ae">([</span><span style="color:#0a3069">&#34;Environment&#34;</span><span style="color:#0550ae">,</span> input <span style="color:#0a3069">&#34;production&#34;</span><span style="color:#0550ae">])))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">storageAccount</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">new</span> Account<span style="color:#0550ae">(</span>
</span></span><span style="display:flex;"><span>            <span style="color:#0a3069">&#34;wwwprodsa&#34;</span><span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#cf222e">new</span> AccountArgs<span style="color:#0550ae">(</span>
</span></span><span style="display:flex;"><span>                ResourceGroupName <span style="color:#0550ae">=</span> io resourceGroup<span style="color:#0550ae">.</span>Name<span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                AccountTier <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;Standard&#34;</span><span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                AccountReplicationType <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;LRS&#34;</span><span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                StaticWebsite <span style="color:#0550ae">=</span> input <span style="color:#0550ae">(</span><span style="color:#cf222e">new</span> AccountStaticWebsiteArgs<span style="color:#0550ae">(</span>IndexDocument <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;index.html&#34;</span><span style="color:#0550ae">))))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">files</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">let</span> <span style="color:#953800">fileNames</span> <span style="color:#0550ae">=</span> <span style="color:#24292e">Directory</span><span style="color:#1f2328">.</span>GetFiles<span style="color:#0550ae">(</span><span style="color:#0a3069">&#34;wwwroot&#34;</span><span style="color:#0550ae">)</span>
</span></span><span style="display:flex;"><span>        fileNames
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">Seq</span><span style="color:#1f2328">.</span>map<span style="color:#0550ae">(</span><span style="color:#cf222e">fun</span> file <span style="color:#0550ae">-&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#cf222e">new</span> Blob<span style="color:#0550ae">(</span>
</span></span><span style="display:flex;"><span>                file<span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#cf222e">new</span> BlobArgs<span style="color:#0550ae">(</span>
</span></span><span style="display:flex;"><span>                    ContentType <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;application/html&#34;</span><span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                    Source <span style="color:#0550ae">=</span> input <span style="color:#0550ae">(</span><span style="color:#cf222e">new</span> FileAsset<span style="color:#0550ae">(</span>file<span style="color:#0550ae">)</span> <span style="color:#0550ae">:&gt;</span> AssetOrArchive<span style="color:#0550ae">),</span>
</span></span><span style="display:flex;"><span>                    StorageAccountName <span style="color:#0550ae">=</span> io storageAccount<span style="color:#0550ae">.</span>Name<span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                    StorageContainerName <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;$web&#34;</span><span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                    Type <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;Block&#34;</span><span style="color:#0550ae">)))</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">List</span><span style="color:#1f2328">.</span>ofSeq
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">[&lt;</span>Output<span style="color:#0550ae">&gt;]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">member</span> <span style="color:#cf222e">val</span> Endpoint <span style="color:#0550ae">=</span> storageAccount<span style="color:#0550ae">.</span>PrimaryWebEndpoint <span style="color:#cf222e">with</span> get<span style="color:#0550ae">,</span> set
</span></span></code></pre></div><p>Now, the test compiles but fails when executed:</p>
<pre tabindex="0"><code>X StackExportsWebsiteUrl [85ms]
  Error Message:
   Expected endpoint to be &#34;https://wwwprodsa.web.core.windows.net&#34;, but found &lt;null&gt;.
</code></pre><p>That&rsquo;s because there is no call to Azure to populate the endpoint. It&rsquo;s time to make the last change to my <code>Mocks</code> class to emulate that assignment.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">endpointKVs</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">if</span> typeName <span style="color:#0550ae">=</span> <span style="color:#0a3069">&#34;azure:storage/account:Account&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">then</span> <span style="color:#0550ae">[</span><span style="color:#0a3069">&#34;primaryWebEndpoint&#34;</span><span style="color:#0550ae">,</span> sprintf <span style="color:#0a3069">&#34;https://%s.web.core.windows.net&#34;</span> name <span style="color:#0550ae">:&gt;</span> <span style="color:#cf222e">obj</span><span style="color:#0550ae">]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">else</span> <span style="color:#6a737d">[]</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">outputs</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">[</span>inputKVs<span style="color:#0550ae">;</span> nameKVs<span style="color:#0550ae">;</span> endpointKVs<span style="color:#0550ae">]</span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// ...
</span></span></span></code></pre></div><p>And that&rsquo;s it! My static website is ready and tested!</p>
<pre tabindex="0"><code>Test Run Successful.
Total tests: 5
     Passed: 5
 Total time: 0.9338 Seconds
</code></pre><p>Note that it still takes less than a second to run my test suite so that I can iterate very quickly.</p>
<h2 id="get-started">Get Started</h2>
<p>The tests above cover the basics of unit testing with Pulumi .NET SDK. You can take it from here and apply the techniques and practices that you use while testing the application code. You may also try more advanced practices like property-based testing or behavior-driven development—we believe that the mocks enable many testing styles.</p>
<p>Here are several useful pointers to get started with testing in Pulumi:</p>
<ul>
<li><a href="https://www.pulumi.com/docs/guides/testing/">Testing Guide</a>.</li>
<li><a href="https://github.com/pulumi/examples/tree/72c9480f4c1240f795f6020f50801733fbef37f2/testing-unit-fs-mocks">Full code for this blog post</a>.</li>
</ul>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                             
                                <category scheme="https://mikhail.io/tags/unit-testing" term="unit-testing" label="Unit Testing" />
                             
                                <category scheme="https://mikhail.io/tags/tdd" term="tdd" label="TDD" />
                             
                                <category scheme="https://mikhail.io/tags/fsharp" term="fsharp" label="FSharp" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Serverless in the Wild: Azure Functions Production Usage Statistics]]></title>
            <link href="https://mikhail.io/2020/05/serverless-in-the-wild-azure-functions-usage-stats/"/>
            <id>https://mikhail.io/2020/05/serverless-in-the-wild-azure-functions-usage-stats/</id>
            
            <published>2020-05-05T00:00:00+00:00</published>
            <updated>2020-05-05T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Insightful statistics about the actual production usage of Azure Functions, based on the data from Microsoft&rsquo;s paper</blockquote><p>Microsoft Azure and Microsoft Research <a href="https://arxiv.org/pdf/2003.03423.pdf">released</a> a paper called &ldquo;Serverless in the Wild: Characterizing and Optimizing the Serverless Workload at a Large Cloud Provider&rdquo;. In part 1 of the paper, they revealed some insightful statistics about the actual production usage of Azure Functions for two weeks in summer 2019.</p>
<p>This blog post is my summary and highlights of the most intriguing data points. I&rsquo;ll cover part 2 of the paper, focusing on cold starts, in a separate installment.</p>
<h2 id="trigger-types-and-invocations">Trigger Types and Invocations</h2>
<p>Every Azure Function has a trigger: it&rsquo;s linked to a specific event source, and every event in that source causes the Function to execute. Event sources include HTTP endpoints, timers, message queues, topics, event logs, and others. Which triggers are used more often?</p>
<p>The paper doesn&rsquo;t have a precise breakdown by specific sources like Service Bus or Event Grid, but it gives relative numbers per event category. It also shows the percentage of Function invocations that each type is responsible for.</p>
<p>Here is the comparison table:</p>
<p>{.table .pure-table .table-striped}</p>
<table>
  <thead>
      <tr>
          <th>Trigger type</th>
          <th>% of Functions</th>
          <th>% of Invocations</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>HTTP</td>
          <td>55%</td>
          <td>36%</td>
      </tr>
      <tr>
          <td>Queue</td>
          <td>15%</td>
          <td>34%</td>
      </tr>
      <tr>
          <td>Event</td>
          <td>2%</td>
          <td>25%</td>
      </tr>
      <tr>
          <td>Timer</td>
          <td>16%</td>
          <td>2%</td>
      </tr>
      <tr>
          <td>Orchestration</td>
          <td>7%</td>
          <td>2%</td>
      </tr>
      <tr>
          <td>Others</td>
          <td>5%</td>
          <td>1%</td>
      </tr>
  </tbody>
</table>
<p>Notable facts:</p>
<ul>
<li>More than half of Functions are triggered by HTTP.</li>
<li>Asynchronous queue and, especially, event functions have much more invocations on average than any other Function type.</li>
<li>The opposite is accurate for timer Functions: a relatively high number of Functions translates to a fraction of invocations.</li>
<li>Durable functions are quite popular if that&rsquo;s what the authors mean by &ldquo;orchestration&rdquo; triggers.</li>
</ul>
<h2 id="functions-and-applications">Functions and Applications</h2>
<p>Function App is the deployment unit of Azure Functions. Each Function App may contain one or more Functions packaged together. How often do people use multiple Functions, and what for?</p>
<p>{.table .pure-table .table-striped}</p>
<table>
  <thead>
      <tr>
          <th># of Functions</th>
          <th>Percentage</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>54%</td>
      </tr>
      <tr>
          <td>2</td>
          <td>16%</td>
      </tr>
      <tr>
          <td>3</td>
          <td>10%</td>
      </tr>
      <tr>
          <td>4 to 5</td>
          <td>8%</td>
      </tr>
      <tr>
          <td>6 to 10</td>
          <td>7%</td>
      </tr>
      <tr>
          <td>11+</td>
          <td>5%</td>
      </tr>
      <tr>
          <td>(100+)</td>
          <td>0.04%</td>
      </tr>
      <tr>
          <td>(2000+)</td>
          <td>(a couple)</td>
      </tr>
  </tbody>
</table>
<p>Apparently, multi-function applications are widespread! The number of functions-per-app is all over the spectrum, with some wild usage scenarios of hundreds or even thousands of Functions in the same Function App.</p>
<p>Combining this with trigger types, it seems hard to derive any common patterns of trigger combinations in the same app. The paper lists several common ones, but none of them has more than 5% of usage. For instance, only 4.5% of applications are exactly one HTTP trigger and one timer trigger—which I could potentially attribute to the pattern of warming HTTP Functions against <a href="/serverless/coldstarts">cold starts</a>.</p>
<h2 id="invocation-patterns">Invocation Patterns</h2>
<p>How often are functions invoked? What are typical numbers for executions per day, minute, or second?</p>
<p>The variation in invocation frequency is tremendous. While many Functions may stay idle for days, others run many times per second. There&rsquo;s nothing unexpected here: it&rsquo;s free to create a Function App, so many apps exist for test purposes, one-off experiments, or to handle infrequent timers or automation tasks. At the same time, some production Functions would serve intensive workloads via HTTP, queues, or event buses.</p>
<p>The paper shows actual numbers for frequency distribution:</p>
<ul>
<li><strong>45%</strong> of Function Apps are invoked at most <strong>once per hour</strong>.</li>
<li>Another <strong>36%</strong> are invoked at most <strong>once per minute</strong>.</li>
<li>The remaining <strong>19%</strong> of more frequently invoked Functions represent more than 99.5% of all invocations.</li>
<li>Only about <strong>3%</strong> of applications are invoked more often than <strong>once a second</strong>.</li>
</ul>
<p>I&rsquo;m surprised that the vast majority of Functions run, on average, very infrequently. It looks like more than 97% of Function Apps don&rsquo;t require any scalability beyond scaling from zero to a single instance and back.</p>
<p>The following chart shows the cumulative number of invocations that Azure Functions handle across all applications. Unfortunately, there are no absolute numbers—the chart is normalized to the peak.</p>
<figure >
    
        <img src="invocations.png"
            alt="Total invocations per hour for all Azure Functions, relative to the peak"
             />
        
    
    <figcaption>
        <h4>Total invocations per hour for all Azure Functions, relative to the peak</h4>
    </figcaption>
    
</figure>
<p>There are apparent repeatable patterns and a constant baseline of roughly 50% of the invocations. The platform needs to handle about 2x scalability through a given day.</p>
<p>Overall, the macro-scalability of the platform seems to be less of a challenge compared to optimizing the lifecycle of each individual Function App. Across applications, the number of invocations per day varies by 8 orders of magnitude, making the resources the provider has to dedicate to each application also highly variable.</p>
<p>Since the majority of applications are mostly idle, any inefficiency in handling them, being significant relative to their total execution (billable) time, can be prohibitively expensive. And that&rsquo;s a hard problem to solve!</p>
<h2 id="function-execution-times">Function Execution Times</h2>
<p>Once a Function is triggered, for how long will it typically run until completed?</p>
<p>Predictably, executions in Function-as-a-Service are very short compared to other cloud workloads. However, again, there are several orders of magnitude difference across different Functions:</p>
<ul>
<li>About <strong>20%</strong> of Functions would complete, on average, in less than <strong>100 ms</strong>.</li>
<li>Approximately <strong>50%</strong> of the Functions execute for less than <strong>1 second</strong> on average.</li>
<li>Also, <strong>50%</strong> of the Functions have <strong>maximum</strong> execution time shorter than <strong>3 seconds</strong>.</li>
<li>Yet, the slowest <strong>10%</strong> of the Functions have maximum execution time more than <strong>1 minute</strong>, and <strong>4% take more than a minute on average</strong>.</li>
</ul>
<p>The most common durations seem to be between 100 ms and several seconds, which is not surprising.</p>
<h2 id="conclusion">Conclusion</h2>
<p>It&rsquo;s incredible how much of a variation exists in real-world workloads running in Azure Functions. Across all the metrics above, there never seem to be one or two dominating scenarios. It&rsquo;s impressive that a single service can decently handle all the variability.</p>
<p>Kudos to folks at Microsoft Azure for releasing the data and analysis for their actual production workloads, albeit just for two weeks and without absolute numbers.</p>
<p>The second part of the paper uses this data to analyze potential improvements in cold starts of Azure Functions. And that&rsquo;s an excellent topic for a follow-up blog post, so stay tuned!</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                             
                                <category scheme="https://mikhail.io/tags/paper-review" term="paper-review" label="Paper review" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[InfiniCache: Distributed Cache on Top of AWS Lambda (paper review)]]></title>
            <link href="https://mikhail.io/2020/03/infinicache-distributed-cache-on-aws-lambda/"/>
            <id>https://mikhail.io/2020/03/infinicache-distributed-cache-on-aws-lambda/</id>
            
            <published>2020-03-10T00:00:00+00:00</published>
            <updated>2020-03-10T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>My review of the paper &ldquo;InfiniCache: Exploiting Ephemeral Serverless Functions to Build a Cost-Effective Memory Cache&rdquo;</blockquote><p>&ldquo;InfiniCache: Exploiting Ephemeral Serverless Functions to Build a Cost-Effective Memory Cache&rdquo; by Ao Wang, et al. (<a href="https://www.usenix.org/conference/fast20/presentation/wang-ao">link</a>) is a recently published paper which describes a prototype of a serverless distributed caching system sitting atop AWS Lambda.</p>
<p>Most distributed caching solutions run on a cluster of VMs. The cost of such a cluster is mostly fixed and depends on required memory allocation, no matter how many requests it serves. InfiniCache aims to bring a serverless pay-per-request dynamic pricing model to the world of in-memory data caching.</p>
<p>While reading through the paper, I got fascinated by several techniques that they employed while implementing the system. Therefore, I decided to review the paper and highlight its most intriguing parts. I target my review at software and IT engineers working on cloud applications. I assume the reader has a basic understanding of serverless cloud functions (Function-as-a-Service, FaaS) and their traditional use cases.</p>
<p>Let&rsquo;s start with a problem that InfiniCache aims to solve.</p>
<h2 id="i-state-in-stateless-functions">I. State in Stateless Functions</h2>
<p>Serverless functions excel at running stateless workloads. FaaS services use ephemeral compute instances to grow and shrink the capacity to adjust to a variable request rate. New virtual machines or containers are assigned and recycled by the cloud provider at arbitrary moments.</p>
<p>Functions are incredibly compelling for low-load usage scenarios, among others. Executions are charged per actual duration, at 100 ms granularity. If your application only needs to run several times per day in a test environment, it costs you very close to zero.</p>
<p>Cloud providers try to reuse compute instances for multiple subsequent requests. The price of bootstrapping new instances is too high to pay for every request. A warm instance would keep pre-loaded files in memory between requests to minimize the overhead and <a href="/serverless/coldstarts">cold start</a> duration. The exact behavior is a black box and varies between region, AZ, and in time.</p>
<p>There seems to be a fundamental opportunity to exploit the keep-warm behavior further. Each function instance has up to several gigabytes of memory, while our executable cache might only consume several megabytes. Can we use this memory to persist useful state?</p>
<h3 id="in-memory-cache">In-memory Cache</h3>
<p>The most straightforward way to use this capacity is to store reusable data between requests. If we need a piece of data from external storage, and that same piece is needed again and again, we could store it after the first load in a global in-memory hashmap. Think of a simple read-through cache backed by an external persistent data store.</p>
<figure >
    
        <img src="in-memory.png"
            alt="In-memory cache in AWS Lambda in front of a persistent database"
             />
        
    
    <figcaption>
        <h4>In-memory cache in AWS Lambda in front of a persistent database</h4>
    </figcaption>
    
</figure>
<p>This approach is still quite limited in several ways:</p>
<ul>
<li>The total size of the cache is up to 3GB (max size of AWS Lambda), which might not be enough for a large number of objects or large objects.</li>
<li>Any parallel request hits a different instance of the same AWS Lambda. The second instance then has to load the data from the data store once again.</li>
</ul>
<figure >
    
        <img src="two-in-memory.png"
            alt="A parallel request hits the second instance with no cache available"
             />
        
    
    <figcaption>
        <h4>A parallel request hits the second instance with no cache available</h4>
    </figcaption>
    
</figure>
<p>Each instance has to store its copy of cached data, which reduces the overall efficiency. Also, cache invalidation might get tricky in this case, particularly because each instance has its own, highly unpredictable lifetime.</p>
<h3 id="distributed-cache">Distributed Cache</h3>
<p>The previous scenario described a local cache co-located with data processing logic. Can we move the cache out of the currently invoked instance, distribute it among many other instances, and have multiple lambdas caching different portions of the data set? This move would help both with the cache size and duplication issues.</p>
<figure >
    
        <img src="distributed-cache.png"
            alt="Application loads data from a distributed cache of multiple AWS Lambdas"
             />
        
    
    <figcaption>
        <h4>Application loads data from a distributed cache of multiple AWS Lambdas</h4>
    </figcaption>
    
</figure>
<p>The idea is to create a Distributed Cache as a Service, with design goals inspired by the serverless mindset:</p>
<ul>
<li>Low management overhead</li>
<li>Scalability to hundreds or thousands of nodes</li>
<li>Pay per request</li>
</ul>
<p>The most significant potential upside is related to the cost structure. The cache capacity is billed only when a request needs an object. Such a pay-per-request model significantly differentiates against conventional cluster-based cache services, which typically charge for memory capacity on an hourly basis whether the cached objects are accessed or not.</p>
<h3 id="challenges">Challenges</h3>
<p>However, once again, serverless functions were not designed for stateful use cases. Utilizing the memory of cloud functions for object caching introduces non-trivial challenges due to the limitations and constraints of serverless computing platforms. The following list is specific to AWS Lambda, but other providers have very similar setups.</p>
<ul>
<li>Limited resource capacity is available: 1 or 2 CPU, up to several GB of memory, and limited network bandwidth.</li>
<li>Providers may reclaim a function and its memory at any time, creating a risk of loss of the cached data.</li>
<li>Each Lambda execution can run at most 900 seconds (15 minutes).</li>
<li>Strict network communication constraints are in place: Lambda only allows outbound network connections, meaning a Lambda function cannot be used to implement a server.</li>
<li>The lack of quality-of-service control. As a result, functions suffer from straggler issues, when response duration distributions have long tails.</li>
<li>Lambda functions run at EC2 Virtual Machines (VMs), where a single VM can host one or more functions. AWS provisions functions on the smallest possible number of VMs, which could cause severe network bandwidth contention for multiple network-intensive Lambda functions on the same host VM.</li>
<li>Executions are billed per 100 ms, even if a request only takes 5 or 10 ms.</li>
<li>No concurrent invocations are allowed, so the same instance can&rsquo;t handle multiple requests at the same time.</li>
<li>There is a non-trivial invocation latency on top of any response processing time.</li>
</ul>
<p>Honestly, that&rsquo;s an extensive list of pitfalls. Nonetheless, InfiniCache presents a careful combination of choosing the best use case, working around some issues, and hacking straight through the others.</p>
<h3 id="large-object-caching">Large Object Caching</h3>
<p>Traditional distributed cache products aren&rsquo;t great when it comes to caching large objects (megabytes):</p>
<ul>
<li>Large objects are accessed less often while consuming vast space in RAM.</li>
<li>They occupy memory and cause evictions of many small objects that might be reused shortly, thus hurting performance.</li>
<li>Requests to large objects consume significant network bandwidth, which may inevitably affect the latencies of small objects.</li>
</ul>
<p>Because of these reasons, it&rsquo;s common <strong>not</strong> to cache large objects and retrieve them from the backing storage like S3 directly.</p>
<p>What is missing is a truly elastic cloud storage service model that charges tenants in a request-driven mode instead of per capacity usage, which the emerging serverless computing naturally enables.</p>
<h2 id="ii-infinicache">II. InfiniCache</h2>
<p>The paper <a href="https://arxiv.org/pdf/2001.10483.pdf">InfiniCache: Exploiting Ephemeral Serverless Functions to Build a Cost-Effective Memory Cache</a> presents a prototype of a distributed cache service running on top of AWS Lambda. The service is aimed specifically at large object caching. It deploys a large fleet of Lambda functions, where each Lambda stores a slice of the total data pool.</p>
<blockquote>
<p>InfiniCache offers a virtually infinite (yet cheap) short-term capacity, which is advantageous for large object caching, since the tenants can invoke many cloud functions but have the provider pay the cost of function caching.</p></blockquote>
<p>The authors employ several smart techniques to work around the limitations that I described above and make the system performant and cost-efficient.</p>
<h3 id="multiple-lambda-functions">Multiple Lambda functions</h3>
<p>InfiniCache creates multiple AWS Lambdas, as opposed to multiple instances of the same Lambda. Each Lambda effectively runs on a single instance (container) that holds a slice of data. A second instance may be used for backups as explained below.</p>
<p>Therefore, there is no dynamic scaling: the cache cluster is pre-provisioned to the level of required capacity. This does not incur a high fixed cost, though: each Lambda is still charged per invocation, not for the number of Lambda services or deployments.</p>
<figure >
    
        <img src="lambda-cache.png"
            alt="AWS Lambdas as cache storage units"
             />
        
    
    <figcaption>
        <h4>AWS Lambdas as cache storage units</h4>
    </figcaption>
    
</figure>
<p>The described approach may sound very limiting. A single instance? Well, each instance can only handle a single request at a time, do the clients need to coordinate and wait for each other? How do the clients even know which Lambda to invoke to retrieve a piece of data?</p>
<p>That&rsquo;s why InfiniCache has an extra component called <strong>a proxy</strong>.</p>
<h3 id="proxy">Proxy</h3>
<p>A proxy is a static always-up well-known component deployed on a large EC2 virtual machine. A proxy has a stable endpoint and becomes the entry point for all end clients. It then talks to Lambdas via an internal protocol, as I explain below.</p>
<figure >
    
        <img src="proxy.png"
            alt="A client makes requests to the proxy which connects to a Lambda"
             />
        
    
    <figcaption>
        <h4>A client makes requests to the proxy which connects to a Lambda</h4>
    </figcaption>
    
</figure>
<p>It&rsquo;s now the responsibility of the proxy to distribute blobs between Lambdas and persist their addresses between the requests.</p>
<p>There may be multiple proxies for large deployments. InfiniCache&rsquo;s client library determines the destination proxy (and therefore its backing Lambda pool) by using a consistent hashing-based load balancing approach.</p>
<p>On the other side, for smaller deployments, the proxy could be placed directly on the client machine to save cost and an extra network hop.</p>
<h3 id="backward-connections">Backward Connections</h3>
<p>Firing a new Lambda invocation for each cache request proves to be inefficient: You pay for the minimum of 100 ms and block the entire instance for the single execution. Therefore, InfiniCache authors use a sophisticated hand-crafted model with two channels for proxy-to-lambda communication.</p>
<p>Traditional Lambda invocations are used only for activation and billed duration control. The same invocation is potentially reused to serve multiple cache requests via a separate TCP channel.</p>
<p>Since AWS Lambda does  not  allow  inbound  TCP  or  UDP  connections,  each Lambda runtime establishes a TCP connection with its designated proxy server the first time it is invoked. A Lambda node gets its proxy’s connection information via the invocation parameters.  The Lambda runtime then keeps the TCP connection established until reclaimed by the provider.</p>
<figure >
    
        <img src="channels.png"
            alt="During an active invocation, Lambda establishes a TCP connection for multiplexed data transfer"
             />
        
    
    <figcaption>
        <h4>During an active invocation, Lambda establishes a TCP connection for multiplexed data transfer</h4>
    </figcaption>
    
</figure>
<p>Here is a somewhat simplified explanation of the protocol:</p>
<ol>
<li>At first, there is no active invocation of a given Lambda.</li>
<li>Whenever the proxy needs to retrieve a blob from a Lambda, it invokes the Lambda with a &ldquo;Ping&rdquo; message, passing the IP of the proxy in the request body.</li>
<li>The Lambda invocation is now activated, but it does NOT return yet. Instead, it establishes a TCP connection to the proxy IP and sends a &ldquo;Pong&rdquo; message over that second channel.</li>
<li>The proxy accepts the TCP connection. Now it can send requests for blob data over that connection. It sends the first request immediately.</li>
<li>The Lambda instance now serves multiple requests on the same TCP connection.</li>
<li>Every billing cycle, the Lambda instance decides whether to keep the invocation alive and keep serving requests over TCP, or to terminate the invocation to stop the billing.</li>
</ol>
<blockquote>
<p>To maximize the use of each billing cycle and to avoid the overhead of restarting Lambdas, InfiniCache&rsquo;s Lambda runtime uses a timeout scheme to control how long a Lambda function runs. When a Lambda node is invoked by a chunk request, a timer is triggered to limit the function’s execution time. The timeout is initially set to expire within the first billing cycle.</p></blockquote>
<blockquote>
<p>If no further chunk request arrives within the first billing cycle, the timer expires and returns 2–10 ms (a short time buffer) before the 100 ms window ends.
If more than one request can be served within the current billing cycle, the heuristic extends the timeout by one more billing cycle, anticipating more incoming requests.</p></blockquote>
<p>This clever workaround overcomes two limitations simultaneously: adjusting to the round-up pricing model, and ensuring that parallel requests can be served by the single Lambda instance, both reusing the same memory cache.</p>
<h3 id="instance-sizes">Instance sizes</h3>
<p>AWS Lambda functions can be provisioned at a memory limit between 128 MB and 3 GB. The allocated CPU cycles are proportional to the provisioned RAM size.</p>
<p>InfiniCache authors find larger instances beneficial. Even though they end up paying more for each invocation, they greatly benefit from improved performance, reduced latency variation, and lack of network contention.</p>
<blockquote>
<p>We find that using relatively bigger Lambda functions largely eliminates Lambda co-location. Lambda’s VM hosts have approximately 3 GB memory.  As such, if we use Lambda functions with ≥ 1.5GB memory, every VM host is occupied exclusively by a single Lambda function.</p></blockquote>
<h3 id="chunking-and-erasure-coding">Chunking and erasure coding</h3>
<p>To serve large files with minimal latency, InfiniCache breaks each file down into several chunks. The proxy stores each chunk on a separate AWS Lambda and requests them in parallel on retrieval. This protocol improves performance by utilizing the aggregated network bandwidth of multiple cloud functions in parallel.</p>
<p>On top of that, InfiniCache uses an approach called <strong>erasure coding</strong>. Redundant information is introduced into each chunk so that the full file could be recovered from a smaller subset of the pieces. For example, &ldquo;10+2&rdquo; encoding breaks each file into 12 chunks, but any 10 chunks are enough to restore the complete file. This redundancy helps fight long-tail latencies: the slowest responses can be ignored, so the stragglers have less effect on the overall latency of the system.</p>
<p>The paper compares several erasure coding schemes (&ldquo;10+0&rdquo;, &ldquo;10+1&rdquo;, &ldquo;10+2&rdquo;, etc.) and finds that 10+1—requiring 10 any chunks out of 11—provides the best trade-off between response latency and computational overhead of encoding.</p>
<p>Predictably, the non-redundant &ldquo;10+0&rdquo; encoding suffers from Lambda straggler issues, which outweighs the performance gained by the elimination of the decoding overhead.</p>
<p>Interestingly, breaking down a file into chunks becomes the responsibility of the client library. The encoding is too computation-heavy to be executed in the proxy.</p>
<h3 id="warm-ups-and-backup">Warm-ups and backup</h3>
<p>Amazon can reclaim any inactive instance of an AWS Lambda at any time. It&rsquo;s not possible to avoid this altogether, but InfiniCache does three things to mitigate the issue and keep the data around for longer:</p>
<ul>
<li>
<p>They issue periodic <a href="https://mikhail.io/2018/08/aws-lambda-warmer-as-pulumi-component/">warm-up</a> requests to each Lambda to prolong the instance lifespan;</p>
</li>
<li>
<p>The redundancy of erasure coding helps recover lost chunks of a single object;</p>
</li>
<li>
<p>Each cloud function periodically performs data synchronization with a clone of itself to minimize the chances that a reclaimed function causes a data loss.</p>
</li>
</ul>
<p>The last point is referred to as “backup” and is uniquely elegant, in my opinion. How does one backup an instance of AWS Lambda? They make the Lambda invoke itself! Because such invocation can’t land on the same instance (the instance is busy calling), the second instance of the same Lambda is going to be created. The two instances can now sync the data, which happens over TCP through a relay co-located with the proxy.</p>
<figure >
    
        <img src="backup.png"
            alt="AWS Lambda creates the second execution of itself and syncs data between the two executions"
             />
        
    
    <figcaption>
        <h4>AWS Lambda creates the second execution of itself and syncs data between the two executions</h4>
    </figcaption>
    
</figure>
<p>If AWS decides to reclaim the primary instance, the next invocation naturally lands on the secondary instance, which already has the data in memory.</p>
<figure >
    
        <img src="failover.png"
            alt="AWS reclaims the primary instance which automatically promotes the secondary instance with hot data"
             />
        
    
    <figcaption>
        <h4>AWS reclaims the primary instance which automatically promotes the secondary instance with hot data</h4>
    </figcaption>
    
</figure>
<p>The promoted instance can now start another replication to create a backup of its own.</p>
<p>Both warm-ups and backups largely contribute to the total cost of the solution, but the authors find this cost justified in terms of improved cache hit rates.</p>
<h2 id="iii-practical-evaluation">III. Practical Evaluation</h2>
<p>The authors implemented a prototype of InfiniCache and benchmarked it against several synthetic tests and a real-life workload of a production Docker registry. Below are some key numbers from their real-life workload comparison against an ElastiCache cluster (a Redis service managed by AWS). Mind that the workload was selected to be a good match for InfiniCache properties (large objects with infrequent access).</p>
<h3 id="cost">Cost</h3>
<p>InfiniCache is one to two orders of magnitude cheaper than ElastiCache (managed Redis cluster) on some workloads:</p>
<blockquote>
<p>By the end of hour 50, ElastiCache costs $518.4, while InfiniCache with all objects costs $20.52. Caching only large objects bigger than 10 MB leads to a cost of $16.51 for InfiniCache. InfiniCache pay-per-use serverless substrate effectively brings down the total cost by 96.8% with a cost effectiveness improvement of 31x. By disabling the backup option, InfiniCache further lowers down the cost to $5.41, which is 96x cheaper than ElastiCache.</p></blockquote>
<p>However, they find that serverless computing platforms are not cost-effective for small-object caching with frequent access.</p>
<blockquote>
<p>The hourly cost increases monotonically with the access rate, and eventually overshoots ElastiCache when the access rate exceeds 312 K requests per hour (86 requests per second).</p></blockquote>
<p>Another gotcha is that the cost above does not include the price of a proxy: the proxy was co-located with the single client VM. A dedicated proxy running on an EC2 instance would increase the total price considerably.</p>
<h3 id="performance">Performance</h3>
<p>Here are the key conclusions based on a production-like test of InfiniCache:</p>
<ul>
<li>
<p>Compared to S3, InfiniCache achieves superior performance improvement for large objects: it&rsquo;s at least 100x for about 60% of all large requests. This trend demonstrates the efficacy of the idea of using a distributed in-memory cache in front of a cloud object store.</p>
</li>
<li>
<p>InfiniCache is particularly good at optimizing latencies for large objects. It is approximately on-par with ElastiCache for objects sizing from 1–100 MB, but InfiniCache achieves consistently lower latencies for objects larger than 100 MB, due to I/O parallelism. However, I suppose chunking could improve the results of ElastiCache too.</p>
</li>
<li>
<p>InfiniCache incurs significant overhead for objects smaller than 1 MB, since fetching an object often requires to invoke Lambda functions, which takes on average 13 ms and is much slower than  directly fetching a small object from ElastiCache.</p>
</li>
</ul>
<h3 id="availability">Availability</h3>
<p>Hit/miss ratio is an essential metric of any cache. A cache miss is usually a result of an object loss when all the replicas of several chunks are gone.</p>
<p>For the large object workload, a production-like test resulted in the availability of 95.4%. InfiniCache without backups sees a substantially lower availability of just 81.4%.</p>
<h2 id="iv-future-directions">IV. Future Directions</h2>
<p>Can a system like InfiniCache be productized and used for production workloads?</p>
<h3 id="service-providers-policy-changes">Service provider&rsquo;s policy changes</h3>
<p>InfiniCache uses several clever tricks to exploit the properties of the existing AWS Lambda service as it works today. However, the behavior of Lambda instances is broadly a black box and changes over time. Even small behavior changes may potentially have a massive impact on the properties of InfiniCache-like systems.</p>
<p>Even worse, service  providers may change their internal implementations and policies <em>in response</em> to systems like InfiniCache. Ideally, any production system would rely on explicit product features of serverless functions rather than implicitly observed characteristics.</p>
<h3 id="explicit-provisioning">Explicit provisioning</h3>
<p>AWS Lambda has recently launched a new feature called <a href="https://mikhail.io/2019/12/aws-lambda-provisioned-concurrency-no-cold-starts/">provisioned concurrency</a>, that allows pinning warm Lambda functions in memory for a fixed hourly fee. Provisioned Lambdas may still get reclaimed and re-initialized periodically, but the reclamation frequency is low compared to non-provisioned Lambdas.</p>
<p>The pay-per-hour pricing component is quite significant, which brings the cost closer to EC2 VMs&rsquo; pricing model. Nonetheless, it opens up research opportunities for hybrid serverless-oriented cloud economics with higher predictability.</p>
<h3 id="internal-implementation-by-a-cloud-provider">Internal implementation by a cloud provider</h3>
<p>Finally, instead of fighting with third-party implementations of stateful systems on top of dynamic function pools, cloud providers could potentially leverage such techniques themselves. A new cloud service could provide short-term caching for data-intensive applications such as big data analytics.</p>
<p>Having the complete picture, datacenter operators would operate a white-box solution and could use the knowledge to optimize data availability and locality. I look forward to new storage products that can more efficiently utilize ephemeral datacenter resources.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/aws-lambda" term="aws-lambda" label="AWS Lambda" />
                             
                                <category scheme="https://mikhail.io/tags/aws" term="aws" label="AWS" />
                             
                                <category scheme="https://mikhail.io/tags/paper-review" term="paper-review" label="Paper review" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Hosting Azure Functions in Google Cloud Run]]></title>
            <link href="https://mikhail.io/2020/02/azure-functions-in-google-cloud-run/"/>
            <id>https://mikhail.io/2020/02/azure-functions-in-google-cloud-run/</id>
            
            <published>2020-02-14T00:00:00+00:00</published>
            <updated>2020-02-14T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Running Azure Functions Docker container inside Google Cloud Run managed service</blockquote><p>Suppose you are a .NET developer, you love the Function-as-a-Service (FaaS) model, but you want to run your serverless functions in Google Cloud. You want to keep using C# or F#, and still leverage all serverless benefits—ease of use, scalability, elasticity, pay-per-value cost model—running in GCP.</p>
<p>You look at Google Cloud Functions, the native FaaS service of Google Cloud, but it only supports JavaScript, Python, and Go. No C# or F#. Time to give up on the plans?</p>
<p>Not so quickly!</p>
<h2 id="google-azure-functions-wat">Google Azure Functions?! Wat?</h2>
<p>You are probably already familiar with Azure Functions—the .NET-based FaaS runtime in Azure. Azure Functions has two faces: it&rsquo;s a managed service in the Azure cloud, and also it is a self-contained runtime that can run anywhere: on your local machine, in a VM, or in a container. As I&rsquo;m going to demonstrate, it can run in Google Cloud too.</p>
<p><img src="teaser.png" alt="Azure Functions Google Cloud Run"></p>
<p>Now, if I simply run an Azure Functions host on a VM in Google Cloud, I don&rsquo;t get all the serverless properties like scalability and pay-for-value. This is where Google Cloud Run comes into the mix.</p>
<p>Cloud Run is a fully managed cloud service that takes a container image and deploys it as an elastic HTTP application with scaling from zero to hero and applying per-request pricing. Cloud Run can host pretty much any container that listens to HTTP requests at a given port.</p>
<p>At the same time, Microsoft provides an official image of Azure Functions host. The host is a web application listening for HTTP requests&hellip; It sounds like we can stick it into Cloud Run!</p>
<h2 id="deploying-azure-functions-to-cloud-run">Deploying Azure Functions to Cloud Run</h2>
<p>My plan has three steps:</p>
<ol>
<li>Develop the code for an Azure Function App</li>
<li>Pack it as a Docker image that would fit the requirements of Google Cloud Run</li>
<li>Deploy the image as a Cloud Run service</li>
</ol>
<h3 id="net-azure-function">.NET Azure Function</h3>
<p>Use your favorite tool to create a new Function App. I employed <code>func</code> CLI to create a new Function App project in C# and define an HTTP Function &ldquo;HttpExample&rdquo;.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#1f2328">[FunctionName(&#34;HttpExample&#34;)]</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">public</span> <span style="color:#cf222e">static</span> <span style="color:#cf222e">async</span> Task<span style="color:#1f2328">&lt;</span>IActionResult<span style="color:#1f2328">&gt;</span> Run<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">    [HttpTrigger(AuthorizationLevel.Anonymous, &#34;get&#34;)]</span> HttpRequest req<span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">string</span> name <span style="color:#1f2328">=</span> <span style="color:#1f2328">(</span><span style="color:#cf222e">string</span><span style="color:#1f2328">)</span>req<span style="color:#1f2328">.</span>Query<span style="color:#1f2328">[</span><span style="color:#0a3069">&#34;name&#34;</span><span style="color:#1f2328">]</span> <span style="color:#1f2328">??</span> <span style="color:#0a3069">&#34;World&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">var</span> service <span style="color:#1f2328">=</span> Environment<span style="color:#1f2328">.</span>GetEnvironmentVariable<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;K_SERVICE&#34;</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">??</span> <span style="color:#0a3069">&#34;&lt;unknown&gt;&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">return</span> <span style="color:#cf222e">new</span> OkObjectResult<span style="color:#1f2328">(</span><span style="color:#0a3069">$&#34;Hello from Azure Function in {service}, {name}&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>It&rsquo;s a standard hello-world Function, except it also retrieves the value of the <code>K_SERVICE</code> environment variable and appends it to the response. This variable should be present when hosted in Cloud Run.</p>
<h3 id="container-image">Container image</h3>
<p>Now, we can wrap the Function App into a Docker image. Here is my <code>Dockerfile</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#cf222e">FROM</span><span style="color:#0a3069"> mcr.microsoft.com/dotnet/core/sdk:3.0 AS installer-env</span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#cf222e">COPY</span> . /src/dotnet-function-app<span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#cf222e">RUN</span> <span style="color:#6639ba">cd</span> /src/dotnet-function-app <span style="color:#0550ae">&amp;&amp;</span> <span style="color:#0a3069">\
</span></span></span><span style="display:flex;"><span><span style="color:#0a3069"></span>    mkdir -p /home/site/wwwroot <span style="color:#0550ae">&amp;&amp;</span> <span style="color:#0a3069">\
</span></span></span><span style="display:flex;"><span><span style="color:#0a3069"></span>    dotnet publish *.csproj --output /home/site/wwwroot<span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#cf222e">FROM</span><span style="color:#0a3069"> mcr.microsoft.com/azure-functions/dotnet:3.0</span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#cf222e">ENV</span> <span style="color:#953800">AzureWebJobsScriptRoot</span><span style="color:#0550ae">=</span>/home/site/wwwroot <span style="color:#0a3069">\
</span></span></span><span style="display:flex;"><span><span style="color:#0a3069"></span>    <span style="color:#953800">AzureFunctionsJobHost__Logging__Console__IsEnabled</span><span style="color:#0550ae">=</span><span style="color:#6639ba">true</span> <span style="color:#0a3069">\
</span></span></span><span style="display:flex;"><span><span style="color:#0a3069"></span>    <span style="color:#953800">ASPNETCORE_URLS</span><span style="color:#0550ae">=</span>http://+:8080<span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#cf222e">COPY</span> --from<span style="color:#0550ae">=</span>installer-env <span style="color:#0550ae">[</span><span style="color:#0a3069">&#34;/home/site/wwwroot&#34;</span>, <span style="color:#0a3069">&#34;/home/site/wwwroot&#34;</span><span style="color:#0550ae">]</span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span></code></pre></div><p>It uses the .NET Core SDK to build the application and publish the binaries to the <code>wwwroot</code> folder. Then I use the official Azure Functions host image to run the application from <code>wwwroot</code>.</p>
<p>The only statement that I have to customize for Cloud Run is <code>ASPNETCORE_URLS=http://+:8080</code>. It instructs my application to listen on the port 8080: the one defined by the Cloud Run&rsquo;s <a href="https://cloud.google.com/run/docs/reference/container-contract">container contract</a>.</p>
<h3 id="deploy-to-cloud-run">Deploy to Cloud Run</h3>
<p>Finally, I can deploy the container definition to Google Cloud Run service. I use Pulumi to deploy all the infrastructure, see <a href="https://mikhail.io/2020/02/serverless-containers-with-google-cloud-run/">this post</a> for a detailed walkthrough.</p>
<p>You can find the complete code of Azure Function deployment to Cloud Run <a href="https://github.com/mikhailshilkov/mikhailio-hugo/tree/master/content/2020/02/azure-functions-in-google-cloud-run/code">here</a>. After running <code>pulumi up</code>, I get a URL back</p>
<pre tabindex="0"><code>endpoint: &#34;https://cloudrun-functions-4f40772-q5zdxwsb2a-ew.a.run.app/api/HttpExample?name=&#34;
</code></pre><p>I can append my name, query the endpoint, and get the response back:</p>
<pre tabindex="0"><code>curl $(pulumi stack output endpoint)Mikhail
Hello from Azure Function in cloudrun-functions-4f40772, Mikhail
</code></pre><p>It works! My Function confirms it&rsquo;s running in the <code>cloudrun-functions-4f40772</code> Cloud Run service.</p>
<h2 id="pros-and-cons">Pros and Cons</h2>
<p>I showed that it&rsquo;s possible to run an Azure Function App inside the managed Google Cloud Run service. Let&rsquo;s spend a moment to discuss the benefits and limitations of this approach.</p>
<h3 id="the-good-parts">The good parts</h3>
<p>You take full advantage of a serverless application model:</p>
<ul>
<li>Google operates Cloud Run and requires next to none management from your side.</li>
<li>The service automatically scales from zero to an arbitrary number of instances based on the actual workload.</li>
<li>You pay per request in chunks of 100 ms. An application that needs to handle few requests may stay below the free allowance.</li>
</ul>
<p>You write your application in a familiar language. I used .NET, it can be C#, F#, or VB, but the same approach should also work for other runtimes supported by Azure Functions, for example, JVM or PowerShell.</p>
<p>You can take advantage of many (but not all, see below) features of Azure Functions: HTTP routes, including parameters, authorization modes, logging, input and output bindings.</p>
<h3 id="not-so-great-parts">Not so great parts</h3>
<p>There is one substantial limitation to my approach, however. Cloud Run can only run HTTP-based workloads, so the set of Azure Function triggers available to you is basically limited to HTTP, EventGrid, and custom triggers based on HTTP. You can&rsquo;t deploy Functions listening to events like Service Bus, or Storage Queues. However, since your application runs in Google Cloud anyway, do you really need those? Google Pub/Sub has HTTP endpoint integration out of the box.</p>
<p>Azure Functions container is an ASP.NET Core application, and we ran it in Google Cloud Run. You may want to forgo the Azure Functions host and deploy your own custom ASP.NET Core application to Cloud Run. Both are possible, and the choice is yours.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Open source and open standards open (dfjokasj) new horizons of possibilities. The team behind Azure Functions provides a way to host Function App inside containers, and Google Cloud Run service enables running arbitrary containers in a serverless manner. Therefore, we can combine the two products to come up with the usage that nobody anticipated in advance.</p>
<p>Isn&rsquo;t that cool? Happy hacking, my cloud friends!</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/gcp" term="gcp" label="GCP" />
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/containers" term="containers" label="Containers" />
                             
                                <category scheme="https://mikhail.io/tags/cloud-run" term="cloud-run" label="Cloud Run" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Serverless Containers with Google Cloud Run]]></title>
            <link href="https://mikhail.io/2020/02/serverless-containers-with-google-cloud-run/"/>
            <id>https://mikhail.io/2020/02/serverless-containers-with-google-cloud-run/</id>
            
            <published>2020-02-04T00:00:00+00:00</published>
            <updated>2020-02-04T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Google Cloud Run is the latest addition to the serverless compute family. While it may look similar to existing services of public cloud, the feature set makes Cloud Run unique.</blockquote><p>Google <a href="https://cloud.google.com/run/">Cloud Run</a> is the latest addition to the serverless compute family. While it may look similar to existing services of public cloud, the feature set makes Cloud Run unique:</p>
<ul>
<li>Docker as a deployment package enables using any language, runtime, framework, or library that can respond to an HTTP request.</li>
<li>Automatic scaling, including scale to zero, means you pay for what you consume with no fixed cost and no management overhead.</li>
<li>HTTP load-balancing out of the box simplifies the usage.</li>
</ul>
<p>Cloud Run is targeted very specifically at stateless web applications. It uses ephemeral containers, and each execution is limited to 15 minutes.</p>
<p>Today, we will deploy our first Cloud Run services with Pulumi. Then, we&rsquo;ll discuss pricing and compare Cloud Run to the competition.</p>
<h2 id="hello-cloud-run">Hello Cloud Run</h2>
<p>We&rsquo;ll start by deploying a pre-built container image provided by Google to a Cloud Run service.</p>
<p>To follow along, create a new Pulumi project with the <a href="https://www.pulumi.com/docs/get-started/gcp/">Get Started with Google Cloud</a> guide.</p>
<h3 id="enable-cloud-run-for-the-project">Enable Cloud Run for the project</h3>
<p>As with any other service of Google Cloud, you need to enable it for the target project before the first deployment. You can do so with the <code>gcp.projects.Service</code> resource:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">enableCloudRun</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">gcp</span><span style="color:#1f2328">.</span><span style="color:#1f2328">projects</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Service</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;EnableCloudRun&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">service</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;run.googleapis.com&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Note: If you destroy the Pulumi stack and delete the <code>projects.Service</code> resource, the Cloud Run service will be disabled again. If you use Cloud Run in multiple Pulumi stacks, you should move the service management to a central location, i.e., a shared stack.</p>
<h3 id="choose-a-location">Choose a location</h3>
<p>Cloud Run is a regional service: all container instances run in a single location of your choice.</p>
<p>Set the region setting to one of the currently supported locations: <code>us-central1</code> (Iowa), <code>us-east1</code> (South Carolina), <code>europe-west1</code>( Belgium), or <code>asia-northeast1</code> (Tokyo).</p>
<pre tabindex="0"><code>pulumi config set gcp:region &lt;region&gt;
</code></pre><p>Then, the program can read the value and reuse it for all resources:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#57606a">// Location to deploy Cloud Run services
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">const</span> <span style="color:#1f2328">location</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">gcp</span><span style="color:#1f2328">.</span><span style="color:#1f2328">config</span><span style="color:#1f2328">.</span><span style="color:#1f2328">region</span> <span style="color:#0550ae">||</span> <span style="color:#0a3069">&#34;us-central1&#34;</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><h3 id="deploy-a-cloud-run-service">Deploy a Cloud Run service</h3>
<p>Google provides a pre-deployed &ldquo;Hello Cloud Run&rdquo; image at <code>gcr.io/cloudrun/hello</code>. The following resource deploys that image to your GCP project with the default settings.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">helloService</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">gcp</span><span style="color:#1f2328">.</span><span style="color:#1f2328">cloudrun</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Service</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;hello&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">template</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">spec</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">containers</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">{</span> <span style="color:#1f2328">image</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;gcr.io/cloudrun/hello&#34;</span> <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">],</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">},</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">dependsOn</span>: <span style="color:#cf222e">enableCloudRun</span> <span style="color:#1f2328">});</span>
</span></span></code></pre></div><h3 id="expose-unrestricted-http-access">Expose unrestricted HTTP access</h3>
<p>By default, Google does not expose HTTP endpoints of a Cloud Run service to the Internet. To make it publicly available, you should grant the <code>roles/run.invoker</code> role to <code>allUsers</code>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">iamHello</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">gcp</span><span style="color:#1f2328">.</span><span style="color:#1f2328">cloudrun</span><span style="color:#1f2328">.</span><span style="color:#1f2328">IamMember</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;hello-everyone&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">service</span>: <span style="color:#cf222e">helloService.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">role</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;roles/run.invoker&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">member</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;allUsers&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Note that the Contributor role is required for the user who executes Pulumi deployment to grant the access.</p>
<h3 id="try-it-out">Try it out</h3>
<p>Each Cloud Run service automatically gets a run.app subdomain. You can export the exact URL as a Pulumi output.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#57606a">// Export the URL
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">helloUrl</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">helloService</span><span style="color:#1f2328">.</span><span style="color:#1f2328">status</span><span style="color:#1f2328">.</span><span style="color:#1f2328">url</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><p>Run <code>pulumi up</code> to deploy all the resources.</p>
<pre tabindex="0"><code>$ pulumi up
...
Outputs:
  ~ helloUrl: &#34;https://hello-585ad15-q4wszdxb2a-ew.a.run.app&#34;
</code></pre><p>Navigate to the URL to see the welcome screen:</p>
<p><img src="./cloud-run-hello-running.png" alt="Cloud Run Hello World container running"></p>
<p>Congratulations, your first Cloud Run service is up and running. Now, it&rsquo;s time to deploy some custom code.</p>
<h2 id="deploy-a-custom-application">Deploy a Custom Application</h2>
<p>Cloud Run can run any Docker container so that you can write the application code in a language of your choice. I picked Ruby as an example of a language that is not supported by Cloud Functions.</p>
<h3 id="create-a-ruby-web-app">Create a Ruby web app</h3>
<p>Let&rsquo;s create an <code>app</code> subfolder and place all application files there. The first file app.rb is a Hello World web application in Ruby.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rb" data-lang="rb"><span style="display:flex;"><span><span style="color:#6639ba">require</span> <span style="color:#0a3069">&#39;sinatra&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>set <span style="color:#032f62">:bind</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#39;0.0.0.0&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>get <span style="color:#0a3069">&#39;/&#39;</span> <span style="color:#cf222e">do</span>
</span></span><span style="display:flex;"><span>  target <span style="color:#0550ae">=</span> <span style="color:#0550ae">ENV</span><span style="color:#0550ae">[</span><span style="color:#0a3069">&#39;TARGET&#39;</span><span style="color:#0550ae">]</span> <span style="color:#0550ae">||</span> <span style="color:#0a3069">&#39;Pulumi&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#0a3069">&#34;Hello </span><span style="color:#0a3069">#{</span>target<span style="color:#0a3069">}</span><span style="color:#0a3069">!</span><span style="color:#0a3069">\n</span><span style="color:#0a3069">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">end</span>
</span></span></code></pre></div><p><a href="https://github.com/pulumi/examples/blob/master/gcp-ts-cloudrun/app/Gemfile">Gemfile</a> and <a href="https://github.com/pulumi/examples/blob/master/gcp-ts-cloudrun/app/Gemfile.lock">Gemfile.lock</a> files configure the required gems (packages).</p>
<h3 id="define-a-docker-image">Define a Docker image</h3>
<p><code>Dockerfile</code> defines the container image to deploy. It is based on a generic Ruby image, copies the application files, and runs the web application.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#57606a"># Use the official lightweight Ruby image.</span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#cf222e">FROM</span><span style="color:#0a3069"> ruby:2.7-slim</span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#57606a"># Install production dependencies.</span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#cf222e">WORKDIR</span><span style="color:#0a3069"> /usr/src/app</span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#cf222e">COPY</span> Gemfile Gemfile.lock ./<span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#cf222e">ENV</span> <span style="color:#953800">BUNDLE_FROZEN</span><span style="color:#0550ae">=</span><span style="color:#6639ba">true</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">RUN</span> bundle install<span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#57606a"># Copy local code to the container image.</span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#cf222e">COPY</span> . ./<span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#57606a"># Run the web service on container startup.</span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#cf222e">CMD</span> <span style="color:#1f2328">[</span><span style="color:#0a3069">&#34;ruby&#34;</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;./app.rb&#34;</span><span style="color:#1f2328">]</span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span></code></pre></div><p>Note that nothing in this web application is specific to Cloud Run.</p>
<h3 id="build-and-publish-the-docker-image">Build and publish the Docker image</h3>
<p>Cloud Run can only deploy images from Google Cloud Registry (GCR). Therefore, our Pulumi program needs to build the Docker image with the sample Ruby application and push it to GCR.</p>
<p>Run <code>gcloud auth configure-docker</code> in your command line to configure your local Docker installation to use GCR endpoints.</p>
<p>Run <code>npm i @pulumi/docker</code> to install the Pulumi Docker SDK. Then, add the <code>docker.Image</code> resource to the Pulumi program.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">myImage</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">docker</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Image</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;ruby-image&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">imageName</span>: <span style="color:#cf222e">pulumi.interpolate</span><span style="color:#0a3069">`gcr.io/</span><span style="color:#0a3069">${</span><span style="color:#1f2328">gcp</span><span style="color:#1f2328">.</span><span style="color:#1f2328">config</span><span style="color:#1f2328">.</span><span style="color:#1f2328">project</span><span style="color:#0a3069">}</span><span style="color:#0a3069">/my-ruby-app:v1.0.0`</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">build</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">context</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;./app&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Pulumi takes care of building the image in the <code>app</code> folder and uploading it to GCR.</p>
<h3 id="deploy-to-cloud-run">Deploy to Cloud Run</h3>
<p>Now, you can deploy another Cloud Run service and point it to the custom image.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">rubyService</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">gcp</span><span style="color:#1f2328">.</span><span style="color:#1f2328">cloudrun</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Service</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;ruby&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">template</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">spec</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">containers</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">image</span>: <span style="color:#cf222e">myImage.imageName</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">resources</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#1f2328">limits</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#1f2328">memory</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;1Gi&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">containerConcurrency</span>: <span style="color:#cf222e">50</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">},</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">dependsOn</span>: <span style="color:#cf222e">enableCloudRun</span> <span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>This snippet illustrates two additional configuration values. The memory limit defines the amount of RAM available to each container. Container concurrency defines how many requests each container instance may process in parallel at any given time. Both parameters influence the price of the deployment, as explained in the Pricing section below.</p>
<p>Currently, it’s not possible to use partial vCPU or multiple vCPUs per instance: your instance has always one vCPU assigned. You can adjust the memory from 128Mb to 2Gb.</p>
<p>Finally, don&rsquo;t forget to enable unrestricted access and export the public URL.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">iamRuby</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">gcp</span><span style="color:#1f2328">.</span><span style="color:#1f2328">cloudrun</span><span style="color:#1f2328">.</span><span style="color:#1f2328">IamMember</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;ruby-everyone&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">service</span>: <span style="color:#cf222e">rubyService.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">role</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;roles/run.invoker&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">member</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;allUsers&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">rubyUrl</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">rubyService</span><span style="color:#1f2328">.</span><span style="color:#1f2328">status</span><span style="color:#1f2328">.</span><span style="color:#1f2328">url</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><p>After deploying the program, you should be able to access the application with an HTTP call.</p>
<pre tabindex="0"><code>$ curl &#34;$(pulumi stack output rubyUrl)&#34;
Hello Pulumi!
</code></pre><p>You can find the complete example in the Pulumi examples <a href="https://github.com/pulumi/examples/blob/master/gcp-ts-cloudrun">Github repository</a>.</p>
<h2 id="pricing">Pricing</h2>
<p>Cloud Run pricing is entirely consumption-based. After you exhaust the free tier, you start paying for four components, combined:</p>
<ul>
<li>Per CPU time ($24.00 / million seconds)</li>
<li>Per memory consumption ($2.50 / million GB-seconds)</li>
<li>Per request ($0.40 / million requests)</li>
<li>Egress traffic, same as any other Google Cloud usage</li>
</ul>
<p>Both time-based measurements are rounded up to the nearest 100 ms. All the provisioned memory is charged, the actual Mb/Gb consumption doesn&rsquo;t matter.</p>
<p>It&rsquo;s important to understand that the CPU and memory metrics are calculated per active instance (host, provisioned container), not per request. Each Cloud Run instance can handle multiple requests concurrently. Overlapping executions aren&rsquo;t double charged: billable time begins with the start of the first request and ends at the end of the last request.</p>
<p>The following picture illustrates the pricing for three executions running on the same container host.</p>
<p><img src="./executions.png" alt="Parallel executions at Cloud Run"></p>
<p>Multiple requests can share the allocated CPU and memory, so it makes sense to set the concurrency setting as high as possible for a given application (but not higher). This model is a big difference to Cloud Functions, which are charged for each request independently.</p>
<h2 id="comparing-cloud-run-to-other-services">Comparing Cloud Run to Other Services</h2>
<p>Despite the existence of multiple services that look somewhat similar, Cloud Run is unique in its capabilities.</p>
<h3 id="google-cloud-functions">Google Cloud Functions</h3>
<p><a href="https://cloud.google.com/functions/">Google Cloud Functions</a> (GCF) service deploys snippets of code as functions, while Cloud Run deploys a web application packaged as a Docker image. Currently, GCF only supports three runtimes (Node.js, Python, and Go), while Cloud Run can run practically any language and any runtime.</p>
<p>GCF has a notion of events and triggers: it can natively integrate with Pub/Sub, Cloud Storage, Cloud Firestore. Cloud Run is all about handling HTTP requests: Any connection to another service has to go via HTTP.</p>
<h3 id="aws-fargate">AWS Fargate</h3>
<p><a href="https://aws.amazon.com/fargate/">AWS Fargate</a> deploys container images. It requires an ECS cluster to run on and imposes more configuration burden on the user, including networking, load balancing, auto-scaling, and service discovery. Pulumi Crosswalk for AWS <a href="https://www.pulumi.com/docs/guides/crosswalk/aws/ecs/">can help</a> with these tasks.</p>
<p>Fargate is capable of hosting long-running workloads. Therefore, Fargate&rsquo;s scaling model is not tied to individual requests, and there is no scale-to-zero out of the box.</p>
<p>With Cloud Run, there&rsquo;s no notion of a cluster to manage, the scaling and billing models are based on individual requests. However, the workloads are HTTP-only, with a maximum duration of 15 minutes per call.</p>
<h3 id="azure-container-instances">Azure Container Instances</h3>
<p><a href="https://azure.microsoft.com/en-us/services/container-instances/">Azure Container Instances</a> (ACI) can also run arbitrary containers and has built-in HTTP endpoints. However, there&rsquo;s no auto-scaling: you get a single host for each instance. Also, there&rsquo;s no load balancing capability across multiple instances.</p>
<p>Cloud Run is elastically scalable, which positions it much better to host applications with variable workloads.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Cloud Run is a service with a unique combination of serverless ease of use and pricing with the full power of container deployments. It is easier to manage than Fargate, has automatic scaling missing from ACI, and greater flexibility compared to GCF. With concurrent requests model, Cloud Run is well-positioned to be significantly cheaper than Cloud Functions for some scenarios.</p>
<p>If you are excited, follow the example to <a href="https://github.com/pulumi/examples/blob/master/gcp-ts-cloudrun">Get Started with Cloud Run and Pulumi</a>.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/gcp" term="gcp" label="GCP" />
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/containers" term="containers" label="Containers" />
                             
                                <category scheme="https://mikhail.io/tags/cloud-run" term="cloud-run" label="Cloud Run" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Provisioned Concurrency: Avoiding Cold Starts in AWS Lambda]]></title>
            <link href="https://mikhail.io/2019/12/aws-lambda-provisioned-concurrency-no-cold-starts/"/>
            <id>https://mikhail.io/2019/12/aws-lambda-provisioned-concurrency-no-cold-starts/</id>
            
            <published>2019-12-19T00:00:00+00:00</published>
            <updated>2019-12-19T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>AWS recently announced the launch of Provisioned Concurrency, a new feature of AWS Lambda that intends to solve the problem of cold starts.</blockquote><p>AWS recently <a href="https://aws.amazon.com/blogs/aws/new-provisioned-concurrency-for-lambda-functions/">announced</a> the launch of <strong>Provisioned Concurrency</strong>, a new feature of AWS Lambda that intends to solve the problem of cold starts. In this article, we take another look at the problem of latency-critical serverless applications, and how the new feature impacts the status-quo.</p>
<h2 id="concurrency-model-of-aws-lambda">Concurrency Model of AWS Lambda</h2>
<p>Despite being serverless, AWS Lambda uses lightweight containers to process incoming requests. Every container, or worker, can process only a single request at any given time.</p>
<figure >
    
        <img src="executions.png"
            alt="Overlapping executions land on separate workers"
             />
        
    
    <figcaption>
        <h4>Overlapping executions land on separate workers</h4>
    </figcaption>
    
</figure>
<p>Because of this, the number of concurrent requests defines the number of required workers that a specific AWS Lambda function needs to serve a response at any given moment.</p>
<h2 id="cold-starts">Cold Starts</h2>
<p>How does AWS know how many workers it needs to run for a given function? Well, it doesn&rsquo;t know in advance. AWS allocates new workers on-demand as the Lambda gets invoked.</p>
<p>Whenever Lambda receives a request but it has no idle workers, the control plane assigns a new generic worker to it. The worker then has to download the custom code or binaries of your Lambda and load them into memory before it can service the request. This process takes time, which significantly increases response latency.</p>
<p>The issue of sporadically slow responses caused by the need to increase the pool of workers is known as <strong>Cold Start</strong>. Cold starts are consistently the top concern about the applicability of serverless technologies to latency-sensitive workloads. There are numerous articles about the problem, including many articles I have written in the <a href="https://mikhail.io/serverless/coldstarts/">Cold Starts</a> section on my website</p>
<h2 id="warming">Warming</h2>
<p>Cold starts don&rsquo;t occur for the majority of requests because AWS Lambda reuses workers between subsequent invocations. However, if a particular worker is idle for too long (usually, several minutes), AWS may decide to recycle and return it to the generic pool.</p>
<p>A trick known as <strong>Lambda Warmers</strong> uses kept-alive workers to reduce the frequency of cold starts. The idea is to have a CloudWatch timer that fires every few minutes and sends <code>N</code> parallel requests to the target Lambda. If all those requests land at the same time, AWS has to provision at least <code>N</code> workers to process them. The actual operation doesn&rsquo;t have to do any useful work, but it resets the recycling timer back to zero.</p>
<p>There are a few drawbacks with this approach:</p>
<ul>
<li>If a valid request comes at the same time as warming requests, it might hit a cold start.</li>
<li>Extra logic is needed in the Lambda code to detect warming requests and short-circuit them instead of doing useful work.</li>
<li>CloudWatch rules require additional setup and management.</li>
<li>The result is still best-effort: Lambda still recycles workers from time to time.</li>
</ul>
<p>You can find more details about how Pulumi can help with some of these issues in <a href="https://mikhail.io/2018/08/aws-lambda-warmer-as-pulumi-component/">AWS Lambda Warmer as Pulumi Component</a>.</p>
<h2 id="provisioned-concurrency">Provisioned Concurrency</h2>
<p>At re:Invent 2019, AWS introduced Lambda <strong>Provisioned Concurrency</strong>&mdash;a feature to work around cold starts. The base concurrency model doesn&rsquo;t change. However, you can request a given number of workers to be always-warm and dedicated to a specific Lambda.</p>
<p>Here is an example of configuring the provisioned concurrency with Pulumi in TypeScript:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">lambda</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">lambda</span><span style="color:#1f2328">.</span><span style="color:#6639ba">Function</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;mylambda&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">code</span>: <span style="color:#cf222e">new</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">asset</span><span style="color:#1f2328">.</span><span style="color:#1f2328">FileArchive</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;./app&#34;</span><span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">handler</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;handler&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">role</span>: <span style="color:#cf222e">role.arn</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">publish</span>: <span style="color:#cf222e">true</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">fixed</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">lambda</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ProvisionedConcurrencyConfig</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;fixed-concurrency&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">functionName</span>: <span style="color:#cf222e">lambda.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">qualifier</span>: <span style="color:#cf222e">lambda.version</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">provisionedConcurrentExecutions</span>: <span style="color:#cf222e">2</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>The snippet sets the provisioned concurrency for <code>mylambda</code> to a fixed value of <code>2</code>. Note that concurrency is provisioned per Lambda version, and it can&rsquo;t be set for the <code>$LATEST</code> version alias. Therefore, I set the <code>publish</code> attribute of my Lambda to <code>true</code> and reference a specific version of the Lambda in provisioning.</p>
<h2 id="dynamic-provisioned-concurrency">Dynamic Provisioned Concurrency</h2>
<p>A fixed level of provisioned concurrency works well for stable workloads.</p>
<figure >
    
        <img src="steady.png"
            alt="Fixed provisioned concurrency for uniform workloads"
             />
        
    
    <figcaption>
        <h4>Fixed provisioned concurrency for uniform workloads</h4>
    </figcaption>
    
</figure>
<p>However, many workloads fluctuate a lot. Extreme elasticity and lack of configuration parameters have always been the essential benefits of AWS Lambda. It works great if you can tolerate the cold starts that come during scale-out. If not, you can explore more advanced scenarios for provisioning concurrency dynamically.</p>
<p>Instead of choosing a permanently fixed value, you can configure provisioned concurrency to autoscale. The first required component is the autoscaling target:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">resourceId</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">interpolate</span><span style="color:#0a3069">`function:</span><span style="color:#0a3069">${</span><span style="color:#1f2328">lambda</span><span style="color:#1f2328">.</span><span style="color:#1f2328">name</span><span style="color:#0a3069">}</span><span style="color:#0a3069">:</span><span style="color:#0a3069">${</span><span style="color:#1f2328">lambda</span><span style="color:#1f2328">.</span><span style="color:#1f2328">version</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">target</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appautoscaling</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Target</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;target&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceId</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">serviceNamespace</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;lambda&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">scalableDimension</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;lambda:function:ProvisionedConcurrency&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">minCapacity</span>: <span style="color:#cf222e">1</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">maxCapacity</span>: <span style="color:#cf222e">10</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>The second component is the autoscaling policy, which defines the conditions when scaling starts. There are two important ways to adapt your concurrency provisioning to traffic patterns.</p>
<h3 id="scheduled-profile">Scheduled profile</h3>
<p>Quite often, increases in request rates are partially predictable. For example, usage increases during business hours and decreases at night.</p>
<figure >
    
        <img src="scheduled.png"
            alt="Provisioned concurrency matches predictable workload changes"
             />
        
    
    <figcaption>
        <h4>Provisioned concurrency matches predictable workload changes</h4>
    </figcaption>
    
</figure>
<p>The following snippet sets two scheduled rules that switch between two levels of provisioned concurrency every day.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">function</span> <span style="color:#1f2328">scheduledConcurrency</span><span style="color:#1f2328">(</span><span style="color:#1f2328">name</span>: <span style="color:#cf222e">string</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">cron</span>: <span style="color:#cf222e">string</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">capacity</span>: <span style="color:#cf222e">number</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">return</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appautoscaling</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ScheduledAction</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`schedule-</span><span style="color:#0a3069">${</span><span style="color:#1f2328">name</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">resourceId</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">serviceNamespace</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;lambda&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">scalableDimension</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;lambda:function:ProvisionedConcurrency&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">scalableTargetAction</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">minCapacity</span>: <span style="color:#cf222e">capacity</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">maxCapacity</span>: <span style="color:#cf222e">capacity</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">schedule</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">`cron(</span><span style="color:#0a3069">${</span><span style="color:#1f2328">cron</span><span style="color:#0a3069">}</span><span style="color:#0a3069">)`</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">dependsOn</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span><span style="color:#1f2328">target</span><span style="color:#1f2328">]});</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">scheduledConcurrency</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;day&#34;</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;0 8 * * ? *&#34;</span><span style="color:#1f2328">,</span> <span style="color:#0550ae">10</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">scheduledConcurrency</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;night&#34;</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;0 18 * * ? *&#34;</span><span style="color:#1f2328">,</span> <span style="color:#0550ae">2</span><span style="color:#1f2328">);</span>
</span></span></code></pre></div><p>The example defines a helper function and calls it twice to set two schedules with different parameters.</p>
<h3 id="autoscaling-based-on-utilization">Autoscaling based on utilization</h3>
<p>If your workload pattern is less predictable, you can configure autoscaling for provisioned concurrency based on the measured utilization.</p>
<figure >
    
        <img src="variable.png"
            alt="Provisioned concurrency matches the variation in workload"
             />
        
    
    <figcaption>
        <h4>Provisioned concurrency matches the variation in workload</h4>
    </figcaption>
    
</figure>
<p>Here is a basic example of a dynamic scaling policy.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">scaledConcurrency</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appautoscaling</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Policy</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;autoscaling&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceId</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">serviceNamespace</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;lambda&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">scalableDimension</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;lambda:function:ProvisionedConcurrency&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">policyType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;TargetTrackingScaling&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">targetTrackingScalingPolicyConfiguration</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">targetValue</span>: <span style="color:#cf222e">0.8</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">predefinedMetricSpecification</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">predefinedMetricType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;LambdaProvisionedConcurrencyUtilization&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">},</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">dependsOn</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span><span style="color:#1f2328">target</span><span style="color:#1f2328">]});</span>
</span></span></code></pre></div><p>Currently, there are issues with autoscaling based on the metrics, which makes provisioned concurrency scale less effectively than what the chart above shows. Hopefully, Amazon will fix these issues soon.</p>
<h2 id="pricing">Pricing</h2>
<p>While hand-crafted Lambda warmers are virtually free, provisioned concurrency can be costly. The new pricing is an integral part of the change: Instead of purely per-call model, AWS charges per hour for provisioned capacity.</p>
<p>You would pay $0.015 per hour per GB of provisioned worker memory, even if a worker handled zero requests.</p>
<p>The per-invocation price gets a discount: $0.035 per GB-hour instead of the regular $0.06 per GB-hour. This change means that fully-utilized workers would be cheaper if provisioned compared to on-demand workers.</p>
<figure >
    
        <img src="pricing.png"
            alt="Comparison of the cost of a 1GB worker for two billing models"
             />
        
    
    <figcaption>
        <h4>Comparison of the cost of a 1GB worker for two billing models</h4>
    </figcaption>
    
</figure>
<p>The equilibrium point is at 60% utilization. Note that because the billed duration is rounded up to the nearest 100 ms for each execution, the utilization is not limited to 100%. A series of sequential executions can be processed by a single worker. If each execution is 10 ms, the charge is still 100 ms, and the total utilization can be as high as 1000% in terms of the chart above!</p>
<p>Finally, be careful to clean up your resources after any experiments: Leaving running workers with provisioned concurrency can be expensive! Using <code>pulumi destroy</code> removes resources after you finish experimenting.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Provisioned concurrency brings the long-awaited solution to cold starts in AWS Lambda. However, it comes at a cost in terms of a direct impact on billing and management overhead to provision concurrency at optimal levels.</p>
<p>For high-load functions where utilization is continuously above a known level of requests, it makes sense to set the provisioned concurrency to that level to save money and have the guarantee of warm workers.</p>
<p>If your Lambda hosts a latency-sensitive API, especially with runtimes like Java and .NET, you should strive to find the right balance between the percentage of requests that result in a cold start and the money spent on concurrency. Autoscaling should help once AWS has fixed the initial glitches that slipped into their current services.</p>
<p>If you want to try this for yourself, we&rsquo;ve updated the <a href="https://github.com/pulumi/examples/blob/master/aws-ts-serverless-raw/">Serverless App example</a> to show off the scenario of configurable AWS Lambda provisioned concurrency.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/aws" term="aws" label="AWS" />
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/aws-lambda" term="aws-lambda" label="AWS Lambda" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Santa Brings Cloud to Every Developer]]></title>
            <link href="https://mikhail.io/2019/12/santa-brings-cloud-to-every-developer/"/>
            <id>https://mikhail.io/2019/12/santa-brings-cloud-to-every-developer/</id>
            
            <published>2019-12-01T00:00:00+00:00</published>
            <updated>2019-12-01T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>How Santa Cloud uses F# and Pulumi to bring cloud resources to the homes of software engineers.</blockquote><p><em>The post is a part of
<a href="https://sergeytihon.com/2019/11/05/f-advent-calendar-in-english-2019/">F# Advent Calendar 2019</a>.
It&rsquo;s Christmas time!</em></p>
<p>Cloud is everywhere, and yet it&rsquo;s still inaccessible to millions of developers and IT pros. With shining yet cloudy eyes return they from KubeCon&rsquo;s and re:Invent&rsquo;s, just to pick up an issue in Jira, fix yet another <code>SingletonProxyFactoryBean</code> in their J2EE application, commit it to SVN, and hope it will be delivered in three months to the data center in their HQ basement.</p>
<p>But hey, it&rsquo;s December now, which means it&rsquo;s time to write a letter to Santa Cloud! Santa Cloud brings gifts to the homes of well-behaved engineers on the night of Christmas Eve. He accomplishes this feat with the aid of his elves, who make the toys in their glorious workshops in the State of Washington.</p>
<p><img src="elves.jpg" alt="Cloud Elves"></p>
<p>Here are some typical letters that Santa gets these days:</p>
<blockquote>I maintain 15 different apps, and each one has a custom deployment workflow. If only I had a <b>Kubernetes</b> cluster, I would stick all the apps in it and let the blue/green deployments do the magic!</blockquote>
<blockquote>My ten-year-old legacy application was designed for twenty users, and now it has to handle twenty thousand. It reached its performance limits! If only I could port it to <b>Serverless</b>, this would fix all my scalability issues.</blockquote>
<blockquote>
<p>We have 2 TB of data to analyze! Could you please send me a data lake and a <strong>Spark</strong> cluster? Actually, can I have two&hellip; or maybe three?</p></blockquote>
<p>There&rsquo;s enough cloud for everyone! Perhaps, a naughty engineer, who created a lot of technical debt and meeting invites, might only get a cozy single-core VM for their LAMP-stack website. But the lucky ones might get a whole Kubernetes cluster for themselves!</p>
<h2 id="behind-the-snow-curtains">Behind the Snow Curtains</h2>
<p>While cloud elves are good at provisioning the underlying hardware and providing services, Santa still needs to coordinate them to fulfill wishes. There are millions of aspiring cloud engineers, and all of them believe in Santa. It&rsquo;s not feasible to click the buttons in web portals or run ad-hoc scripts for each and every wishlist.</p>
<p>Luckily, Santa Cloud is good at programming and automation. He codes in <a href="https://fsharp.org">F#</a>, and he&rsquo;s one of the early customers of the .NET SDK for <a href="https://pulumi.com">Pulumi</a>. Hey, it&rsquo;s my fairy tale, so I choose the tech stack, thank you. Worry not, even if you are not familiar with F# or Pulumi, you will be able to follow along.</p>
<p>The workflow looks approximately like this:</p>
<ul>
<li>Represent a letter as a value in the program</li>
<li>Pick an elf which can fulfill the desire</li>
<li>Produce the resource</li>
<li>Wrap a note to the recipient</li>
</ul>
<h3 id="parsing-wishlists">Parsing wishlists</h3>
<p>Too bad, not every wishful engineer knows F#. Maybe, next year, Santa can make an online form that wouldn&rsquo;t accept anything but valid code. For now, it&rsquo;s the duty of Mrs. Cloud to read every letter and convert it to a list of records. Here is a sample:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">wishes</span> <span style="color:#0550ae">=</span> <span style="color:#0550ae">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">{</span> Recipient <span style="color:#0550ae">=</span> Person <span style="color:#0a3069">&#34;Kelsey&#34;</span> <span style="color:#0550ae">;</span> Resource <span style="color:#0550ae">=</span> Kubernetes <span style="color:#0550ae">(</span>Nodes 25<span style="color:#0550ae">)</span> <span style="color:#0550ae">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">{</span> Recipient <span style="color:#0550ae">=</span> Person <span style="color:#0a3069">&#34;Jeff&#34;</span>   <span style="color:#0550ae">;</span> Resource <span style="color:#0550ae">=</span> Serverless DotNet <span style="color:#0550ae">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">{</span> Recipient <span style="color:#0550ae">=</span> Person <span style="color:#0a3069">&#34;Satoshi&#34;</span><span style="color:#0550ae">;</span> Resource <span style="color:#0550ae">=</span> Blockchain Public <span style="color:#0550ae">}</span>
</span></span><span style="display:flex;"><span><span style="color:#0550ae">]</span>
</span></span></code></pre></div><p>The value <code>wishes</code> is a strongly-typed list of records. Each record has multiple properties and utilizes &ldquo;algebraic data types&rdquo;: each type would have a label (&ldquo;Kubernetes&rdquo;) and an associated piece of data (25 nodes). The shape of data depends on the label: it&rsquo;s not possible to ask 25 nodes of serverless.</p>
<p>When the list is defined and compiled, it goes to production. But first, we need to assign an elf to each item in the list.</p>
<h3 id="elves">Elves</h3>
<p>All-powerful craftselves are represented as another F# type: a function type.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">type</span> <span style="color:#1f2328">Elf</span> <span style="color:#0550ae">=</span> Wish <span style="color:#0550ae">-&gt;</span> <span style="color:#0550ae">(</span><span style="color:#cf222e">unit</span> <span style="color:#0550ae">-&gt;</span> Identifier<span style="color:#0550ae">)</span> option
</span></span></code></pre></div><p>Here is what it tells us:</p>
<ul>
<li>It&rsquo;s a function which expects a <code>Wish</code> as an input parameter</li>
<li>It may return nothing if this elf can&rsquo;t fulfill the wish (represented by <code>option</code> type)</li>
<li>If it returns something, it returns yet another function&mdash;of type <code>unit -&gt; Identifier</code></li>
<li>This resulting function has a side effect (it accepts <code>unit</code>): the production of a cloud resource</li>
<li>It returns an <code>Identifier</code> of that resource</li>
</ul>
<p>Whoa, that&rsquo;s a lot of information conveyed by a single-line type definition!</p>
<p>In our program, each elf is a function, for example:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">aws</span> <span style="color:#0550ae">(</span>wish<span style="color:#0550ae">:</span> Wish<span style="color:#0550ae">)</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">match</span> wish<span style="color:#0550ae">.</span>Resource <span style="color:#cf222e">with</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">|</span> Kubernetes k8s <span style="color:#0550ae">-&gt;</span> makeEksCluster k8s <span style="color:#0550ae">|&gt;</span> Some
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">|</span> Serverless s8s <span style="color:#0550ae">-&gt;</span> makeAwsLambda s8s  <span style="color:#0550ae">|&gt;</span> Some
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">//|  ... more resource types
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>    <span style="color:#0550ae">|</span> <span style="color:#0550ae">_</span> <span style="color:#0550ae">-&gt;</span> None
</span></span></code></pre></div><p>The function matches the type of the desired resource and calls a corresponding resource provisioning routine.</p>
<h3 id="resource-provisioning">Resource provisioning</h3>
<p>Pulumi handles the creation of cloud resources with its .NET SDKs spanning across all major cloud providers.</p>
<p>Here is a snippet which defines an Azure Function App:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">makeFunctionApp</span> <span style="color:#0550ae">(</span>Person name<span style="color:#0550ae">)</span> <span style="color:#0550ae">(</span>runtime<span style="color:#0550ae">:</span> Runtime<span style="color:#0550ae">)</span> <span style="color:#6a737d">()</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">app</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>        FunctionApp<span style="color:#0550ae">(</span>name<span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>            FunctionAppArgs
</span></span><span style="display:flex;"><span>               <span style="color:#0550ae">(</span>ResourceGroupName <span style="color:#0550ae">=</span> io resourceGroup<span style="color:#0550ae">.</span>Name<span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                AppSettings <span style="color:#0550ae">=</span> inputMap <span style="color:#0550ae">[</span><span style="color:#0a3069">&#34;runtime&#34;</span><span style="color:#0550ae">,</span> runtime<span style="color:#0550ae">.</span>ToString<span style="color:#6a737d">()</span> <span style="color:#0550ae">|&gt;</span> input<span style="color:#0550ae">],</span>
</span></span><span style="display:flex;"><span>                StorageConnectionString <span style="color:#0550ae">=</span> io storageAccount<span style="color:#0550ae">.</span>PrimaryConnectionString<span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                Version <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;~2&#34;</span><span style="color:#0550ae">))</span>
</span></span><span style="display:flex;"><span>    Url app<span style="color:#0550ae">.</span>DefaultHostname
</span></span></code></pre></div><p>And here is a Google Kubernetes Engine cluster:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">makeGkeCluster</span> <span style="color:#0550ae">(</span>Person name<span style="color:#0550ae">)</span> <span style="color:#0550ae">(</span>Nodes nodeCount<span style="color:#0550ae">)</span> <span style="color:#6a737d">()</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">masterVersion</span> <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;1.15.1&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#953800">cluster</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>        Cluster<span style="color:#0550ae">(</span>name<span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>            ClusterArgs
</span></span><span style="display:flex;"><span>               <span style="color:#0550ae">(</span>InitialNodeCount <span style="color:#0550ae">=</span> input nodeCount<span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                MinMasterVersion <span style="color:#0550ae">=</span> masterVersion<span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                NodeVersion <span style="color:#0550ae">=</span> masterVersion<span style="color:#0550ae">,</span>
</span></span><span style="display:flex;"><span>                NodeConfig <span style="color:#0550ae">=</span> input <span style="color:#0550ae">(</span>
</span></span><span style="display:flex;"><span>                    ClusterNodeConfigArgs
</span></span><span style="display:flex;"><span>                       <span style="color:#0550ae">(</span>MachineType <span style="color:#0550ae">=</span> input <span style="color:#0a3069">&#34;n1-standard-1&#34;</span><span style="color:#0550ae">))))</span>
</span></span><span style="display:flex;"><span>    Url cluster<span style="color:#0550ae">.</span>Endpoint
</span></span></code></pre></div><h3 id="production">Production</h3>
<p>The application logic of picking the right production facility and executing the order takes five lines of code.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">fulfill</span> wish <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">// Randomize the order of elves, so that they get equal opportunities
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>    <span style="color:#cf222e">let</span> <span style="color:#953800">elves</span> <span style="color:#0550ae">=</span> shuffle <span style="color:#0550ae">[</span>aws<span style="color:#0550ae">;</span> azure<span style="color:#0550ae">;</span> gcp<span style="color:#0550ae">]</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">// Find the first elf which agrees to produce a resource
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>    <span style="color:#cf222e">let</span> <span style="color:#953800">make</span> <span style="color:#0550ae">=</span> elves <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">List</span><span style="color:#1f2328">.</span>pick <span style="color:#0550ae">(</span><span style="color:#cf222e">fun</span> elf <span style="color:#0550ae">-&gt;</span> elf wish<span style="color:#0550ae">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">// Run the production!
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>    <span style="color:#cf222e">let</span> <span style="color:#953800">identifier</span> <span style="color:#0550ae">=</span> make<span style="color:#6a737d">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    wish<span style="color:#0550ae">.</span>Recipient<span style="color:#0550ae">,</span> identifier
</span></span></code></pre></div><p>We shuffle the order of elves to give them comparable workload, pick the first one in the list who agrees to execute the order, run the provisioning, and return the recipient name with their shiny resource identifier.</p>
<p>Go team Santa!</p>
<h3 id="delivery">Delivery</h3>
<p>For a nice personal touch, a card needs to be printed, gift-wrapped, and delivered by Santa and reindeers. Programming the reindeers is for another blog post. For now, we just return a list of tuples of person names and resource URLs:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">send</span> <span style="color:#0550ae">(</span>responses<span style="color:#0550ae">:</span> Response <span style="color:#cf222e">list</span><span style="color:#0550ae">)</span> <span style="color:#0550ae">=</span>
</span></span><span style="display:flex;"><span>    responses
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">|&gt;</span> <span style="color:#24292e">List</span><span style="color:#1f2328">.</span>map <span style="color:#0550ae">(</span><span style="color:#cf222e">fun</span> <span style="color:#0550ae">(</span>Person name<span style="color:#0550ae">,</span> Url url<span style="color:#0550ae">)</span> <span style="color:#0550ae">-&gt;</span> name<span style="color:#0550ae">,</span> url<span style="color:#0550ae">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">|&gt;</span> dict
</span></span></code></pre></div><p>Finally, another line combines the steps into the workflow:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">santaCloud</span> <span style="color:#6a737d">()</span> <span style="color:#0550ae">=</span> wishes <span style="color:#0550ae">|&gt;</span> <span style="color:#0550ae">(</span><span style="color:#24292e">List</span><span style="color:#1f2328">.</span>map fulfill<span style="color:#0550ae">)</span> <span style="color:#0550ae">|&gt;</span> send
</span></span></code></pre></div><p>And the last line defines the entry point of the application:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fsharp" data-lang="fsharp"><span style="display:flex;"><span><span style="color:#0550ae">[&lt;</span>EntryPoint<span style="color:#0550ae">&gt;]</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#953800">main</span> <span style="color:#0550ae">_</span> <span style="color:#0550ae">=</span> <span style="color:#24292e">Deployment</span><span style="color:#1f2328">.</span>run santaCloud
</span></span></code></pre></div><p>Running the program produces an output like this:</p>
<p><img src="pulumiup.png" alt="Pulumi Console Output"></p>
<h2 id="retrospective">Retrospective</h2>
<p>Honestly, the system is brand-new this year, so we still need to see how it plays out on Christmas Eve. Regardless, we learned a thing or two today:</p>
<ul>
<li><strong>Types</strong>: F# has an excellent type system that allows expressing domain terms concisely and precisely.</li>
<li><strong>Logic</strong>: The program workflow is expressed in terms of function calls, pattern matching, and collections manipulation.</li>
<li><strong>Infrastructure</strong>: Cloud resources are defined in F# with Pulumi .NET SDK, which supports pretty much any resource in any cloud.</li>
<li><strong>Application</strong>: Blended together, these components enable writing expressive, cohesive, maintainable, cloud-native applications using familiar and loved tools and practices.</li>
</ul>
<p>Learn more about <a href="https://pulumi.com/dotnet">Pulumi for .NET</a> or browse <a href="https://github.com/mikhailshilkov/fsharp-advent-pulumi/tree/master/2019">the full code</a> for this example.</p>
<p>Merry Cloudsmas, ho-ho-ho!</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                             
                                <category scheme="https://mikhail.io/tags/fsharp" term="fsharp" label="FSharp" />
                             
                                <category scheme="https://mikhail.io/tags/aws" term="aws" label="AWS" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/gcp" term="gcp" label="GCP" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Choosing the Best Deployment Tool for Your Serverless Applications]]></title>
            <link href="https://mikhail.io/2019/11/choosing-the-best-deployment-tool-for-serverless-applications/"/>
            <id>https://mikhail.io/2019/11/choosing-the-best-deployment-tool-for-serverless-applications/</id>
            
            <published>2019-11-12T00:00:00+00:00</published>
            <updated>2019-11-12T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Factors to consider while deploying cloud infrastructure for serverless apps.</blockquote><p>Function-as-a-Service solutions, such as AWS Lambda and Azure Functions, are a great way to build modern, scalable, and affordable cloud-native applications. By delegating routine work to cloud providers, serverless applications focus on custom logic to provide the ultimate business value. But, in fact, serverless is more than cloud functions. It&rsquo;s storage, HTTP gateways, databases, queues, monitoring, and security&mdash;and your serverless applications are likely to use multiple managed services and consist of many moving parts.</p>
<p>So how do you deploy such applications reliably? There are multiple cloud deployment tools out there! In this article, I identify several criteria to consider when making your decision.</p>
<h2 id="point-and-click-or-write-code">Point-and-Click or Write Code?</h2>
<p>Most tutorials and getting-started guides use web portals, like AWS Management Console or Microsoft Azure portal, to create basic serverless applications and required infrastructure. The visual flow of solutions like these is simple for newcomers to understand, and they can take shortcuts and assume some sensible defaults to streamline the process.</p>
<p>However, simply clicking through the online wizard won&rsquo;t produce reliable outcomes. Two weeks later, for example, you might not be able to follow the exact same sequence of steps to create a copy of your application when you need it. Now imagine if your colleague has to try!</p>
<p>Instead of the manual provisioning process, you can define the infrastructure as code. I&rsquo;m using “code” in a broad sense here: It can be any machine-readable format that is written by a human and executed by an automated tool.</p>
<p>I strongly recommend using the infrastructure-as-code approach for any application that&rsquo;s beyond simple demos and one-off trials. Put the code definition to the source control, and you&rsquo;ll get repeatable deployments, documentation, a history of changes, and reference materials&mdash;all in one step.</p>
<h2 id="procedural-or-desired-state">Procedural or Desired State?</h2>
<p>Bash or PowerShell scripts are the traditional way to automate. Every cloud provider has a command-line interface (CLI) to manage its resource portfolio, so it&rsquo;s entirely possible to script the provisioning of the entire infrastructure for your serverless application.</p>
<p>However, there are a few downsides to this approach. Naturally, scripts are very imperative: You describe the exact steps to execute, in the required order. And if you need to change the state of an existing environment, you&rsquo;ll likely need to manage update scripts that bring the infrastructure through transition steps. Finally, it&rsquo;s hard to manage failures: If a script fails in the middle, the exact state of your resources will often be unknown.</p>
<p>You can also try the declarative style of Desired State Configuration (DSC). The goal of DSC is to describe the complete state of the infrastructure, regardless of its current state. Then, it functions as an automated tool which reads the desired state, compares it to the current state, and figures out which steps to execute to reconcile the two.</p>
<p>All the tools that I mention in the rest of the article follow the desired-state philosophy.</p>
<h2 id="cloud-vendor-or-third-party">Cloud Vendor or Third Party?</h2>
<p>Each cloud vendor has its own native tool to manage infrastructure as code: <a href="https://aws.amazon.com/cloudformation/">AWS CloudFormation</a>, <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/template-deployment-overview">Azure Resource Manager (ARM) templates</a>, and <a href="https://cloud.google.com/deployment-manager/">Google Cloud Deployment Manager</a>, for example. These are all first-class services, so they have excellent feature coverage and are widely used, stable, and battle-tested.</p>
<p>However, while the vendor tools are robust, they have a set of constraints. Obviously, each tool focuses on one cloud, so mastering CloudFormation won&rsquo;t make you an ARM guru. Also, the deployment managers are fundamental infrastructure components of their respective clouds. Because they need to support every service and feature, they tend to operate at a quite low level. Additionally, they must avoid breaking changes, so legacy features and issues tend to pile up over time. The tools may feel rigid, and because they are closed source and vendor managed, you don&rsquo;t have much influence.</p>
<p>It&rsquo;s worth noting that numerous third-party infrastructure management tools try to compete with cloud vendors, and serverless apps can successfully use many of them. <a href="https://www.terraform.io/">HashiCorp&rsquo;s Terraform</a>, for example, is a cross-provider tool, which covers not only the majority of each cloud&rsquo;s features, but also the configuration of Kubernetes, Docker images, Kafka, monitoring tools, and some databases.</p>
<p>The downside of third-party tools is that they may not offer support for some cloud features. For example, while using Terraform&rsquo;s AzureRM provider, I definitely bumped into missing features, especially for newer services or niche configurations. To compensate for these kinds of issues, third-party tools are usually open source and accept contributions, so an active community is an essential factor.</p>
<h2 id="execution-on-the-client-or-in-the-cloud">Execution on the Client or in the Cloud?</h2>
<p>Cloud vendor tools take your definition files and run them from within the managed cloud service. For example, Azure creates a first-class object&mdash;deployment&mdash;which shows the deployed resources or problems that occurred. CloudFormation can automatically roll back a failed deployment to the previous well-known state.</p>
<p>In contrast, CLI scripts, Terraform, and <a href="https://www.pulumi.com/">Pulumi</a> run the deployment from the client machine where they are executed, or, even better, from CI/CD servers for most production deployments. This means they have more granular control over the execution flow and error handling, but also that they aren&rsquo;t able to reuse the built-in deployment features of the native-cloud managers.</p>
<p>Some tools, like <a href="https://github.com/cloudtools/troposphere">Troposphere</a>, <a href="https://aws.amazon.com/cdk/">AWS Cloud Development Kit (AWS CDK)</a>, and <a href="https://serverless.com/">Serverless Framework</a>, have their own way to describe the infrastructure. Still, they transpile these definitions to formats like CloudFormation to make use of the cloud deployment engines.</p>
<h2 id="general-purpose-or-specialized">General Purpose or Specialized?</h2>
<p>CloudFormation, ARM templates, and Terraform are all general-purpose tools: You can define serverless applications with them, but that&rsquo;s not their primary focus. Traditional infrastructure and networking solutions still have a far larger audience, so these scenarios attract more attention from the vendors. General-purpose tools lack higher-level abstractions, so the resource definitions tend to be verbose and repetitive.</p>
<p>Instead of general-purpose tools, you can use a specialized solution that is inherently modeled around the concepts of serverless. Serverless Framework is a multi-provider tool, while <a href="https://aws.amazon.com/serverless/sam/">AWS Serverless Application Model (SAM)</a> is a comparable option from AWS. Another example is Claudia.js&mdash;a deployment tool for node-based AWS Lambda.</p>
<p>It&rsquo;s easy to get started with specialized tools, and to use them for implementing basic scenarios. However, if your application is a combination of serverless features and traditional infrastructure, you may need to use more than one tool. In this case, make sure that the benefits of the specialized tools are enough to compensate for the complexity of using multiple options simultaneously.</p>
<p>I struggled with trying to use Serverless Framework for Azure: The experience was not ideal, and some features were missing. Understandably, AWS is Serverless Framework&rsquo;s primary target, so plugins targeting other clouds may lag behind.</p>
<h2 id="markup-or-programming-language">Markup or Programming Language?</h2>
<p>If you settle for a desired-state configuration tool, you will likely use a markup language to define the cloud infrastructure. CloudFormation, Google Cloud Deployment Manager, and Serverless Framework use YAML, while ARM templates are defined in JSON. Terraform invented its own markup language called HCL. This language makes the definitions more succinct and expressive, but requires you to master a new language and toolchain.</p>
<p>However, markup languages are lacking expressiveness to easily represent conditional shapes, multiple similar resources, or higher-level abstractions. They embed home-grown constructs inside YAML or JSON, or make you fall back to template-based generation. A newer class of tools is trying to address this limitation by using general-purpose programming languages to define cloud infrastructures. For example, AWS CDK uses TypeScript, Python, Java, and .NET to produce CloudFormation output.</p>
<p>Pulumi takes this idea even further by providing support for a similar set of languages to provision infrastructure for any cloud&mdash;and many tools beyond the cloud. Developers can use their existing skills and tools to define type-safe and testable infrastructure and to create higher level abstractions with classes, components, and functions.</p>
<p>Both Pulumi and AWS CDK have a set of components focusing on serverless applications, so they might be the best tools for providing both high-level expressive serverless components and low-level infrastructure resources, if needed.</p>
<p>In the past, I deployed Azure resources with templates containing five thousand lines of JSON. It wasn&rsquo;t a great developer experience to write all those lines, and the application was mostly a bunch of web servers and SQL databases&mdash;nothing too fancy. When I define a similar infrastructure with TypeScript, the code size reduction is at least tenfold. <a href="https://mikhail.io/2019/02/from-yaml-to-typescript-developers-view-on-cloud-automation/">This post</a> gives an example of a similar transformation for an AWS serverless app.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Serverless applications consist of many components, combining multiple managed services into one cohesive flow. Regardless of your preference for a specific tool, you should use the infrastructure-as-code approach to make your serverless applications reliable and maintainable in the long run.</p>
<p>Cloud infrastructure tools are evolving quickly, and none of them are perfect just yet. To choose what&rsquo;s best for you, evaluate your needs and constraints, compare several options, and keep an eye on the field: More innovations are coming soon.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/aws" term="aws" label="AWS" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/aws-lambda" term="aws-lambda" label="AWS Lambda" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                             
                                <category scheme="https://mikhail.io/tags/infrastructure-as-code" term="infrastructure-as-code" label="Infrastructure as code" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[AWS Lambda vs. Azure Functions: 10 Major Differences]]></title>
            <link href="https://mikhail.io/2019/10/aws-lambda-vs-azure-functions-ten-major-differences/"/>
            <id>https://mikhail.io/2019/10/aws-lambda-vs-azure-functions-ten-major-differences/</id>
            
            <published>2019-10-20T00:00:00+00:00</published>
            <updated>2019-10-20T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>A comparison AWS Lambda with Azure Functions, focusing on their unique features and limitations.</blockquote><p>Forget about managing virtual machines or paying for idle hardware! Serverless compute brings unlimited scale and high availability to every company in the world, from small startups to multinational corporations. At least, that’s the vision of Amazon and Microsoft, today’s biggest cloud vendors.</p>
<p>AWS Lambda pioneered the Function as a Service (FaaS) application model in 2014. With Faas, a small piece of code—called a function—is deployed as a ZIP file and linked to a specific type of event, such as a queue or an HTTP endpoint. AWS runs this function every time a matching event occurs, be it once per day or a thousand times per second.</p>
<p>Since 2014, the serverless model has taken off, and every major cloud provider has introduced its flavor of an FaaS service: Azure Functions, Google Cloud Functions, and IBM Cloud Functions, to name a few. While the basic idea behind all the offerings is the same, there are many differences between these implementations.</p>
<p>Today, I’ll compare AWS Lambda with Azure Functions (Lambda’s equivalent in Azure cloud), focusing on their unique features and limitations. Here are the ten major differences between the two options.</p>
<h2 id="1-hosting-plans">1. Hosting Plans</h2>
<p>To put it simply, there is one way to run a serverless function in AWS: deploy it to the AWS Lambda service. Amazon’s strategy here is to make sure that this service covers as many customer scenarios as possible, ranging from hobby websites to enterprise-grade data processing systems.</p>
<p>Microsoft takes a different approach. They separated the notion of the Azure Functions programming model from the serverless operational model. With Azure Functions, I can deploy my functions to a pay-per-use, fully-managed Consumption plan. However, I can also use <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-scale">other hosting options</a> to run the same code:</p>
<ul>
<li>App Service plan: provides a predictable pay-per-hour price, but has limited auto-scaling behavior</li>
<li>Premium plan (preview): gives reserved capacity <em>and</em> elastic scaling, combined with advanced networking options, for a higher price</li>
<li>A Docker container: can run anywhere on self-managed infrastructure</li>
<li>Kubernetes-based event-driven architecture (KEDA, experimental): brings functions to Kubernetes, running in any cloud or on-premises</li>
</ul>
<p>The Consumption plan has the lowest management overhead and no fixed-cost component, which makes it the most serverless hosting option on the list. For that reason, I’m going to focus on AWS Lambda vs. Azure Functions Consumption plan for the rest of this article.</p>
<h2 id="2-configurability">2. Configurability</h2>
<p>When deploying <a href="https://docs.aws.amazon.com/lambda/latest/dg/resource-model.html">an AWS Lambda function</a>, I need to define the maximum memory allocation, which has to be between 128MB and 3GB. The CPU power and cost of running the function are proportional to the allocated memory. It takes a bit of experimentation to define the optimal size, depending on the workload profile. Regardless of size, all instances run on Amazon Linux.</p>
<p>On the other hand, Azure Functions Consumption plan is one-size-fits-all. It comes with 1.5GB of memory and one low-profile virtual core. You can choose between Windows and Linux as a host operating system.</p>
<p><a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-premium-plan#plan-and-sku-settings">Azure Functions Premium plan</a> comes with multiple instance sizes, up to 14GB of memory, and four vCPUs. However, you have to pay a fixed per-hour fee for the reserved capacity.</p>
<h2 id="3-programming-languages">3. Programming Languages</h2>
<p>AWS Lambda natively supports JavaScript, Java, Python, Go, C#, F#, PowerShell, and Ruby code.</p>
<p>Azure Functions has runtimes for JavaScript, Java, Python, C#, F#, and PowerShell (preview). Azure lacks Go and Ruby—otherwise, the language options are very similar.</p>
<h2 id="4-programming-models">4. Programming Models</h2>
<p>Specifics vary between runtimes, but, overall, AWS Lambda has a <a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-services.html">straightforward programming model</a>. A function receives a JSON object as input and may return another JSON as output. The event type defines the schema of those objects, which are documented and defined in language SDKs.</p>
<p>Azure Functions has a more sophisticated model based on <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings">triggers and bindings</a>. A trigger is an event that the function listens to. The function may have any number of input and output bindings to pull and/or push extra data at the time of processing. For example, an HTTP-triggered function can also read a document from Azure Cosmos DB and send a queue message, all done declaratively via binding configuration.</p>
<p>The implementation details differ per language runtime. The binding system provides extra flexibility, but it also brings some complexity, in terms of both API and configuration.</p>
<h2 id="5-extensibility">5. Extensibility</h2>
<p>One drawback of all FaaS services on the market is the limited set of supported event types. For example, if you want to trigger your functions from a Kafka topic, you are out of luck on both AWS and Azure.</p>
<p>Other aspects of serverless functions are more customizable. AWS Lambda defines a <a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html">concept of layers</a>: a distribution mechanism for libraries, custom runtimes to support other languages, and other dependencies.</p>
<p>Azure Functions enables open <a href="https://github.com/Azure/azure-webjobs-sdk-extensions/wiki/Binding-Extensions-Overview">binding extensions</a> so that the community can create new types of bindings and bring them into Function Apps.</p>
<h2 id="6-concurrency-and-isolation">6. Concurrency and Isolation</h2>
<p>Both services can run multiple (potentially thousands) executions of the same function simultaneously, each handling one incoming event.</p>
<p>AWS Lambda always reserves a separate instance for a single execution. Each execution has its exclusive pool of memory and CPU cycles. Therefore, the performance is entirely predictable and stable.</p>
<p>Azure Functions allocates multiple concurrent executions to the same virtual node. If one execution is idle waiting for a response from the network, other executions may use resources which would otherwise be wasted. However, resource-hungry executions may fight for the pool of shared resources, harming the overall performance and processing time.</p>
<h2 id="7-cost">7. Cost</h2>
<p>Serverless pricing is based on a pay-per-usage model. Both services have two cost components: pay-per-call and pay-per-GB*seconds. The latter is a metric combining execution time and consumed memory.</p>
<p>Moreover, the price tag for both services is almost exactly the same: $0.20 per million requests and $16 per million GB*seconds ($16.67 for AWS). One million executions running for 100 ms each and consuming 1GB of memory cost less than $2. Since AWS Lambda was the first on the market, I assume Microsoft just copied the numbers.</p>
<p>There are some differences in the details, though:</p>
<ul>
<li>AWS Lambda charges for full provisioned memory capacity, while Azure Functions measures the actual average memory consumption of executions.</li>
<li>If Azure Function’s executions share the instance, the memory cost isn’t charged multiple times, but shared between executions, which may lead to noticeable reductions.</li>
<li>Both services charge for at least 100 ms and 128MB for each execution. AWS rounds the time up to the nearest 100 ms, while Azure rounds up to 1 ms.</li>
<li>CPU profiles are different for Lambda and Functions, which may lead to different durations for comparable workloads.</li>
</ul>
<p>I wrote more on how to measure the cost of Azure Functions <a href="https://mikhail.io/2019/08/how-to-measure-the-cost-of-azure-functions/">here</a>.</p>
<h2 id="8-http-integration">8. HTTP Integration</h2>
<p>AWS Lambda used to require Amazon API Gateway to listen to HTTP traffic, which came at a massive additional cost. Recently, Amazon introduced <a href="https://aws.amazon.com/about-aws/whats-new/2018/11/alb-can-now-invoke-lambda-functions-to-serve-https-requests/">integration with Elastic Load Balancing</a>, which may be more cost efficient for high-load scenarios. However, the pricing is per hour, so good judgment is required.</p>
<p>Azure Functions comes with HTTP endpoint integration out of the box, and there is no additional cost for this integration.</p>
<h2 id="9-performance-and-scalability">9. Performance and Scalability</h2>
<p>AWS Lambda has been on the market longer than Azure Functions, and has a laser focus on the single-hosting model. Although there are no established industry-wide benchmarks, many claim that AWS Lambda is better for rapid scale-out and handling massive workloads, both for web APIs and queue-based applications. The bootstrapping delay effect—cold starts—are also less significant with Lambda.</p>
<p>Azure Functions has improved significantly in the last year or two, but Microsoft is still playing catch-up.</p>
<p>I published several comparison articles in the past:</p>
<ul>
<li><a href="https://mikhail.io/2019/serverless-at-scale-serving-stackoverflow-like-traffic/">Serverless at Scale: Serving StackOverflow-like Traffic</a></li>
<li><a href="https://mikhail.io/2018/11/from-0-to-1000-instances-how-serverless-providers-scale-queue-processing/">From 0 to 1000 Instances: How Serverless Providers Scale Queue Processing</a></li>
<li><a href="https://mikhail.io/serverless/coldstarts/">Cold Starts in Serverless Functions</a></li>
</ul>
<h2 id="10-orchestrations">10. Orchestrations</h2>
<p>Serverless functions are nanoservices: small blocks of code doing just one thing. The question of how to build large applications and systems out of those tiny pieces is still open, but some composition patterns already exist.</p>
<p>Both AWS and Azure have dedicated services for workflow orchestration: AWS Step Functions and Azure Logic Apps. Quite often, functions are used as steps in those workflows, allowing them to stay independent but still solve significant tasks.</p>
<p>In addition, <a href="https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview">Azure Durable Functions</a> is a library that brings workflow orchestration abstractions to code. It comes with several patterns to combine multiple serverless functions into stateful long-running flows. The library handles communication and state management robustly and transparently, while keeping the API surface simple.</p>
<h2 id="so-what-should-you-choose">So, What Should You Choose?</h2>
<p>AWS Lambda and Azure Functions are similar services, but the devil is in the details—and virtually every angle shows some essential distinctions between the two. My list of ten differences is certainly not exhaustive, and each aspect would need a separate article to cover it in full.</p>
<p>It’s unlikely that your choice will be driven purely by these differences. At the same time, whenever you have to choose one option over the other, or when <a href="https://www.iamondemand.com/blog/azure-user-heres-what-you-must-know-about-aws/">you switch between providers</a>, it’s crucial to adjust your thinking and practices to match the peculiarities.</p>
<p>In short, choose the option that fits you best!</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/aws" term="aws" label="AWS" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                             
                                <category scheme="https://mikhail.io/tags/aws-lambda" term="aws-lambda" label="AWS Lambda" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[How To Deploy a Function App with KEDA (Kubernetes-based Event-Driven Autoscaling)]]></title>
            <link href="https://mikhail.io/2019/10/how-to-deploy-a-function-app-with-keda/"/>
            <id>https://mikhail.io/2019/10/how-to-deploy-a-function-app-with-keda/</id>
            
            <published>2019-10-10T00:00:00+00:00</published>
            <updated>2019-10-10T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Hosting Azure Functions in Kubernetes: how it works and the simplest way to get started.</blockquote><p><strong>Azure Functions</strong> is a managed service for serverless applications in the Azure cloud. More broadly, Azure Functions is a runtime with multiple hosting possibilities. <strong>KEDA</strong> (<a href="https://cloudblogs.microsoft.com/opensource/2019/05/06/announcing-keda-kubernetes-event-driven-autoscaling-containers/">Kubernetes-based Event-Driven Autoscaling</a>) is an emerging option to host this runtime in <strong>Kubernetes</strong>.</p>
<p>In the first part of this post, I compare KEDA with cloud-based scaling and outline the required components. In the second part, I define infrastructure as code to deploy a sample KEDA application to an Azure Kubernetes Service (AKS) cluster.</p>
<p>The result is a fully working example and a high-level idea of how it works. Kubernetes expertise is not required!</p>
<h2 id="autoscaling-primer">Autoscaling Primer</h2>
<p>When you deploy an Azure Function, it runs within the Azure Functions runtime. The runtime is a host process which knows how to pull events from an <strong>event source</strong> (defined by the function trigger) and pass those to your function:</p>
<p><img src="runtime.png" alt="Azure Functions Runtime"></p>
<p>However, one instance of runtime rarely provides adequate processing capacity. If you only get one message per day, having an instance always running is wasteful. If you get thousands of events per second, one instance won’t be able to process all of them.</p>
<p>Automatic <strong>horizontal scaling</strong> solves the problem. At any point in time, several identical <strong>workers</strong> are crunching the events. The number N is optimized continuously to fit the current workload by adding new workers and removing underutilized ones.</p>
<figure >
    
        <img src="idea.png"
            alt="Components of an automatically scaled application"
             />
        
    
    <figcaption>
        <h4>Components of an automatically scaled application</h4>
    </figcaption>
    
</figure>
<p><strong>Instance Provisioner</strong> is an extra component in the auto-scaled system. It monitors the stream of metrics from the event source and decides to add or remove workers. A massive standby pool of idle generic workers provides the workforce. Such a generic worker pulls the <strong>artifact</strong> of the assigned function, plugs it into the runtime, and starts processing events.</p>
<h2 id="azure-functions-consumption-plan">Azure Functions Consumption Plan</h2>
<p><strong>Consumption Plan</strong> is the serverless hosting option where Azure manages all the scaling components internally. Let’s consider an example of an Azure Function triggered by a <strong>Storage Queue</strong>:</p>
<figure >
    
        <img src="functionapp.png"
            alt="Azure Functions Consumption Plan"
             />
        
    
    <figcaption>
        <h4>Azure Functions Consumption Plan</h4>
    </figcaption>
    
</figure>
<p>The deployment artifact is just the code packaged as a zip archive and uploaded to Blob Storage. <strong>Scale Controller</strong> is an internal Azure component that observes the target queue and allocates Function App instances based on the queue length fluctuations. Each instance bootstraps itself with a zip file, connects to the queue, and pulls messages to process.</p>
<p>The cloud provider manages all the components of the system, so developers can focus on writing business logic code. It can be <a href="/2019/08/ten-pearls-with-azure-functions-in-pulumi/">as simple as a JavaScript callback</a>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#1f2328">queue</span><span style="color:#1f2328">.</span><span style="color:#1f2328">onEvent</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;MyHandler&#34;</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">msg</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">console</span><span style="color:#1f2328">.</span><span style="color:#1f2328">log</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;New message: &#34;</span> <span style="color:#0550ae">+</span> <span style="color:#1f2328">JSON</span><span style="color:#1f2328">.</span><span style="color:#1f2328">stringify</span><span style="color:#1f2328">(</span><span style="color:#1f2328">msg</span><span style="color:#1f2328">));</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><h2 id="keda">KEDA</h2>
<p>Over the last few years, Kubernetes has gained traction across many industries. KEDA is provides a way to design and run event-driven applications inside a Kubernetes cluster. KEDA implements the autoscaling components in terms of Kubernetes tools.</p>
<p>The target Function App is packaged together with the Azure Functions runtime into a custom <strong>Docker image</strong> and published to a <strong>registry</strong>. A Kubernetes <strong>deployment</strong> utilizes that image and configures the parameters to connect it to the target event source (for instance, a queue):</p>
<p><img src="k8s-deployment.png" alt="Kubernetes Deployment"></p>
<p>The application can then run on one or many instances, or <strong>pods</strong> in Kubernetes terms. Kubernetes has a built-in component to scale out the pods: <a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/">Horizontal Pod Autoscaler</a> (HPA). By default, HPA makes scaling decisions based on the processor utilization of existing pods. CPU turns out to be a poor metric for event-driven applications: many workloads are not CPU-bound, so the scale-out won’t be aggressive enough to keep the queue empty. Therefore, KEDA introduces a <strong>ScaledObject</strong>&mdash;a <a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/">custom resource</a> which pulls metric values from the event source and feeds them as a custom metric to HPA:</p>
<figure >
    
        <img src="keda.png"
            alt="Kubernetes-based Event-Driven Autoscaling"
             />
        
    
    <figcaption>
        <h4>Kubernetes-based Event-Driven Autoscaling</h4>
    </figcaption>
    
</figure>
<p>At the time of the initial KEDA preview announcement and until Kubernetes version 1.16, Horizontal Pod Autoscaler wasn’t able to scale a deployment down to zero pods. Therefore, KEDA includes an extra controller to disable the deployment if the event source is empty and re-enable it when new events come in. This duty might be delegated back to HPA in the future versions.</p>
<p>Now, when we know what KEDA is and how it works, it’s time to deploy a Function App!</p>
<h2 id="deploy-a-function-app-with-keda-and-pulumi">Deploy a Function App with KEDA and Pulumi</h2>
<p>Here is the list of the components required to run a Function App in Kubernetes with KEDA:</p>
<ul>
<li>Azure Kubernetes Service cluster</li>
<li>Azure Container Registry</li>
<li>Helm chart keda-edge with KEDA service and custom resource definition</li>
<li>Azure Storage Queue as a sample event source</li>
<li>Docker image of the queue processor application</li>
<li>Deployment of this image</li>
<li>Scaled Object custom resource</li>
</ul>
<p>All these components can be defined and deployed within a single Pulumi program. Below I highlight the main blocks of the program. Navigate to the <a href="#reusable-components">following section</a> to look at a short and reusable component.</p>
<h3 id="aks-cluster">AKS Cluster</h3>
<p>KEDA can run on any Kubernetes cluster, but I choose to do so on managed AKS. My custom <code>AksCluster</code> component defines a cluster, including the required Active Directory and networking configuration. It makes multiple assumptions, so I only need to specify the main properties:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">resourceGroup</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">core</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ResourceGroup</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;keda-sample&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">aks</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">AksCluster</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;keda-cluster&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">kubernetesVersion</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;1.13.5&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">vmSize</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard_B2s&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">vmCount</span>: <span style="color:#cf222e">3</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p><code>aks.cluster</code> now has all the required output values, for instance, <code>aks.cluster.kubeConfigRaw</code> configuration.</p>
<h3 id="shared-cluster-components">Shared Cluster Components</h3>
<p>The next group of components needs to be deployed just once.</p>
<p>Azure Container Registry is not part of the AKS cluster. Its purpose is to host Docker images of applications.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">registry</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">containerservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Registry</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;registry&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">args.resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">adminEnabled</span>: <span style="color:#cf222e">true</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">sku</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Premium&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>A Helm chart <code>kedacore/keda-edge</code> deploys the KEDA service and the <code>ScaledObject</code> custom resource definition.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">keda</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">k8s</span><span style="color:#1f2328">.</span><span style="color:#1f2328">helm</span><span style="color:#1f2328">.</span><span style="color:#1f2328">v2</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Chart</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;keda-edge&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">repo</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;kedacore&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">chart</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;keda-edge&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">version</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;0.0.1-2019.07.24.21.37.42-8ffd9a3&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">values</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">logLevel</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;debug&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">},</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">providers</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">kubernetes</span>: <span style="color:#cf222e">aks.provider</span> <span style="color:#1f2328">}</span> <span style="color:#1f2328">});</span>
</span></span></code></pre></div><h3 id="event-source">Event Source</h3>
<p>KEDA supports multiple types of event sources, and the current list is available <a href="https://github.com/kedacore/keda#event-sources-and-scalers">here</a>. An event source is supported if there is a scaler which knows how to pull metrics out of it and turn them into a custom metric for HPA.</p>
<p>My example uses an Azure Storage Queue as the event source:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">storageAccount</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Account</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;kedasa&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">accountTier</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">accountReplicationType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;LRS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">queue</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Queue</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;kedaqueue&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">storageAccountName</span>: <span style="color:#cf222e">storageAccount.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><h3 id="docker-image">Docker Image</h3>
<p>The same Pulumi program is capable of building a Docker image and uploading it to the Container Registry.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">dockerImage</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">docker</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Image</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;image&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">imageName</span>: <span style="color:#cf222e">pulumi.interpolate</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">registry</span><span style="color:#1f2328">.</span><span style="color:#1f2328">loginServer</span><span style="color:#0a3069">}</span><span style="color:#0a3069">/</span><span style="color:#0a3069">${</span><span style="color:#1f2328">args</span><span style="color:#1f2328">.</span><span style="color:#1f2328">queue</span><span style="color:#1f2328">.</span><span style="color:#1f2328">name</span><span style="color:#0a3069">}</span><span style="color:#0a3069">:v1.0.0`</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">build</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">context</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;./functionapp&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">registry</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">server</span>: <span style="color:#cf222e">registry.loginServer</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">username</span>: <span style="color:#cf222e">registry.adminUsername</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">password</span>: <span style="color:#cf222e">registry.adminPassword</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>The image refers to the folder with a <code>Dockerfile</code> in it and uses the <code>registry</code> variables to fill the credentials.</p>
<h3 id="deployment">Deployment</h3>
<p>Now, we can define a Deployment which uses the Docker image to run our Function App on Kubernetes pods.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">appLabels</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">app</span>: <span style="color:#cf222e">name</span> <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">deployment</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">k8s</span><span style="color:#1f2328">.</span><span style="color:#1f2328">apps</span><span style="color:#1f2328">.</span><span style="color:#1f2328">v1</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Deployment</span><span style="color:#1f2328">(</span><span style="color:#1f2328">name</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">apiVersion</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;apps/v1&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">kind</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Deployment&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">metadata</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">labels</span>: <span style="color:#cf222e">appLabels</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">spec</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">selector</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">matchLabels</span>: <span style="color:#cf222e">appLabels</span> <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">template</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">metadata</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">labels</span>: <span style="color:#cf222e">appLabels</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">spec</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">containers</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#1f2328">name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#1f2328">image</span>: <span style="color:#cf222e">dockerImage.imageName</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#1f2328">env</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span> <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;queuename&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">value</span>: <span style="color:#cf222e">args.queue.name</span> <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#1f2328">envFrom</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span> <span style="color:#1f2328">secretRef</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span><span style="color:#1f2328">name</span>: <span style="color:#cf222e">secretQueue.metadata.name</span> <span style="color:#1f2328">}</span> <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">imagePullSecrets</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span> <span style="color:#1f2328">name</span>: <span style="color:#cf222e">args.service.registrySecretName</span> <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">},</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">provider</span>: <span style="color:#cf222e">aks.provider</span> <span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>The deployment refers to the secret value which stores the connection string to our target Storage Queue:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">secretQueue</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">k8s</span><span style="color:#1f2328">.</span><span style="color:#1f2328">core</span><span style="color:#1f2328">.</span><span style="color:#1f2328">v1</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Secret</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;queue-secret&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">data</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">queueConnectionString</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#cf222e">args.storageAccount.primaryConnectionString.apply</span><span style="color:#1f2328">(</span><span style="color:#1f2328">c</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">Buffer</span><span style="color:#1f2328">.</span><span style="color:#cf222e">from</span><span style="color:#1f2328">(</span><span style="color:#1f2328">c</span><span style="color:#1f2328">).</span><span style="color:#1f2328">toString</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;base64&#34;</span><span style="color:#1f2328">)),</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">},</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">provider</span>: <span style="color:#cf222e">aks.provider</span> <span style="color:#1f2328">});</span>
</span></span></code></pre></div><h3 id="scaled-object">Scaled Object</h3>
<p>Finally, we need to deploy an instance of the <code>ScaledObject</code> custom resource, which takes care of feeding the metrics from the queue to the Horizontal Pod Autoscaler.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">scaledObject</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">k8s</span><span style="color:#1f2328">.</span><span style="color:#1f2328">apiextensions</span><span style="color:#1f2328">.</span><span style="color:#1f2328">CustomResource</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;scaledobject&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">apiVersion</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;keda.k8s.io/v1alpha1&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">kind</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;ScaledObject&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">metadata</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">labels</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">deploymentName</span>: <span style="color:#cf222e">name</span> <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">spec</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">scaleTargetRef</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">deploymentName</span>: <span style="color:#cf222e">name</span> <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">triggers</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#cf222e">type</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;azure-queue&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">metadata</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                <span style="color:#cf222e">type</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;queueTrigger&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">connection</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;queueConnectionString&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">queueName</span>: <span style="color:#cf222e">args.queue.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;myQueueItem&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">},</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">provider</span>: <span style="color:#cf222e">aks.provider</span> <span style="color:#1f2328">});</span>
</span></span></code></pre></div><h2 id="reusable-components">Reusable Components</h2>
<p>We can simplify the program further by creating reusable Pulumi components for a Cluster, a Service, and a Function app. After those are done, here is all the code required to deploy a Kubernetes cluster with a Function App and KEDA.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#57606a">// Create an AKS K8s cluster
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">const</span> <span style="color:#1f2328">aks</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">AksCluster</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;keda-cluster&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">kubernetesVersion</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;1.13.5&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">vmSize</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard_B2s&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">vmCount</span>: <span style="color:#cf222e">3</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Deploy shared components of KEDA (container registry, kedacore/keda-edge Helm chart)
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">const</span> <span style="color:#1f2328">service</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">KedaService</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;keda-edge&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroup</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">k8sProvider</span>: <span style="color:#cf222e">aks.provider</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Deploy a Function App which subscribes to the Storage Queue
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">const</span> <span style="color:#1f2328">app</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">KedaStorageQueueHandler</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;queue-handler&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroup</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">storageAccount</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">queue</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">service</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">path</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;./functionapp&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>You can find the full code of the components, the sample program, and steps to run it in <a href="https://github.com/pulumi/examples/tree/master/azure-ts-aks-keda">this example</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>As of October 2019, the KEDA project is still in the experimental phase and should not be used for any production applications. Also, if the managed version of Azure Functions suits your needs, you should probably stick to that service. It requires less effort, provides high-level primitives, and enables unlimited elastic scaling.</p>
<p>However, if your company is betting on Kubernetes as the standard application platform, but you still see high value in event-driven fine-grained applications, KEDA might be an exciting option for the future.</p>
<p>Pulumi is a great tool to get a sample KEDA application running quickly and effortlessly. It combines Docker image creation, Kubernetes cluster provisioning, Helm chart installation, and KEDA deployment in the single program written in a familiar general-purpose language.</p>
<p>Get started with <a href="https://github.com/pulumi/examples/tree/master/azure-ts-aks-keda">Azure Kubernetes Service (AKS) Cluster and Azure Functions with KEDA</a>.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                             
                                <category scheme="https://mikhail.io/tags/kubernetes" term="kubernetes" label="Kubernetes" />
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[How To Build Globally Distributed Applications with Azure Cosmos DB and Pulumi]]></title>
            <link href="https://mikhail.io/2019/09/how-to-build-globally-distributed-applications-with-azure-cosmos-db-and-pulumi/"/>
            <id>https://mikhail.io/2019/09/how-to-build-globally-distributed-applications-with-azure-cosmos-db-and-pulumi/</id>
            
            <published>2019-09-24T00:00:00+00:00</published>
            <updated>2019-09-24T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>A reusable component to build highly-available, low-latency applications on Azure</blockquote><p>In a <a href="/2019/07/globally-distributed-serverless-application-in-100-lines-of-code/">previous blog post</a>, I shared how easy it is to create a globally distributed, highly-available, low-latency application with Azure Functions, Azure Cosmos DB, and Pulumi.</p>
<p>Today, I want to show how the same approach can be generalized for any cloud compute service, including containers, virtual machines, and serverless functions.</p>
<h2 id="e-commerce-example">E-commerce Example</h2>
<p>Let’s consider an example of an e-commerce website which targets a worldwide audience. The architecture of such a website should address several challenges.</p>
<h3 id="multiple-teams">Multiple Teams</h3>
<p>A large e-commerce website is a sophisticated software application and consists of many subcomponents. Many engineers in multiple teams work on it simultaneously.</p>
<p>A typical approach is to organize engineers into smaller teams and make each team autonomous and responsible for one or several business domains. This strategy leads to microservices architecture when the application is built out of loosely coupled components. Each component represents a &ldquo;vertical slice&rdquo;: a team owns functionality across the vertical stack, from its user interface and service API to the data storage layer and underlying infrastructure.</p>
<p>Here is an example of such a breakdown:</p>
<p><img src="website.png" alt="Multiple components on a single web page"></p>
<figcaption>
    <h4>
        Multiple components on a single web page
    </h4>
</figcaption>
<h3 id="diversity-of-technology-decisions">Diversity of Technology Decisions</h3>
<p>One of the guiding principles of microservices architecture is that each team makes decisions regarding which tools and technologies to use. They are free to choose programming languages, databases, or cloud services that are the best fit for the service. In practice, some restrictions do apply&mdash;for instance, the teams might need to share the same cloud provider.</p>
<p>In our hypothetical example, the whole product is hosted on Azure cloud, however other aspects differ:</p>
<ul>
<li>Product Page team runs JavaScript serverless functions and stores data in a document database</li>
<li>Shopping Cart team uses C# and .NET Core, packages the code in a Docker container, and uses event sourcing to store every action ever made by users while shopping</li>
<li>Pricing Engine team has a mix of Java and C++ code with a bunch of third-party libraries and deploys the services to Ubuntu VMs that use MongoDB drivers for storage</li>
</ul>
<h3 id="global-presence">Global Presence</h3>
<p>Regardless of the tech, the user-facing services must serve customers across the globe and serve them fast. This requirement demands a geo-distributed infrastructure so that user requests could be handled by the nearest data center to minimize the latency and ensure excellent user experience.</p>
<p>Each team has to solve the following problems in a way which fits their tech stack:</p>
<ul>
<li>Distribute the data across the globe</li>
<li>Run their compute workload in each region next to the data</li>
<li>Manage all this infrastructure in a reliable way</li>
</ul>
<p>Let&rsquo;s consider these goals and possible solutions.</p>
<h2 id="azure-cosmos-db">Azure Cosmos DB</h2>
<p>Distributing data across many data centers while keeping them consistent and available is one of the hardest problems in computer science and engineering. Delegating this challenge to a specialized service or product is usually a sensible idea.</p>
<p>Azure Cosmos DB is the database of choice for global applications running in the Azure cloud. Cosmos DB provides a turn-key global distribution to any number of regions worldwide. Regions can be added or removed along the way while running production workloads and without having any impact on high availability. The accounts can be provisioned with a single write region or in multi-master mode with every region being writable, thus enabling writes that are local to each region.</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="distributed-cosmos.svg"
            alt="Azure Cosmos DB with multiple locations"
             />
        
    
    <figcaption>
        <h4>Azure Cosmos DB with multiple locations</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>Cosmos DB supports multiple models of data: key-value, document, column family, graph, with wire-protocol compatible APIs for MongoDB, Apache Cassandra, SQL, etcd, Gremlin, etc. It also supports multiple well-defined and practical consistency levels, from eventual to strong consistency, so that each application can strike the right balance between latency, availability, and consistency guarantees. All of this is offered as a fully-managed service, with the comprehensive SLAs covering latency, throughput, consistency, and 99.999% high availability for both reads and writes.</p>
<p>Sounds great, but how do we build applications to leverage this power?</p>
<h2 id="distributed-web-applications">Distributed Web Applications</h2>
<p>In contrast to Cosmos accounts, compute services in Azure are located in a single region. This holds true for Virtual Machines, Container Instances, Serverless Functions, and managed Azure Kubernetes Service.</p>
<p>If an application is deployed to a single location, we are not making good use of geographic redundancy of the database. If a user happens to be far from the application region, their request has to travel across the globe. Moreover, the application connects to the nearest region of Cosmos DB, while all other regions stay idle:</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="distributed-cosmos-single-app.svg"
            alt="Application deployed in a single location"
             />
        
    
    <figcaption>
        <h4>Application deployed in a single location</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>Instead, we should deploy a copy of application infrastructure to each of the target regions:</p>
</div>
</div>
</div>
<div class="row no-gutter">
    <div class="col-lg-1 pl-0"></div>
    <div class="col-sm-11">
        
<figure >
    
        <img src="distributed-app.svg"
            alt="Application deployed in multiple locations"
             />
        
    
    <figcaption>
        <h4>Application deployed in multiple locations</h4>
    </figcaption>
    
</figure>

    </div>
</div>
<div class="row">
    <div class="col-lg-2 pl-0"></div>
    <div class="col-lg-9 flex-first flex-lg-unordered">
        <div class="article-post">
<p>Each application instance has to be configured to connect to the endpoint of the Cosmos DB in the same region to enjoy local single-digit-milliseconds latency for reads and writes.</p>
<p>While beneficial to end-users, such setup brings much extra complexity in terms of infrastructure management:</p>
<ul>
<li>Application regions must stay in sync with Cosmos DB regions, including the correct configuration of preferred locations.</li>
<li>With the growth of the number of regions, the traditional capacity planning gets harder and harder. This makes a dynamic auto-scaling configuration increasingly important.</li>
<li>A central routing service must be configured to flow the traffic from end-users to the closest application instance.</li>
<li>Each team must execute all these tasks, independently but coherently.</li>
</ul>
<p>Let’s see how infrastructure as code and a general-purpose programming language come to the rescue.</p>
<h2 id="search-for-a-reusable-abstraction">Search for a Reusable Abstraction</h2>
<p>In our example, three teams have a common goal of providing high-quality service to customers around the world, but they differ in the compute services they want to use.</p>
<p>Product Card team relies on serverless functions, so their infrastructure may look like this:</p>
<p><img src="functions.png" alt="Global application with Azure Functions"></p>
<figcaption>
    <h4>
        Global application with Azure Functions
    </h4>
</figcaption>
<p>Shopping Cart team packages their code as a Docker container, which they put into Azure Container Registry, and then use Azure Container Instances to run the application. The Registry is a global Azure resource, while Container Instances are deployed separately per region.</p>
<p><img src="containers.png" alt="Global application with Azure Container Instances"></p>
<figcaption>
    <h4>
        Global application with Azure Container Instances
    </h4>
</figcaption>
<p>Pricing Engine team runs their code on Infrastructure-as-a-Service (IaaS), so they have a task of creating Virtual Machine images, and then deploying those to an Azure Virtual Machine Scale Set, fronting it with a Load Balancer, and configuring all the networking infrastructure including Virtual Networks, Subnets, Public IPs, Network Security Groups, etc. Once again, this has to be done for each location independently.</p>
<p><img src="vmscalesets.png" alt="Global application with Virtual Machines"></p>
<figcaption>
    <h4>
        Global application with Virtual Machines
    </h4>
</figcaption>
<p>Each team has the same global structure of infrastructure, including a Traffic Manager front-end and a Cosmos DB data store. At the same time, the per-region compute infrastructure differs substantially.</p>
<p>Instead of copying the common pieces of infrastructure between the teams, we can think of a reusable abstraction which could be shared by every service.</p>
<p><img src="globalapp.png" alt="Global application pattern"></p>
<figcaption>
    <h4>
        Global application pattern
    </h4>
</figcaption>
<p>For this blog post, I created a custom Pulumi component called CosmosApp which implements such abstraction and simplifies the provisioning of global web applications. The component creates distributed Cosmos DB resources, as well as the front-end routing component, while allowing pluggable compute layer implementation.</p>
<h2 id="cosmos-app-with-azure-functions">Cosmos App with Azure Functions</h2>
<p>Let&rsquo;s look at an example of how the component can be used. Creating a globally-distributed application requires three steps:</p>
<h3 id="list-the-regions">List the Regions</h3>
<p>We define a configuration value to contain a comma-separated list of Azure regions that we’ll deploy the application to.</p>
<pre tabindex="0"><code>$ pulumi config set locations WestUS,WestEurope,SouthEastAsia,SouthBrazil,AustraliaCentral
</code></pre><p>This value can be loaded and parsed into a JavaScript array:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">locations</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Config</span><span style="color:#1f2328">().</span><span style="color:#cf222e">require</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;locations&#34;</span><span style="color:#1f2328">).</span><span style="color:#1f2328">split</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;,&#34;</span><span style="color:#1f2328">);</span>
</span></span></code></pre></div><h3 id="build-a-region-template">Build a Region Template</h3>
<p>We define a factory function to create the infrastructure in each location. Its signature is a bit unusual in that we return a function from another function. I&rsquo;ll explain the motivation in the next example. For now, it&rsquo;s important to understand the following:</p>
<ul>
<li>We get a Cosmos account and a location as input arguments</li>
<li>We create a Function App in the specified location with connection settings pointing to the local Cosmos DB replica</li>
<li>We return the identifier of the Function App so that a Traffic Manager endpoint could point to it</li>
</ul>
<p>Here is the function:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">function</span> <span style="color:#1f2328">buildProductApp</span><span style="color:#1f2328">({</span> <span style="color:#1f2328">cosmosAccount</span> <span style="color:#1f2328">}</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">GlobalContext</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">return</span> <span style="color:#1f2328">({</span> <span style="color:#1f2328">location</span> <span style="color:#1f2328">}</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">RegionalContext</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">const</span> <span style="color:#1f2328">app</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ArchiveFunctionApp</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;function-app&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">archive</span>: <span style="color:#cf222e">new</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">asset</span><span style="color:#1f2328">.</span><span style="color:#1f2328">FileArchive</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;./app&#34;</span><span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">appSettings</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">COSMOSDB_ENDPOINT</span>: <span style="color:#cf222e">cosmosAccount.endpoint</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">COSMOSDB_KEY</span>: <span style="color:#cf222e">cosmosAccount.primaryMasterKey</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">COSMOSDB_LOCATION</span>: <span style="color:#cf222e">location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">id</span>: <span style="color:#cf222e">app.functionApp.id</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><h3 id="instantiate-a-cosmos-app">Instantiate a Cosmos App</h3>
<p>Finally, we use the <code>buildProductApp</code> factory function to create the geo-distributed application:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">products</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">CosmosApp</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;products&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroup</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">locations</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">databaseName</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;productsdb&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">containerName</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;products&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">factory</span>: <span style="color:#cf222e">buildProductApp</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>[ <a href="https://github.com/pulumi/examples/tree/master/azure-ts-cosmosapp-component/functionApp.ts">Full Example</a> ]</p>
<h2 id="cosmos-app-with-container-instances">Cosmos App with Container Instances</h2>
<p>The Shopping Cart team will have to follow the same steps but define their compute infrastructure accordingly.</p>
<p>A significant difference to the Functions-based example is the fact that the Container-based application needs some infrastructure to be shared across regions. The top block of the factory function defines an instance of Azure Container Registry, builds a Docker image, and puts that image into the Registry:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">function</span> <span style="color:#1f2328">buildShoppingCartApp</span><span style="color:#1f2328">({</span> <span style="color:#1f2328">cosmosAccount</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">database</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">container</span> <span style="color:#1f2328">}</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">GlobalContext</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">const</span> <span style="color:#1f2328">registry</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">containerservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Registry</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;global&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">adminEnabled</span>: <span style="color:#cf222e">true</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">sku</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Premium&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span> <span style="color:#1f2328">opts</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">const</span> <span style="color:#1f2328">dockerImage</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">docker</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Image</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;node-app&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">imageName</span>: <span style="color:#cf222e">pulumi.interpolate</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">registry</span><span style="color:#1f2328">.</span><span style="color:#1f2328">loginServer</span><span style="color:#0a3069">}</span><span style="color:#0a3069">/mynodeapp:v1.0.0`</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">build</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">context</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;./container&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">registry</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">server</span>: <span style="color:#cf222e">registry.loginServer</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">username</span>: <span style="color:#cf222e">registry.adminUsername</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">password</span>: <span style="color:#cf222e">registry.adminPassword</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">return</span> <span style="color:#1f2328">({</span> <span style="color:#1f2328">location</span> <span style="color:#1f2328">}</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">RegionalContext</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#57606a">// ... see the next sample
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>    <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>Now, the factory function that builds per-region infrastructure can directly reference the Registry and the image to deploy an Azure Container Instances group. Note how <code>registry</code> and <code>dockerImage</code> are used as any other TypeScript variable inside the closure factory function:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">function</span> <span style="color:#1f2328">buildShoppingCartApp</span><span style="color:#1f2328">({</span> <span style="color:#1f2328">cosmosAccount</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">database</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">container</span> <span style="color:#1f2328">}</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">GlobalContext</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">const</span> <span style="color:#1f2328">registry</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">containerservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Registry</span><span style="color:#1f2328">(</span><span style="color:#57606a">/* ... */</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">const</span> <span style="color:#1f2328">dockerImage</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">docker</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Image</span><span style="color:#1f2328">(</span><span style="color:#57606a">/* ... */</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">return</span> <span style="color:#1f2328">({</span> <span style="color:#1f2328">location</span> <span style="color:#1f2328">}</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">RegionalContext</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">const</span> <span style="color:#1f2328">group</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">containerservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Group</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`aci-</span><span style="color:#0a3069">${</span><span style="color:#1f2328">location</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">imageRegistryCredentials</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">server</span>: <span style="color:#cf222e">registry.loginServer</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">username</span>: <span style="color:#cf222e">registry.adminUsername</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">password</span>: <span style="color:#cf222e">registry.adminPassword</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">osType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Linux&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">containers</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#1f2328">cpu</span>: <span style="color:#cf222e">0.5</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#1f2328">image</span>: <span style="color:#cf222e">dockerImage.imageName</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#1f2328">memory</span>: <span style="color:#cf222e">1.5</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;web-server&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#1f2328">ports</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#1f2328">port</span>: <span style="color:#cf222e">80</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#1f2328">protocol</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;TCP&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#1f2328">environmentVariables</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#1f2328">ENDPOINT</span>: <span style="color:#cf222e">cosmosAccount.endpoint</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#1f2328">MASTER_KEY</span>: <span style="color:#cf222e">cosmosAccount.primaryMasterKey</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#1f2328">DATABASE</span>: <span style="color:#cf222e">database.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#1f2328">COLLECTION</span>: <span style="color:#cf222e">container.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#1f2328">LOCATION</span>: <span style="color:#cf222e">location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">],</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">ipAddressType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;public&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">dnsNameLabel</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">`acishop-</span><span style="color:#0a3069">${</span><span style="color:#1f2328">location</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">url</span>: <span style="color:#cf222e">group.fqdn</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>Finally, a <code>CosmosApp</code> is defined:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">aci</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">CosmosApp</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;aci&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroup</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">locations</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">databaseName</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;cartdb&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">containerName</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;items&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">factory</span>: <span style="color:#cf222e">buildShoppingCartApp</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">enableMultiMaster</span>: <span style="color:#cf222e">true</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Shopping Cart workload expects both reads and writes from the end-users, so the team decides to enable multi-master support in their Cosmos DB instance.</p>
<p>[ <a href="https://github.com/pulumi/examples/tree/master/azure-ts-cosmosapp-component/aci.ts">Full Example</a> ]</p>
<h2 id="cosmos-app-with-virtual-machine-scale-sets">Cosmos App with Virtual Machine Scale Sets</h2>
<p>The Pricing Engine team has to go through the same steps but employ IaaS-based infrastructure to run application code. The factory function creates a dozen resources related to virtual machines, load balancing, networking, and auto-scaling. You may define as many resources as needed in the code, as long as it returns a pointer to the proper resource to link the endpoint.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">function</span> <span style="color:#1f2328">buildVMScaleSetApp</span><span style="color:#1f2328">({</span> <span style="color:#1f2328">cosmosAccount</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">database</span> <span style="color:#1f2328">}</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">GlobalContext</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">// Build a VM image here...
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">// Define a MongoDB compatible collection
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>    <span style="color:#cf222e">const</span> <span style="color:#1f2328">collection</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">cosmosdb</span><span style="color:#1f2328">.</span><span style="color:#1f2328">MongoCollection</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`mongo-</span><span style="color:#0a3069">${</span><span style="color:#1f2328">name</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">accountName</span>: <span style="color:#cf222e">cosmosAccount.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">databaseName</span>: <span style="color:#cf222e">database.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">return</span> <span style="color:#1f2328">({</span> <span style="color:#1f2328">location</span> <span style="color:#1f2328">}</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">RegionalContext</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">const</span> <span style="color:#1f2328">publicIp</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">network</span><span style="color:#1f2328">.</span><span style="color:#1f2328">PublicIp</span><span style="color:#1f2328">(</span><span style="color:#57606a">/* ... */</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">const</span> <span style="color:#1f2328">loadBalancer</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">lb</span><span style="color:#1f2328">.</span><span style="color:#1f2328">LoadBalancer</span><span style="color:#1f2328">(</span><span style="color:#57606a">/* ... */</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">const</span> <span style="color:#1f2328">bpepool</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">lb</span><span style="color:#1f2328">.</span><span style="color:#1f2328">BackendAddressPool</span><span style="color:#1f2328">(</span><span style="color:#57606a">/* ... */</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">const</span> <span style="color:#1f2328">probe</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">lb</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Probe</span><span style="color:#1f2328">(</span><span style="color:#57606a">/* ... */</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">const</span> <span style="color:#1f2328">rule</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">lb</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Rule</span><span style="color:#1f2328">(</span><span style="color:#57606a">/* ... */</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">const</span> <span style="color:#1f2328">vnet</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">network</span><span style="color:#1f2328">.</span><span style="color:#1f2328">VirtualNetwork</span><span style="color:#1f2328">(</span><span style="color:#57606a">/* ... */</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">const</span> <span style="color:#1f2328">subnet</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">network</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Subnet</span><span style="color:#1f2328">(</span><span style="color:#57606a">/* ... */</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">const</span> <span style="color:#1f2328">scaleSet</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">compute</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ScaleSet</span><span style="color:#1f2328">(</span><span style="color:#57606a">/* ... */</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">const</span> <span style="color:#1f2328">autoscale</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">monitoring</span><span style="color:#1f2328">.</span><span style="color:#1f2328">AutoscaleSetting</span><span style="color:#1f2328">(</span><span style="color:#57606a">/* ... */</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">id</span>: <span style="color:#cf222e">publicIp.id</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>The code also uses a collection with the MongoDB protocol compatibility because that’s what the application expects.</p>
<p>[ <a href="https://github.com/pulumi/examples/tree/master/azure-ts-cosmosapp-component/vms.ts">Full Example</a> ]</p>
<h2 id="conclusion">Conclusion</h2>
<p>Azure Cloud Platform offers an amazing array of capabilities for application developers. Azure Cosmos DB takes care of distributing the data globally, reliably, consistently, and with low-latency access anywhere around the world. Nonetheless, developers still need to take care of adequately provisioning the application layer for efficiently serving users around the globe.</p>
<p>Cosmos App component is an excellent example of the power of using a general-purpose programming language to define cloud infrastructure. Such a component can let you codify the best practices and let multiple teams or companies share the implementation. The component and applications can evolve independently, with tools like versioning, package managers, compilers, and automated tests helping to maintain high-quality standards.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                             
                                <category scheme="https://mikhail.io/tags/azure-cosmos-db" term="azure-cosmos-db" label="Azure Cosmos DB" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[How to Avoid Cost Pitfalls by Monitoring APIs in AWS Lambda]]></title>
            <link href="https://mikhail.io/2019/08/how-to-avoid-cost-pitfalls-by-monitoring-apis-in-aws-lambda/"/>
            <id>https://mikhail.io/2019/08/how-to-avoid-cost-pitfalls-by-monitoring-apis-in-aws-lambda/</id>
            
            <published>2019-08-23T00:00:00+00:00</published>
            <updated>2019-08-23T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>How to monitor your APIs using serverless technologies and an Epsagon dashboard.</blockquote><p>The modern software world runs on application programming interfaces, otherwise known as APIs. There is an API for everything: charging a credit card, sending an email, finding an address on a map, or hailing a taxi.</p>
<p>Likewise, the huge computing power of the cloud is entirely accessible via multiple layers of programming interfaces, including HTTP services and language-specific SDKs. Cloud vendors such as AWS have thousands of endpoints available to every developer in the world and modern businesses ought to embrace these existing services to stay productive. Instead of trying to reinvent the wheel, businesses should focus on the core values and differentiators for their specific market and simply purchase, adopt and reuse the remaining services required from third parties that have proven themselves in their given niche.</p>
<p>This principle stands at the core of the philosophy behind serverless architecture: focus on the crucial bits. Serverless tech has a low barrier of entry and was designed with the intention to require little boilerplate code to use. This helps to explain why so many reliable <a href="https://epsagon.com/blog/the-importance-and-impact-of-apis-in-serverless/">APIs are an inherent part</a> of serverless applications today. Lambda functions, for their part, are used by developers as the glue between cloud services and internal as well as external API calls.</p>
<h2 id="role-of-performance">Role of Performance</h2>
<p>One thing to keep in mind when adopting APIs, in general, is performance. Most API calls are usually synchronous requests over HTTP or HTTPS. They may be fast to execute, but they may also be slow and responsiveness is not manifested in the API definition itself. Moreover, the latency may vary over time, as APIs can crash at seemingly random intervals or under higher loads. Throttling strategies might also kick in once the workload passes a given threshold.</p>
<p>This aspect of performance is arguably more decisive in the serverless world than ever before, as slow downstream APs for serverless platforms have numerous implications:</p>
<ul>
<li>Each Lambda function has a maximum duration threshold and it&rsquo;s going to time out and fail if the total duration of API calls goes above that value.</li>
<li>AWS charges organizations for execution times for Lambda invocations. Even when a function is idle while waiting for the HTTP response, you pay for each 100 milliseconds consumed. Slow API calls lead to the inevitable <a href="https://epsagon.com/blog/how-much-does-aws-lambda-cost/">extra cost of the serverless application</a>.</li>
<li>For synchronous invocations, when a user is waiting for the Lambda to complete, substantial slowdowns lead to poor user experience, as unhappy customers and lose time, and often, revenue.</li>
</ul>
<p>Luckily, a lot can be done to prevent and mitigate such issues.</p>
<h2 id="instrumenting-api-performance">Instrumenting API Performance</h2>
<p>There are several strategies to assess the impact that the performance of a third-party API has on your serverless application.</p>
<p>While just getting started with a new API, it makes sense to research if the supplier has provided a performance SLA or not. Regardless of the answer, the next step is to execute the target calls and measure the latency profile. You should strive to do so well in advance, before signing up for a new service. Finally, it&rsquo;s worth running a load test to emulate your target workload over an extended period.</p>
<p>Your job is still not done even after an application is successfully deployed to production. For any non-trivial application, it&rsquo;s worth investing in monitoring tools and best practices. A monitoring toolkit provides both real-time and historical perspectives on the performance of your dependencies as well as their impact on application usability and cost.</p>
<p>The following section gives a sneak peek of such activities.</p>
<h2 id="example-geo-alerting-application">Example: Geo-Alerting Application</h2>
<p>Let&rsquo;s consider a sample application that processes telemetry from connected vehicles. Each vehicle sends periodic messages, which end up in the AWS cloud. A single AWS Lambda receives a message and executes the following steps:</p>
<ul>
<li>Decodes the payload and extracts data, including geo-coordinates and sensor values.</li>
<li>Reverse geocodes the coordinates to infer the address with a call to the <a href="https://geocode.xyz/">xyz</a> service.</li>
<li>Saves a log entry containing the message properties and addresses to an <a href="https://aws.amazon.com/dynamodb/">AWS DynamoDB</a>.</li>
<li>Compares the values against thresholds to check whether to send an alert.</li>
<li>If needed, loads a suitable email template from an <a href="https://aws.amazon.com/s3/">AWS S3</a> bucket.</li>
<li>Sends an email notification using the <a href="https://sendgrid.com/">SendGrid</a> mailing service.</li>
</ul>
<p>It&rsquo;s a reasonably straightforward application and yet it depends on four external APIs: two AWS services and two third-party endpoints. Here, we will implement a prototype of such an application and link it to an <a href="https://epsagon.com/">Epsagon</a> dashboard to see what kind of insights we can get about its performance.</p>
<h2 id="timeline-of-a-single-invocation">Timeline of a Single Invocation</h2>
<p>Let&rsquo;s start by calling my AWS Lambda once, then find that invocation in the Epsagon dashboard and display the <a href="https://epsagon.com/blog/introducing-the-timeline-view/">timeline view</a>:</p>
<p><img src="timeline.png" alt="The Epsagon dashboard: A Timeline view of a single call."></p>
<figcaption><h4>The Epsagon dashboard: A Timeline view of a single call.</h4></figcaption>
<p>You can see that this single invocation took 827 milliseconds to complete and that this time was entirely spent on waiting for the API calls to complete. You can also clearly see the breakdown of the execution time between the four dependencies and that the geocoding and email sending is slower than the calls to DynamoDB and S3.</p>
<p>The timeline view looks very familiar to any developer who has ever worked with performance and network tabs as part of a browser&rsquo;s developer tools. And it&rsquo;s beneficial for a similar purpose: hunting down the culprit of slow execution.</p>
<p>It&rsquo;s worth mentioning that you don&rsquo;t have to track the calls manually. All the data is collected automatically by the Epsagon library once you plug it into the Lambda.</p>
<h2 id="tracking-invocations-over-time">Tracking Invocations Over Time</h2>
<p>The above was just one measurement, which might not be very representative of reality. The next step is to see how the target AWS Lambda behaves over time. Let&rsquo;s set up a test to execute the function several hundred times over several minutes. It&rsquo;s easiest to use AWS API Gateway and invoke the function via HTTP. Alternatively, you could use an SQS queue or another asynchronous trigger, but monitoring behaves similarly in all cases.</p>
<p>After the test, go ahead and navigate to the Architecture Map page:</p>
<p><img src="architecturemap.png" alt="Architecture Map - Epsagon dashboard."></p>
<figcaption><h4>Architecture Map - Epsagon dashboard.</h4></figcaption>
<p>The preceding Epsagon dashboard gives you an aggregate view of all recent function executions. In this case, the numbers happen to be quite similar to the previous test: about 800 milliseconds of invocation time on average with geocoding being the slowest call. SendGrid calls seem to go faster on average, but the red border around it indicates that some of the requests failed. This also points to a potential problem, which you would need to investigate. You may want to take some concrete actions based on the collected data, such as trying another geocoding service to see if it is faster for a comparable price.</p>
<p>You can estimate the cost of your application based on the predicted number of invocations multiplied by the price for 900 milliseconds of execution time. This function spends all the time waiting for I/O operations to complete and it&rsquo;s not CPU- or memory-heavy. Therefore, it makes sense to provision smaller sizes of AWS Lambda instances.</p>
<p>Finally, you may want to break down your &ldquo;monolith&rdquo; function into multiple functions that are interconnected with queues. Smaller single-purpose functions <a href="https://epsagon.com/blog/the-right-way-to-distribute-messages-effectively-in-serverless-applications/">have several benefits</a>, including higher resilience and potentially lower cost.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Almost every serverless function relies on one or many API calls and a combination of other cloud services and third-party applications. The latency and stability of those calls directly affect the Lambda function, both in terms of performance and cost.</p>
<p>Be sure to test the performance of any newly adopted API before relying on it. In addition, you need to monitor the latency, reliability and <a href="https://epsagon.com/blog/finding-serverless-hidden-costs/">cost of the API calls in production</a>. It is important that you optimize the structure of your application to avoid long-running calls on the synchronous path that is observable by end-users. And for better cost manageability and higher resilience, complete the executions as fast as possible and delegate the remaining work to queue- or event-based functions.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/aws" term="aws" label="AWS" />
                             
                                <category scheme="https://mikhail.io/tags/epsagon" term="epsagon" label="Epsagon" />
                             
                                <category scheme="https://mikhail.io/tags/monitoring" term="monitoring" label="Monitoring" />
                             
                                <category scheme="https://mikhail.io/tags/aws-lambda" term="aws-lambda" label="AWS Lambda" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Ten Pearls With Azure Functions in Pulumi]]></title>
            <link href="https://mikhail.io/2019/08/ten-pearls-with-azure-functions-in-pulumi/"/>
            <id>https://mikhail.io/2019/08/ten-pearls-with-azure-functions-in-pulumi/</id>
            
            <published>2019-08-21T00:00:00+00:00</published>
            <updated>2019-08-21T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Ten bite-sized code snippets that use Pulumi to build serverless applications with Azure Functions and infrastructure as code.</blockquote><p>In this post, we&rsquo;ll take a look at 10 &ldquo;pearls&rdquo;—bite-sized code snippets—that demonstrate using Pulumi to build serverless applications with Azure Functions and infrastructure as code. These pearls are organized into four categories, each demonstrating a unique scenario:</p>
<ul>
<li><strong>Function App Deployment</strong>: Deploy an existing Azure Functions application using infrastructure as code.</li>
<li><strong>HTTP Functions as Callbacks</strong>: Mix JavaScript or TypeScript functions with your infrastructure definition to produce strongly-typed, self-contained, serverless HTTP endpoints.</li>
<li><strong>Cloud Event Handling</strong>: Leverage a variety of event sources available to Azure Functions with lightweight event handlers.</li>
<li><strong>Data Flows with Function Bindings</strong>: Take advantage of function bindings—declarative connectors to Azure services.</li>
</ul>
<p>Our Azure Functions pearls are:</p>
<p><a href="#function-app-deployment"><strong>Function App Deployment</strong></a></p>
<ul>
<li><a href="#1-deploy-a-net-function-app">Deploy a Function App written in .NET or any other supported runtime</a></li>
<li><a href="#2-run-functions-using-an-elastic-premium-plan">Configure Functions to run on an Elastic Premium Plan</a></li>
</ul>
<p><a href="#http-functions-as-callbacks"><strong>HTTP Functions as Callbacks</strong></a></p>
<ul>
<li><a href="#3-define-node-js-functions-as-inline-callbacks">Define Node.js Functions as inline callbacks</a></li>
<li><a href="#4-rest-apis-as-multiple-functions">Implement REST APIs as multiple Functions</a></li>
<li><a href="#5-function-warming-with-a-timer-function">&ldquo;Warm&rdquo; the Functions to avoid Cold Starts</a></li>
</ul>
<p><a href="#cloud-event-handling"><strong>Cloud Event Handling</strong></a></p>
<ul>
<li><a href="#6-process-events-from-azure-event-hubs">Process events from Azure Event Hub</a></li>
<li><a href="#7-subscribe-to-azure-event-grid">Subscribe to Blob creation with Azure Event Grid</a></li>
<li><a href="#8-respond-to-resource-level-events">Run a Function every time an Azure resource is modified</a></li>
</ul>
<p><a href="#data-flows-with-function-bindings"><strong>Data Flows with Function Bindings</strong></a></p>
<ul>
<li><a href="#9-output-bindings">Push a message to a Storage Queue with an output binding</a></li>
<li><a href="#10-input-bindings">Pull a Storage Table row with an input binding</a></li>
</ul>
<h2 id="function-app-deployment">Function App Deployment</h2>
<p>Azure Functions can be written in many languages, and Pulumi supports whatever choice you make. You can take any existing serverless application and deploy it using infrastructure-as-code.</p>
<h3 id="1-deploy-a-net-function-app">1. Deploy a .NET Function App</h3>
<p>[ <a href="https://github.com/pulumi/examples/tree/master/azure-ts-functions-raw">Runnable Example</a> ]</p>
<p>Many Function Apps are .NET applications created with native tooling like Visual Studio or the Functions CLI. Pulumi can simply deploy such an application in only a few lines of JavaScript:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">dotnetApp</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ArchiveFunctionApp</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;http-dotnet&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">archive</span>: <span style="color:#cf222e">new</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">asset</span><span style="color:#1f2328">.</span><span style="color:#1f2328">FileArchive</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;./app/bin/Debug/netcoreapp2.1/publish&#34;</span><span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">appSettings</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">runtime</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;dotnet&#34;</span> <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>There are only four things required:</p>
<ul>
<li>the Function App name (&ldquo;http-dotnet&rdquo;)</li>
<li>the resource group it belongs to</li>
<li>the path to the compiled .NET assemblies, and</li>
<li>the desired runtime</li>
</ul>
<p>Pulumi takes care of the rest for you. It handles the following tasks:</p>
<ul>
<li>Creating a Storage Account and a Blob Container</li>
<li>Zipping up the binaries and uploading them to the blob container</li>
<li>Calculating a SAS token</li>
<li>Preparing a Consumption Plan and a Function App using this Consumption Plan</li>
<li>Configuring the required application settings, including a reference to the zip archive with the SAS token</li>
</ul>
<p><img src="console.png" alt="Serverless Application deployed by Pulumi"></p>
<p>While a few values are required, you are not restricted to the default behavior and can change any setting.</p>
<h3 id="2-run-functions-using-an-elastic-premium-plan">2. Run Functions using an Elastic Premium Plan</h3>
<p>While the Consumption Plan is the ultimate serverless option, there are other ways to host Azure Functions. Azure also offers the Premium Plan: a combination of the power and guarantees of a fixed App Service Plan with the elasticity of Consumption.</p>
<p>If you want to take advantage of a Premium Plan, go ahead and define it as a Pulumi resource, then link to it in the Function App definition:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">premiumPlan</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Plan</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;my-premium&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">sku</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">tier</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Premium&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">size</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;EP1&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">maximumElasticWorkerCount</span>: <span style="color:#cf222e">20</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">javaApp</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ArchiveFunctionApp</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;http-java&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">plan</span>: <span style="color:#cf222e">premiumPlan</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">archive</span>: <span style="color:#cf222e">new</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">asset</span><span style="color:#1f2328">.</span><span style="color:#1f2328">FileArchive</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;./java/target/functions/fabrikam&#34;</span><span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">appSettings</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">runtime</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;java&#34;</span> <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>In this example, I deployed a Java application. The Premium Plan has a couple of configuration knobs: the <em>instance size</em> and the <em>maximum scale-out limit</em>.</p>
<h2 id="http-functions-as-callbacks">HTTP Functions as Callbacks</h2>
<p>Node.js is another runtime supported by Azure Functions. Like before, you can use the <code>ArchiveFunctionApp</code> class to deploy the application from an external folder. However, Pulumi&rsquo;s Node.js SDK provides a way to mix the code of your functions directly into your infrastructure definition.</p>
<h3 id="3-define-nodejs-functions-as-inline-callbacks">3. Define Node.js Functions as Inline Callbacks</h3>
<p>[ <a href="https://github.com/pulumi/examples/tree/master/azure-ts-functions">Runnable Example</a> ]</p>
<p>A TypeScript or a JavaScript function becomes an Azure Function deployed to the cloud:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">greeting</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">HttpEventSubscription</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#39;greeting&#39;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">req</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">status</span>: <span style="color:#cf222e">200</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">body</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">`Hello </span><span style="color:#0a3069">${</span><span style="color:#1f2328">req</span><span style="color:#1f2328">.</span><span style="color:#1f2328">query</span><span style="color:#1f2328">[</span><span style="color:#0a3069">&#39;name&#39;</span><span style="color:#1f2328">]</span> <span style="color:#0550ae">||</span> <span style="color:#0a3069">&#39;World&#39;</span><span style="color:#0a3069">}</span><span style="color:#0a3069">!`</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">url</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">greeting</span><span style="color:#1f2328">.</span><span style="color:#1f2328">url</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><p>You are free to use any NPM module inside the callback, and the dependencies will be automatically packaged into the deployment artifact. Alternatively, you can extract the callback function into a separate file or package and import it from your infrastructure code.</p>
<p>While mixing infrastructure and application code in the same file may seem counterintuitive, it provides many benefits:</p>
<ul>
<li>Combined code binaries (data plane) and infrastructure (control plane) as a single unit of deployment</li>
<li>Eliminate the need for boilerplate configuration like <code>host.json</code> and <code>function.json</code> files</li>
<li>Robust typing out-of-the-box: For instance, you can flawlessly &ldquo;dot into&rdquo; the <code>content</code> and <code>req</code> object above.</li>
</ul>
<p>You can read more about the motivation in <a href="https://mikhail.io/2019/05/serverless-as-simple-callbacks-with-pulumi-and-azure-functions/">Serverless as Simple Callbacks with Pulumi and Azure Functions</a>.</p>
<p>The previous example deployed a Function App with a single Function. However, Azure supports applications with multiple Functions bundled together.</p>
<h3 id="4-rest-apis-as-multiple-functions">4. REST APIs as Multiple Functions</h3>
<p>It&rsquo;s common to use Azure Functions for implementing RESTful APIs. As an API may consist of multiple endpoints, we may need to combine several HTTP Functions into a single deployment unit.</p>
<p>To achieve that, we define an <code>HttpFunction</code> object per Azure Function, each with its callback and settings. Then, we pass an array of these objects into a <code>MultiCallbackFunctionApp</code> constructor. Each Function definition may have a specific route and HTTP methods to handle:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#cf222e">get</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">HttpFunction</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;Read&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">route</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;items&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">methods</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span><span style="color:#0a3069">&#34;GET&#34;</span><span style="color:#1f2328">],</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">request</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">const</span> <span style="color:#1f2328">items</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">await</span> <span style="color:#1f2328">repository</span><span style="color:#1f2328">.</span><span style="color:#1f2328">list</span><span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">status</span>: <span style="color:#cf222e">200</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">body</span>: <span style="color:#cf222e">items</span> <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">post</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">HttpFunction</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;Add&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">route</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;items&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">methods</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span><span style="color:#0a3069">&#34;POST&#34;</span><span style="color:#1f2328">],</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">request</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">const</span> <span style="color:#1f2328">id</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">await</span> <span style="color:#1f2328">repository</span><span style="color:#1f2328">.</span><span style="color:#1f2328">add</span><span style="color:#1f2328">(</span><span style="color:#1f2328">request</span><span style="color:#1f2328">.</span><span style="color:#1f2328">body</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">status</span>: <span style="color:#cf222e">201</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">body</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">id</span> <span style="color:#1f2328">}</span>  <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">app</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">MultiCallbackFunctionApp</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;multi-app&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">functions</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span><span style="color:#cf222e">get</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">post</span><span style="color:#1f2328">],</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>You can combine as many Functions as you want, including functions of different types, as shown in the next example.</p>
<h3 id="5-function-warming-with-a-timer-function">5. Function &ldquo;Warming&rdquo; with a Timer Function</h3>
<p>[ <a href="https://github.com/pulumi/pulumi-azure/blob/master/examples/http-multi">Runnable Example</a> ]</p>
<p>Scheduled jobs are another frequent use case for serverless functions. It&rsquo;s possible to define a <a href="https://docs.microsoft.com/azure/azure-functions/functions-bindings-timer#cron-expressions"><em>cron expression</em></a> and get the code executed at designated intervals.</p>
<p>The Consumption Plan disposes a worker if no Function runs in about 20 minutes. After disposal, the next execution will cause a <a href="https://mikhail.io/serverless/coldstarts/define/"><em>cold start</em></a>, and the response latency, will increase.</p>
<p>In order to avoid this disposal, we can combine a target HTTP Function with a Timer Function. The body of the Timer Function is empty, since its sole purpose is to trigger the Function App and keep the worker &ldquo;warm&rdquo;:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">warmer</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">TimerFunction</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;warmer&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">schedule</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;0 */5 * * * *&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">()</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">http</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">HttpFunction</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;hello&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">req</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">status</span>: <span style="color:#cf222e">200</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">body</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Hello World!&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">app</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">MultiCallbackFunctionApp</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;always-warm-app&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">functions</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span><span style="color:#1f2328">http</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">warmer</span><span style="color:#1f2328">],</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>It&rsquo;s easy to imagine a custom component <code>WarmedFunctionApp</code> which appends a standard Timer Function to an array of Functions passed to its constructor.</p>
<h2 id="cloud-event-handling">Cloud Event Handling</h2>
<p>While HTTP is a widespread use case, Azure Functions support many other trigger types too. <a href="https://mikhail.io/2019/05/serverless-as-simple-callbacks-with-pulumi-and-azure-functions/">The previous post</a> featured Storage Queues and ServiceBus Topics. Pulumi supports Timers, Events Hubs, Event Grid, Storage Blobs, Service Bus Queues, and Cosmos DB Change Feed events, too! Let&rsquo;s see how that looks using Azure Event Hubs.</p>
<h3 id="6-process-events-from-azure-event-hubs">6. Process Events from Azure Event Hubs</h3>
<p>[ <a href="https://github.com/pulumi/pulumi-azure/tree/master/examples/eventhub">Runnable Example</a> ]</p>
<p>Azure Event Hubs is a fully-managed log-based messaging service comparable to Apache Kafka. In contrast to a self-hosted Kafka cluster, it only takes a few lines of JavaScript to create an Event Hub and start processing events:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">eventHub</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">eventhub</span><span style="color:#1f2328">.</span><span style="color:#1f2328">EventHub</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;my-hub&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">namespaceName</span>: <span style="color:#cf222e">namespace.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">partitionCount</span>: <span style="color:#cf222e">2</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">messageRetention</span>: <span style="color:#cf222e">7</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">eventHub</span><span style="color:#1f2328">.</span><span style="color:#1f2328">onEvent</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;MyHubEvent&#34;</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">msg</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">console</span><span style="color:#1f2328">.</span><span style="color:#1f2328">log</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;Event Hub message: &#34;</span> <span style="color:#0550ae">+</span> <span style="color:#1f2328">JSON</span><span style="color:#1f2328">.</span><span style="color:#1f2328">stringify</span><span style="color:#1f2328">(</span><span style="color:#1f2328">msg</span><span style="color:#1f2328">));</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Every time a new event comes in, be it once per hour or a thousand times a second, the Function is executed. Azure manages the scale-out for you.</p>
<h3 id="7-subscribe-to-azure-event-grid">7. Subscribe to Azure Event Grid</h3>
<p>[ <a href="https://github.com/pulumi/pulumi-azure/blob/master/examples/eventgrid">Runnable Example</a> ]</p>
<p>Azure Event Grid is another trigger type for Azure Functions. It is a dispatcher service for distributing events from many other Azure services as well as external data sources.</p>
<p>A classic example is subscribing to events from Azure Blob Storage. The Event Grid subscription connects to a given Storage Account and provides several handy options to filter the event stream. The following example subscribes to all JPG files created in any container of the account:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">storageAccount</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Account</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;eventgridsa&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountReplicationType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;LRS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountTier</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountKind</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;StorageV2&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">eventgrid</span><span style="color:#1f2328">.</span><span style="color:#1f2328">events</span><span style="color:#1f2328">.</span><span style="color:#1f2328">onGridBlobCreated</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;OnNewBlob&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">storageAccount</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">subjectFilter</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">caseSensitive</span>: <span style="color:#cf222e">false</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">subjectEndsWith</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;.jpg&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">event</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">context</span><span style="color:#1f2328">.</span><span style="color:#1f2328">log</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`Subject: </span><span style="color:#0a3069">${</span><span style="color:#1f2328">event</span><span style="color:#1f2328">.</span><span style="color:#1f2328">subject</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">context</span><span style="color:#1f2328">.</span><span style="color:#1f2328">log</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`File size: </span><span style="color:#0a3069">${</span><span style="color:#1f2328">event</span><span style="color:#1f2328">.</span><span style="color:#1f2328">data</span><span style="color:#1f2328">.</span><span style="color:#1f2328">contentLength</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>The <code>event</code> object is strongly typed: The snippet above logs the file size, but there are many other properties at your disposal. Code completion makes the discovery process painless.</p>
<p>It&rsquo;s worth mentioning that Pulumi does much of the work behind the scenes here, allowing you to focus on the important parts of the task at hand. In particular, Pulumi retrieves the appropriate secret key from Azure Functions ARM API and creates an Event Grid subscription which points to a specific webhook containing that key.</p>
<h3 id="8-respond-to-resource-level-events">8. Respond to Resource-level Events</h3>
<p>[ <a href="https://github.com/pulumi/pulumi-azure/blob/master/examples/eventgrid">Runnable Example</a> ]</p>
<p>Here is a good illustration of the power of Event Grid. A callback function gets triggered for each change to any resource belonging to the target Resource Group:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">resourceGroup</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">core</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ResourceGroup</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;eventgrid-rg&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">eventgrid</span><span style="color:#1f2328">.</span><span style="color:#1f2328">events</span><span style="color:#1f2328">.</span><span style="color:#1f2328">onResourceGroupEvent</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;OnResourceChange&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">event</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">context</span><span style="color:#1f2328">.</span><span style="color:#1f2328">log</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`Subject: </span><span style="color:#0a3069">${</span><span style="color:#1f2328">event</span><span style="color:#1f2328">.</span><span style="color:#1f2328">subject</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">context</span><span style="color:#1f2328">.</span><span style="color:#1f2328">log</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`Event Type: </span><span style="color:#0a3069">${</span><span style="color:#1f2328">event</span><span style="color:#1f2328">.</span><span style="color:#1f2328">eventType</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">context</span><span style="color:#1f2328">.</span><span style="color:#1f2328">log</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`Data: </span><span style="color:#0a3069">${</span><span style="color:#1f2328">JSON</span><span style="color:#1f2328">.</span><span style="color:#1f2328">stringify</span><span style="color:#1f2328">(</span><span style="color:#1f2328">event</span><span style="color:#1f2328">.</span><span style="color:#1f2328">data</span><span style="color:#1f2328">)</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>This simple piece of code is an easy launching point for many automation, auditing, and governance scenarios.</p>
<h2 id="data-flows-with-function-bindings">Data Flows with Function Bindings</h2>
<p>Azure Functions come with a powerful system of bindings. So far, we only saw examples of using event sources as trigger bindings. However, Azure also supports a powerful set of so called &ldquo;input&rdquo; and &ldquo;output&rdquo; bindings.</p>
<h3 id="9-output-bindings">9. Output Bindings</h3>
<p>[ <a href="https://github.com/pulumi/pulumi-azure/blob/master/examples/queue">Runnable Example</a> ]</p>
<p>Output bindings enable developers to easily forward the data from an Azure Function to an arbitrary destination in a declarative manner. For instance, if a queue handler needs to send a message to another queue, we don&rsquo;t have to use cloud SDKs. Instead, we can return the message-to-be-sent from the callback and wire it to the output queue. Here is a quick example:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">incoming</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Queue</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;queue-in&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">storageAccountName</span>: <span style="color:#cf222e">storageAccount.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">outgoing</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Queue</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;queue-out&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">storageAccountName</span>: <span style="color:#cf222e">storageAccount.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">incoming</span><span style="color:#1f2328">.</span><span style="color:#1f2328">onEvent</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;NewMessage&#34;</span><span style="color:#1f2328">,</span>  <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">outputs</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span><span style="color:#1f2328">outgoing</span><span style="color:#1f2328">.</span><span style="color:#1f2328">output</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;queueOut&#34;</span><span style="color:#1f2328">)],</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">person</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">queueOut</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">person</span><span style="color:#1f2328">.</span><span style="color:#1f2328">name</span><span style="color:#0a3069">}</span><span style="color:#0a3069"> logged into the system`</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Two elements play together here:</p>
<ul>
<li>The <code>outputs</code> property defines an output binding with the name <code>queueOut</code> and the destination to <code>outgoing</code></li>
<li>The <code>queueOut</code> property of the resulting object contains the output message</li>
</ul>
<p>Note that the binding name must match the output property.</p>
<h3 id="10-input-bindings">10. Input Bindings</h3>
<p>[ <a href="https://github.com/pulumi/pulumi-azure/tree/master/examples/table">Runnable Example</a> ]</p>
<p>Input bindings pull extra bits of information and pass them as input parameters to the callback. The exact usage depends on the trigger and binding types and might be tricky to get right with JSON configuration files. Here is one example of wiring done in a Pulumi program:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">values</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Table</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;values&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">storageAccountName</span>: <span style="color:#cf222e">storageAccount.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">getFunc</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">HttpEventSubscription</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#39;get-value&#39;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">route</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;{key}&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">inputs</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">values</span><span style="color:#1f2328">.</span><span style="color:#1f2328">input</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;entry&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">partitionKey</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;lookup&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">rowKey</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;{key}&#34;</span> <span style="color:#1f2328">}),</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">],</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">request</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">entry</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">status</span>: <span style="color:#cf222e">200</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">body</span>: <span style="color:#cf222e">entry.value</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>There are three crucial bits here:</p>
<ul>
<li>The <code>route</code> property of this HTTP Function contains a template parameter <code>key</code>. The runtime extracts the actual <code>key</code> value from the HTTP request.</li>
<li>The <code>inputs</code> option contains a reference to a Storage Table with a hardcoded <code>partitionKey</code> and a <code>rowKey</code> bound to the <code>key</code> template parameter. At execution time, a row is retrieved based on the combination of the keys. The entry, if found, is passed to the callback.</li>
<li>The <code>callback</code> has three input parameters, while all previous examples had two. The third parameter contains the retrieved row.</li>
</ul>
<p>It&rsquo;s possible to have multiple input and output bindings, and any combination of those.</p>
<h2 id="wrapping-up">Wrapping Up</h2>
<p>In this post, we&rsquo;ve seen some of the exciting things you can do with Azure Functions in Pulumi. Developers use serverless functions as a glue between managed cloud services. Pulumi offers a compelling way to define the links between these pieces of cloud infrastructure in a simple and expressive way.</p>
<p>For a streamlined Pulumi walkthrough, including language runtime installation and cloud configuration, see the  <a href="https://www.pulumi.com/docs/quickstart/azure/">Azure Quickstart</a>, check more examples at <a href="https://github.com/pulumi/pulumi-azure/tree/master/examples">Pulumi Azure GitHub</a>, and join the <a href="https://slack.pulumi.com/">Pulumi Community Slack</a>.</p>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[AWS Lambda Cold Starts After 10 Minutes]]></title>
            <link href="https://mikhail.io/2019/08/aws-lambda-cold-starts-after-10-minutes/"/>
            <id>https://mikhail.io/2019/08/aws-lambda-cold-starts-after-10-minutes/</id>
            
            <published>2019-08-20T00:00:00+00:00</published>
            <updated>2019-08-20T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>How AWS Lambda changed the policy of recycling idle instances</blockquote><p>I&rsquo;ve just released an update to the <a href="/serverless/coldstarts/">Serverless Cold Starts</a> section of my website. The most significant change to the previous dataset seems to be in how AWS treats idle instances of AWS Lambda.</p>
<p>Cold starts are expensive, so all cloud providers preserve a warm instance of a cloud function even when the application is idle. If the function stays unused for an extended period, such idle instance might eventually be recycled.</p>
<p>Azure Functions recycles its instances after 20 minutes of idling. Google Cloud Functions has no fixed value, but most often, instances may survive several hours of inactivity. The policy of AWS Lambda has recently changed.</p>
<h2 id="pre-july-between-25-and-60-minutes">Pre-July: between 25 and 60 minutes</h2>
<p>The behavior of AWS Lambda used to look a bit random: an average idle instance would typically survive between 25 and 60 minutes:</p>




  





<script type="text/javascript">
addChart((data, options) => {
  data.addColumn('number', 'ID');
  data.addColumn('number', 'Value');
  data.addColumn({ 'type': 'string', 'role': 'style' });
  data.addRows([[38.526995191666664,0.1550262,"point {fill-color: red}"],[49.383118026666665,0.3873159,"point {fill-color: blue}"],[30.627794798333333,0.3444636,"point {fill-color: blue}"],[74.55351005666667,0.6604886999999999,"point {fill-color: blue}"],[40.02867755333333,0.44541949999999997,"point {fill-color: blue}"],[80.70089967333334,0.3074113,"point {fill-color: blue}"],[13.919134966666666,0.2557926,"point {fill-color: red}"],[20.954349756666666,0.1430216,"point {fill-color: red}"],[69.30654145,1.7465974,"point {fill-color: blue}"],[51.437736711666666,0.8658656,"point {fill-color: blue}"],[41.848015896666666,0.0581395,"point {fill-color: red}"],[73.16705693,0.2018015,"point {fill-color: blue}"],[83.82289175666666,0.5951442,"point {fill-color: blue}"],[3.2707585133333335,0.0964497,"point {fill-color: red}"],[66.16240756833334,0.4297529,"point {fill-color: blue}"],[59.17943319166667,0.1006936,"point {fill-color: red}"],[5.27709279,0.13207929999999998,"point {fill-color: red}"],[69.47966145666666,1.7406161999999998,"point {fill-color: blue}"],[59.19029861333333,0.1015944,"point {fill-color: red}"],[27.499207288333334,0.49999869999999996,"point {fill-color: blue}"],[22.710786873333333,0.09022949999999999,"point {fill-color: red}"],[48.95806383833333,0.11274819999999999,"point {fill-color: red}"],[65.48154354666667,14.947999699999999,"point {fill-color: blue}"],[1.4133973316666666,0.15462019999999999,"point {fill-color: red}"],[9.55894851,0.0874689,"point {fill-color: red}"],[67.69212707333334,3.7460364999999998,"point {fill-color: blue}"],[57.763618925,0.1694528,"point {fill-color: red}"],[25.383047783333335,0.11548,"point {fill-color: red}"],[15.135814975,0.0829861,"point {fill-color: blue}"],[86.21106092833334,2.1164294999999997,"point {fill-color: blue}"],[64.66950564666666,0.5459723,"point {fill-color: blue}"],[38.61166446666667,0.0641221,"point {fill-color: red}"],[55.90309728333334,1.9127231,"point {fill-color: blue}"],[42.49044799166667,0.5686247999999999,"point {fill-color: blue}"],[24.975154416666665,0.0692699,"point {fill-color: red}"],[52.56358164,0.3999735,"point {fill-color: blue}"],[26.981866243333332,0.0788804,"point {fill-color: red}"],[69.17019360833334,0.2850802,"point {fill-color: blue}"],[9.011017508333333,0.061772299999999995,"point {fill-color: red}"],[70.70602261333333,0.37219169999999996,"point {fill-color: blue}"],[5.3830482716666666,0.1564117,"point {fill-color: red}"],[41.183333123333334,4.3570378,"point {fill-color: blue}"],[49.20586052666667,0.6072678,"point {fill-color: blue}"],[57.739191868333336,1.0766117,"point {fill-color: red}"],[68.35231534333333,0.5408453,"point {fill-color: blue}"],[78.83988125333333,0.2447595,"point {fill-color: blue}"],[41.262252188333335,0.2634827,"point {fill-color: blue}"],[86.75830183333333,0.42455919999999997,"point {fill-color: blue}"],[59.96241439666667,0.3954815,"point {fill-color: blue}"],[38.77283253833333,0.7081769,"point {fill-color: red}"],[52.34286770333333,0.29376979999999997,"point {fill-color: blue}"],[21.98877415666667,0.1383248,"point {fill-color: red}"],[50.37356623833333,0.2773712,"point {fill-color: blue}"],[12.080005235,0.0981435,"point {fill-color: red}"],[29.329113341666666,3.4658368,"point {fill-color: blue}"],[17.092130383333334,0.1038376,"point {fill-color: red}"],[74.917163235,2.5994357999999997,"point {fill-color: blue}"],[8.132405245,0.0736616,"point {fill-color: red}"],[52.931433815,0.2546939,"point {fill-color: blue}"],[75.70268996333334,1.6510258,"point {fill-color: blue}"],[45.389382411666666,0.24255459999999998,"point {fill-color: blue}"],[26.962280723333333,0.6419199,"point {fill-color: red}"],[39.424155168333336,3.5855287999999996,"point {fill-color: blue}"],[76.90400380333334,4.4713025,"point {fill-color: blue}"],[30.403184581666668,0.127333,"point {fill-color: red}"],[29.021802965,0.0987057,"point {fill-color: blue}"],[34.48582949666667,3.4538837,"point {fill-color: blue}"],[41.205893455,0.12239499999999999,"point {fill-color: red}"],[1.8639237333333334,0.0576263,"point {fill-color: red}"],[41.067952205,0.0847106,"point {fill-color: red}"],[1.8757833433333333,0.039630399999999996,"point {fill-color: red}"],[48.392306383333334,0.14035599999999998,"point {fill-color: red}"],[40.47558774,9.3439129,"point {fill-color: blue}"],[66.45981046333334,1.5875199999999998,"point {fill-color: blue}"],[82.95569771,2.0429387,"point {fill-color: blue}"],[1.271768535,0.14090919999999998,"point {fill-color: red}"],[66.21468290166666,0.33384949999999997,"point {fill-color: blue}"],[49.21878872,0.0773193,"point {fill-color: red}"],[34.057152085,0.0710756,"point {fill-color: red}"],[80.80053234500001,0.3747753,"point {fill-color: blue}"],[60.04510325333334,0.0874165,"point {fill-color: red}"],[22.034956168333334,0.0904994,"point {fill-color: red}"],[46.157779061666666,0.0744843,"point {fill-color: red}"],[85.419151815,0.2379674,"point {fill-color: blue}"],[12.9599633,0.274729,"point {fill-color: red}"],[16.034895655,0.1649873,"point {fill-color: red}"],[35.555929825,0.0453263,"point {fill-color: red}"],[49.593340945,0.2723506,"point {fill-color: blue}"],[79.36076043666667,0.316059,"point {fill-color: blue}"],[69.01801610833334,0.8283473,"point {fill-color: blue}"],[70.063744925,0.4620835,"point {fill-color: blue}"],[80.1751643,0.2826442,"point {fill-color: blue}"],[1.1729676583333333,0.048190199999999996,"point {fill-color: red}"],[42.33278996166667,0.3490057,"point {fill-color: red}"],[4.419674148333334,0.1804642,"point {fill-color: red}"],[60.28483970166667,0.14762119999999998,"point {fill-color: blue}"],[25.991137336666668,0.7594624999999999,"point {fill-color: red}"],[32.78963815666667,0.1544057,"point {fill-color: red}"],[22.052811168333335,0.8696495999999999,"point {fill-color: red}"],[69.418732085,0.24269259999999998,"point {fill-color: blue}"],[32.022322808333335,6.205000699999999,"point {fill-color: blue}"],[8.766117276666668,0.0791263,"point {fill-color: red}"],[82.04198705333333,1.4059723,"point {fill-color: blue}"],[77.825930185,1.6483149,"point {fill-color: blue}"],[5.624886218333334,0.1566382,"point {fill-color: red}"],[73.98357983,0.5510028,"point {fill-color: blue}"],[17.60873581,0.4956273,"point {fill-color: red}"],[20.550943963333335,0.4935788,"point {fill-color: red}"],[45.674281875,0.4088925,"point {fill-color: blue}"],[42.545045435,2.4441832,"point {fill-color: blue}"],[19.882126030000002,0.6622591,"point {fill-color: red}"],[42.6391371,3.4835228999999996,"point {fill-color: blue}"],[76.755180255,0.3556185,"point {fill-color: blue}"],[88.62716236,1.3676065,"point {fill-color: blue}"],[77.31318927333334,0.28793219999999997,"point {fill-color: blue}"],[8.070663053333334,0.1375013,"point {fill-color: red}"],[17.057905591666668,0.09323659999999999,"point {fill-color: red}"],[38.93715281666667,0.25168409999999997,"point {fill-color: red}"],[14.149482925000001,0.2588968,"point {fill-color: red}"],[24.336118831666667,0.055414599999999994,"point {fill-color: red}"],[58.186720385,0.46007719999999996,"point {fill-color: blue}"],[45.53523862666667,3.5395277999999997,"point {fill-color: blue}"],[12.969594291666667,0.2101442,"point {fill-color: red}"],[18.79669068,0.149012,"point {fill-color: red}"],[41.91367900833333,0.0839695,"point {fill-color: red}"],[18.242984068333335,0.0901901,"point {fill-color: red}"],[21.996763806666667,0.1241394,"point {fill-color: red}"],[69.65529542333333,0.7016737,"point {fill-color: blue}"],[49.79530868166667,0.0648183,"point {fill-color: red}"],[51.0066732,0.3343772,"point {fill-color: blue}"],[27.38611686666667,0.2376876,"point {fill-color: red}"],[47.009798626666665,0.07447089999999999,"point {fill-color: red}"],[17.167833645,0.9254233,"point {fill-color: red}"],[15.69020994,0.14488009999999998,"point {fill-color: red}"],[21.131497565,0.1447976,"point {fill-color: red}"],[14.074645466666666,0.0842025,"point {fill-color: red}"],[83.59170068,15.5666993,"point {fill-color: blue}"],[77.83909701500001,0.22315449999999998,"point {fill-color: blue}"],[22.995626943333335,0.1252585,"point {fill-color: red}"],[25.53520855,0.1670047,"point {fill-color: red}"],[52.68116263833333,0.2983606,"point {fill-color: blue}"],[5.8015912033333334,0.0770951,"point {fill-color: red}"],[19.280252418333333,0.08207629999999999,"point {fill-color: red}"],[63.85613409166667,1.32148,"point {fill-color: blue}"],[69.616274215,9.464854299999999,"point {fill-color: blue}"],[68.24069932333333,0.3779628,"point {fill-color: blue}"],[13.993746851666668,0.0789617,"point {fill-color: red}"],[8.878349793333333,0.6475008,"point {fill-color: red}"],[67.668255225,1.9237023,"point {fill-color: blue}"],[6.326890916666667,0.07884379999999999,"point {fill-color: red}"],[24.749510005,0.10695189999999999,"point {fill-color: red}"],[84.21048494666667,0.44550069999999997,"point {fill-color: blue}"],[88.96865076666667,0.29153809999999997,"point {fill-color: blue}"],[48.511442693333336,1.7128995999999999,"point {fill-color: blue}"],[79.66668867166666,0.4309491,"point {fill-color: blue}"],[64.39583149833334,0.90518,"point {fill-color: blue}"],[29.685448296666667,0.0826042,"point {fill-color: red}"],[43.29882289333334,3.8454314999999997,"point {fill-color: blue}"],[0.474605485,0.1236186,"point {fill-color: red}"],[56.049815931666664,0.051351999999999995,"point {fill-color: red}"],[81.99091987833333,0.29411349999999997,"point {fill-color: blue}"],[63.07512019833334,0.3395738,"point {fill-color: blue}"],[29.345477113333335,0.140897,"point {fill-color: red}"],[49.89055974166667,0.30003209999999997,"point {fill-color: blue}"],[83.60091419333334,2.1698577,"point {fill-color: blue}"],[42.50274455,3.9732217,"point {fill-color: blue}"],[12.140651748333333,0.169569,"point {fill-color: red}"],[44.77874430833334,0.2017753,"point {fill-color: red}"],[10.051442241666667,0.1366813,"point {fill-color: blue}"],[21.661612018333333,0.07703,"point {fill-color: red}"],[13.758143198333334,0.2562397,"point {fill-color: red}"],[44.45710445,0.37225959999999997,"point {fill-color: blue}"],[38.24003102666667,0.24395419999999998,"point {fill-color: red}"],[79.88764414833334,0.38577649999999997,"point {fill-color: blue}"],[64.15617373833334,0.40928139999999996,"point {fill-color: blue}"],[57.39915237333334,19.868931099999998,"point {fill-color: blue}"],[10.345730388333333,0.0971137,"point {fill-color: red}"],[65.45581997166667,0.4287224,"point {fill-color: blue}"],[22.55339774,0.0855411,"point {fill-color: red}"],[65.40696494333334,0.3989004,"point {fill-color: blue}"],[79.52298223833334,3.4590891,"point {fill-color: blue}"],[43.76374572333334,0.0729218,"point {fill-color: red}"],[39.038809193333336,0.08668379999999999,"point {fill-color: red}"],[50.86184635666667,0.3091078,"point {fill-color: blue}"],[86.21345749666666,1.8218668,"point {fill-color: blue}"],[59.12925654833334,0.41616359999999997,"point {fill-color: blue}"],[51.09478328833333,0.1282289,"point {fill-color: red}"],[2.871335323333333,0.0803813,"point {fill-color: red}"],[58.85896658833333,0.07109309999999999,"point {fill-color: red}"],[58.11024778666667,0.5860803,"point {fill-color: blue}"],[26.98715775666667,0.21246949999999998,"point {fill-color: red}"],[56.097461368333335,0.0885328,"point {fill-color: red}"],[42.03146309,0.2326391,"point {fill-color: red}"],[45.36751881,0.0840113,"point {fill-color: red}"],[87.11906116166666,3.5698512,"point {fill-color: blue}"],[58.55965826,0.34712879999999996,"point {fill-color: blue}"],[25.816009206666667,0.2552513,"point {fill-color: red}"],[17.404054948333332,0.0900733,"point {fill-color: red}"],[65.73017756833333,0.5520629,"point {fill-color: blue}"],[14.399627151666667,0.149383,"point {fill-color: red}"],[83.27538464333334,3.3537849,"point {fill-color: blue}"],[70.74300259166667,0.237814,"point {fill-color: blue}"],[88.65498865166667,0.2842586,"point {fill-color: blue}"],[38.991331716666664,3.5091847,"point {fill-color: blue}"],[38.14694393333333,0.9561502,"point {fill-color: red}"],[71.10140489833333,0.3424193,"point {fill-color: blue}"],[58.56162732833334,0.16513619999999998,"point {fill-color: red}"],[21.959824858333334,0.0590597,"point {fill-color: red}"],[53.137709226666665,0.0997979,"point {fill-color: red}"],[18.013297526666666,0.2582125,"point {fill-color: red}"],[69.18486378666667,0.4516975,"point {fill-color: blue}"],[13.351460756666667,0.1327903,"point {fill-color: red}"],[85.00581744666667,1.8761225,"point {fill-color: blue}"],[39.07636569,0.0808726,"point {fill-color: red}"],[51.594023211666666,0.2955469,"point {fill-color: blue}"],[5.350187821666667,0.16918629999999998,"point {fill-color: red}"],[58.34890635166667,0.3161403,"point {fill-color: blue}"],[89.686224735,0.8639465,"point {fill-color: blue}"],[18.323332263333334,0.0954989,"point {fill-color: red}"],[34.87626021333333,0.3901031,"point {fill-color: blue}"],[27.952431695,0.1634298,"point {fill-color: red}"],[74.40671932833334,0.281557,"point {fill-color: blue}"],[72.76571325500001,0.38391939999999997,"point {fill-color: blue}"],[7.33391814,0.11623779999999999,"point {fill-color: red}"],[5.975228706666667,0.10757929999999999,"point {fill-color: red}"],[85.54390559833334,0.26751369999999997,"point {fill-color: blue}"],[49.00373446333334,0.8579922,"point {fill-color: blue}"],[16.763886736666667,1.5631433,"point {fill-color: red}"],[67.04111677666667,0.33007749999999997,"point {fill-color: blue}"],[42.47902446,1.13818,"point {fill-color: red}"],[46.97548657833333,0.5071719,"point {fill-color: red}"],[86.612332575,1.7822205999999998,"point {fill-color: blue}"],[2.4378143233333334,0.1488296,"point {fill-color: red}"],[27.40071021,0.7840035999999999,"point {fill-color: red}"],[73.74944608166666,0.27938409999999997,"point {fill-color: blue}"],[31.61127565,0.3431031,"point {fill-color: blue}"],[34.180652725,0.2551906,"point {fill-color: red}"],[37.12643061666667,1.6426549,"point {fill-color: blue}"],[1.3390968633333333,0.0552043,"point {fill-color: red}"],[6.727067206666667,0.09216139999999999,"point {fill-color: red}"],[25.780578365,0.6253052,"point {fill-color: blue}"],[79.14681766333334,5.0709165,"point {fill-color: blue}"],[69.817400345,0.3040488,"point {fill-color: blue}"],[10.202245455,0.13399239999999998,"point {fill-color: red}"],[4.346626401666667,0.0546916,"point {fill-color: red}"],[42.897410746666665,0.26411,"point {fill-color: blue}"],[89.076378615,0.3723878,"point {fill-color: blue}"],[21.729789506666666,0.0486503,"point {fill-color: red}"],[35.79139018166667,1.7933103,"point {fill-color: blue}"],[23.373755308333333,0.0816527,"point {fill-color: red}"],[26.64472644,0.2566372,"point {fill-color: red}"],[28.746672148333335,0.322054,"point {fill-color: blue}"],[89.08841646500001,9.4912413,"point {fill-color: blue}"],[37.482617760000004,0.09839439999999999,"point {fill-color: red}"],[29.311321578333335,0.32794029999999996,"point {fill-color: blue}"],[5.457457761666666,0.0734413,"point {fill-color: red}"],[85.213222265,1.6167749999999999,"point {fill-color: blue}"],[61.404976751666666,0.6279275,"point {fill-color: blue}"],[9.051504411666667,0.1886284,"point {fill-color: red}"],[63.24859332166667,4.039847,"point {fill-color: blue}"],[9.03212719,0.0734992,"point {fill-color: red}"],[40.51894098333334,0.8137154,"point {fill-color: blue}"],[55.42688147166667,0.32365459999999996,"point {fill-color: blue}"],[75.122186805,0.4001491,"point {fill-color: blue}"],[70.62906459833333,0.6791537,"point {fill-color: blue}"],[1.3122326683333334,0.0831619,"point {fill-color: red}"],[59.76903205166667,0.08785119999999999,"point {fill-color: red}"],[25.388045336666668,0.29014239999999997,"point {fill-color: red}"],[0.920790815,0.6097923,"point {fill-color: red}"],[83.51527025166666,4.7549209999999995,"point {fill-color: blue}"],[83.44897519833333,0.9816033,"point {fill-color: blue}"],[27.450029371666666,0.2180827,"point {fill-color: blue}"],[17.821980085,0.0455135,"point {fill-color: red}"],[22.403803613333334,0.16736199999999998,"point {fill-color: red}"],[15.961839651666667,0.14143709999999998,"point {fill-color: red}"],[19.012584058333335,0.3111471,"point {fill-color: red}"],[31.62366229166667,3.7095799,"point {fill-color: blue}"],[1.8704391166666667,0.057992999999999996,"point {fill-color: red}"],[71.42424322333333,0.27396929999999997,"point {fill-color: blue}"],[83.85850077333333,0.49724549999999995,"point {fill-color: blue}"],[62.26884993666667,0.2524341,"point {fill-color: blue}"],[20.45903944166667,0.1138647,"point {fill-color: red}"],[3.086111831666667,0.043722,"point {fill-color: red}"],[71.28978561333334,0.37220909999999996,"point {fill-color: blue}"],[28.870723481666666,0.4992834,"point {fill-color: red}"],[2.959983771666667,0.14606249999999998,"point {fill-color: red}"],[85.65798081,0.2530921,"point {fill-color: blue}"],[34.65570706166667,0.1287787,"point {fill-color: red}"],[13.682872056666668,0.21241089999999999,"point {fill-color: red}"],[34.115176395,0.1617923,"point {fill-color: red}"],[25.928021343333334,0.32191949999999997,"point {fill-color: blue}"],[21.103599826666667,0.0800965,"point {fill-color: red}"],[85.10701037166668,0.4554698,"point {fill-color: blue}"],[69.37371559,0.3305394,"point {fill-color: blue}"],[73.361643245,0.8546243,"point {fill-color: blue}"],[1.922387565,0.0474819,"point {fill-color: red}"],[10.94825157,0.8821517999999999,"point {fill-color: red}"],[60.22903840666667,0.6054992,"point {fill-color: blue}"],[62.832648585,1.776465,"point {fill-color: blue}"]]);
  options.hAxis = {
    title: 'minutes'
  };
  options.vAxis = {
    title: 'seconds'
  };
  return new google.visualization.ScatterChart(document.getElementById('chart_div_aws-lambda-10-minutes\/old_scatter'));
});
</script>
<figure>
  <div id="chart_div_aws-lambda-10-minutes/old_scatter"></div>
  <figcaption class="imageCaption"><h4>Examples of AWS Lambda idle instances lifecycle in June 2019</h4></figcaption>
</figure>
<p>This chart plots the response duration (Y-axis) by the interval since the previous requests (X-axis). Each point represents a single request in the dataset. Blue points are cold starts, and red points are responses from warm instances.</p>
<p>While some cold starts happen after 10 minutes or so, many instances survive up to 1 hour.</p>
<h2 id="post-july-the-fixed-lifespan-of-10-minutes">Post-July: the fixed lifespan of 10 minutes</h2>
<p>The chart has changed in July:</p>




  





<script type="text/javascript">
addChart((data, options) => {
  data.addColumn('number', 'ID');
  data.addColumn('number', 'Value');
  data.addColumn({ 'type': 'string', 'role': 'style' });
  data.addRows([[5.205542855,0.0729751,"point {fill-color: red}"],[2.950550171666667,0.10641429999999999,"point {fill-color: red}"],[21.773925093333332,0.6577742,"point {fill-color: blue}"],[17.136447813333334,4.2055996,"point {fill-color: blue}"],[8.678438453333333,0.08126789999999999,"point {fill-color: red}"],[7.511579011666667,0.09276709999999999,"point {fill-color: red}"],[9.738658645000001,0.11244219999999999,"point {fill-color: red}"],[55.04185414,3.9711586,"point {fill-color: blue}"],[21.243548555,2.8069797999999997,"point {fill-color: blue}"],[38.209268525,4.2607133,"point {fill-color: blue}"],[55.89774716833333,0.5142095,"point {fill-color: blue}"],[32.63548630166667,3.4478115999999996,"point {fill-color: blue}"],[5.314496326666667,0.142228,"point {fill-color: red}"],[18.875097523333334,0.3599982,"point {fill-color: blue}"],[21.034865136666667,0.2502255,"point {fill-color: blue}"],[2.1307685133333334,0.1471817,"point {fill-color: red}"],[27.960624361666667,0.7032609,"point {fill-color: blue}"],[5.02703192,0.1417984,"point {fill-color: red}"],[47.290301013333334,4.3324842,"point {fill-color: blue}"],[58.04031498,0.236867,"point {fill-color: blue}"],[33.783128768333334,0.3956788,"point {fill-color: blue}"],[27.090651733333335,0.42828099999999997,"point {fill-color: blue}"],[22.565576651666667,1.5347891,"point {fill-color: blue}"],[32.63414636,0.3581791,"point {fill-color: blue}"],[24.458362688333334,0.23919769999999999,"point {fill-color: blue}"],[20.523905925,0.6085069,"point {fill-color: blue}"],[24.79081742,0.5613693,"point {fill-color: blue}"],[16.008860071666668,0.4109767,"point {fill-color: blue}"],[13.163823415,0.9173602,"point {fill-color: blue}"],[38.091357423333335,1.5530088,"point {fill-color: blue}"],[21.81296898,0.2500099,"point {fill-color: blue}"],[26.801957608333336,1.5911244,"point {fill-color: blue}"],[35.680861621666665,0.3148438,"point {fill-color: blue}"],[16.67575253,1.5898845,"point {fill-color: blue}"],[7.109732576666667,0.056506499999999994,"point {fill-color: red}"],[1.9068108266666668,0.0918271,"point {fill-color: red}"],[15.357300865000001,4.3314313,"point {fill-color: blue}"],[3.885373285,0.068304,"point {fill-color: red}"],[26.202351248333333,0.28664629999999997,"point {fill-color: blue}"],[23.011914403333332,1.6756772999999998,"point {fill-color: blue}"],[1.9420863933333334,0.25440019999999997,"point {fill-color: red}"],[29.297437353333333,0.264167,"point {fill-color: blue}"],[29.40653468,0.354613,"point {fill-color: blue}"],[9.99938658,0.5570834,"point {fill-color: red}"],[7.18001534,0.6364238999999999,"point {fill-color: red}"],[7.807944281666667,0.0748373,"point {fill-color: red}"],[43.669602233333336,0.3297212,"point {fill-color: blue}"],[17.135989770000002,0.2279042,"point {fill-color: blue}"],[49.769005220000004,1.6557754999999998,"point {fill-color: blue}"],[48.00394794166667,0.2524152,"point {fill-color: blue}"],[5.506282056666667,0.1191433,"point {fill-color: red}"],[28.164396455000002,0.35999549999999997,"point {fill-color: blue}"],[33.04928259166667,0.757203,"point {fill-color: blue}"],[5.682673811666667,0.0683164,"point {fill-color: red}"],[3.21177282,0.1632293,"point {fill-color: red}"],[21.07322926666667,0.3272121,"point {fill-color: blue}"],[0.9020255466666667,0.2184137,"point {fill-color: red}"],[9.788402721666667,0.0699385,"point {fill-color: red}"],[9.333410851666667,0.7360896,"point {fill-color: red}"],[38.19660054166667,0.2631861,"point {fill-color: blue}"],[12.876818468333333,0.24644329999999998,"point {fill-color: blue}"],[14.977115771666668,5.248785499999999,"point {fill-color: blue}"],[30.234662506666666,1.3478455999999999,"point {fill-color: blue}"],[2.5742192966666666,0.0633237,"point {fill-color: red}"],[13.865113048333333,0.22283519999999998,"point {fill-color: blue}"],[10.794890818333334,2.1035478,"point {fill-color: blue}"],[28.932961621666667,0.9330444,"point {fill-color: blue}"],[2.8972817833333333,1.1923922999999998,"point {fill-color: red}"],[3.14905917,0.0735439,"point {fill-color: red}"],[14.62542796,0.3699729,"point {fill-color: blue}"],[28.146624486666667,0.2253889,"point {fill-color: blue}"],[57.90674627333333,1.3434393,"point {fill-color: blue}"],[26.862139135,0.2592239,"point {fill-color: blue}"],[33.539368198333335,0.2819228,"point {fill-color: blue}"],[3.3368404883333334,0.1401955,"point {fill-color: red}"],[10.948546381666667,0.2810572,"point {fill-color: blue}"],[12.40320724,4.5088441999999995,"point {fill-color: blue}"],[11.584143055,0.5768689,"point {fill-color: blue}"],[16.923735803333333,1.2714741,"point {fill-color: blue}"],[28.787613108333336,0.6679575999999999,"point {fill-color: blue}"],[52.41602769,1.9783807,"point {fill-color: blue}"],[21.883031556666666,0.47135439999999995,"point {fill-color: blue}"],[30.547405513333334,0.5419035999999999,"point {fill-color: blue}"],[27.964696043333333,0.43265149999999997,"point {fill-color: blue}"],[17.70462578,0.3022551,"point {fill-color: blue}"],[49.44523039833334,1.5939056,"point {fill-color: blue}"],[9.012838056666666,0.0721861,"point {fill-color: red}"],[20.959079543333335,0.3322603,"point {fill-color: blue}"],[48.506768575,1.2003183,"point {fill-color: blue}"],[19.94375859,4.4317548,"point {fill-color: blue}"],[22.395982421666666,0.2636794,"point {fill-color: blue}"],[25.350514098333335,0.2810781,"point {fill-color: blue}"],[43.09774109833334,4.1969408,"point {fill-color: blue}"],[26.227272545,0.2525319,"point {fill-color: blue}"],[44.967660001666665,4.2895959999999995,"point {fill-color: blue}"],[15.747613823333333,2.2996567,"point {fill-color: blue}"],[11.4916439,0.2540919,"point {fill-color: blue}"],[4.66845564,0.39803,"point {fill-color: red}"],[15.619132365,0.2643333,"point {fill-color: blue}"],[10.560933736666668,1.681524,"point {fill-color: blue}"],[4.895317276666667,0.0667763,"point {fill-color: red}"],[11.710955435,4.5365003,"point {fill-color: blue}"],[42.15546887166667,0.2732461,"point {fill-color: blue}"],[55.932061585,0.5618080999999999,"point {fill-color: blue}"],[5.7023475716666665,0.2815375,"point {fill-color: red}"],[4.538830501666666,0.1070778,"point {fill-color: red}"],[55.03088436,0.5004191,"point {fill-color: blue}"],[0.236882975,0.066214,"point {fill-color: red}"],[52.800519040000005,1.8903378,"point {fill-color: blue}"],[0.012698408333333334,0.0777326,"point {fill-color: red}"],[12.825518370000001,0.3066105,"point {fill-color: blue}"],[17.521510253333332,0.474263,"point {fill-color: blue}"],[46.33441173166667,0.2527368,"point {fill-color: blue}"],[4.665199341666667,0.0459963,"point {fill-color: red}"],[22.036639306666668,0.2759649,"point {fill-color: blue}"],[4.6440072416666665,0.08288949999999999,"point {fill-color: red}"],[9.667525656666667,0.13132359999999998,"point {fill-color: red}"],[10.925694471666667,0.3007049,"point {fill-color: blue}"],[7.546159071666667,0.06749909999999999,"point {fill-color: red}"],[38.92562516666667,0.36546409999999996,"point {fill-color: blue}"],[15.11285829,10.3576766,"point {fill-color: blue}"],[54.970940925,0.3952174,"point {fill-color: blue}"],[10.460719738333333,1.5359456999999999,"point {fill-color: blue}"],[20.071385555,0.2511376,"point {fill-color: blue}"],[13.375347798333333,0.290476,"point {fill-color: blue}"],[15.230204370000001,0.6791967999999999,"point {fill-color: blue}"],[9.594194141666668,0.2098504,"point {fill-color: red}"],[43.56448059,0.4917996,"point {fill-color: blue}"],[17.381496876666667,0.1719635,"point {fill-color: blue}"],[4.090363793333333,0.08397009999999999,"point {fill-color: red}"],[31.032993763333334,0.219763,"point {fill-color: blue}"],[12.852999543333334,1.6204977999999999,"point {fill-color: blue}"],[13.009825766666667,0.2275748,"point {fill-color: blue}"],[44.51589496333333,0.24906709999999999,"point {fill-color: blue}"],[48.29526980833334,9.4752444,"point {fill-color: blue}"],[28.11104407166667,4.3849355999999995,"point {fill-color: blue}"],[44.79007943166667,3.7983647,"point {fill-color: blue}"],[19.523992656666668,1.0391688,"point {fill-color: blue}"],[28.648346773333333,0.2724181,"point {fill-color: blue}"],[4.781787685,0.11107399999999999,"point {fill-color: red}"],[27.014539446666667,0.3432175,"point {fill-color: blue}"],[24.142719325,1.7235786,"point {fill-color: blue}"],[23.134774085,0.2685766,"point {fill-color: blue}"],[25.259148943333333,0.9360737,"point {fill-color: blue}"],[21.726234818333335,0.45835929999999997,"point {fill-color: blue}"],[4.732111293333333,0.0694786,"point {fill-color: red}"],[17.285875865,0.3481688,"point {fill-color: blue}"],[17.878259521666667,0.6408619,"point {fill-color: blue}"],[3.2312401833333335,0.0841282,"point {fill-color: red}"],[3.6145708216666668,0.21329199999999998,"point {fill-color: red}"],[35.61125183833333,0.7937889,"point {fill-color: blue}"],[26.891271548333332,0.39790719999999996,"point {fill-color: blue}"],[28.840341006666666,0.2858997,"point {fill-color: blue}"],[17.986112015,0.2457246,"point {fill-color: blue}"],[29.708526795,1.1692529,"point {fill-color: blue}"],[29.849828813333335,0.5814541,"point {fill-color: blue}"],[17.218032018333332,0.73374,"point {fill-color: blue}"],[7.6352141216666665,0.0787389,"point {fill-color: red}"],[46.09706425833333,0.34692599999999996,"point {fill-color: blue}"],[24.77022902166667,0.4238134,"point {fill-color: blue}"],[32.04818470833333,0.453159,"point {fill-color: blue}"],[39.297754938333334,0.3214896,"point {fill-color: blue}"],[54.77747788333333,0.5726214,"point {fill-color: blue}"],[4.967630998333333,0.0706193,"point {fill-color: red}"],[30.491208241666666,0.4764519,"point {fill-color: blue}"],[0.45684577833333334,0.0727438,"point {fill-color: red}"],[25.982041776666666,0.33197309999999997,"point {fill-color: blue}"],[57.58344489666667,0.213691,"point {fill-color: blue}"],[25.960420346666666,0.21276109999999998,"point {fill-color: blue}"],[29.374855788333335,0.2368525,"point {fill-color: blue}"],[8.095142546666667,0.0777544,"point {fill-color: red}"],[10.730351386666667,0.46126399999999995,"point {fill-color: blue}"],[22.310034181666666,0.3214547,"point {fill-color: blue}"],[45.88233271166667,4.3751836,"point {fill-color: blue}"],[6.267874396666667,0.7806938,"point {fill-color: red}"],[41.767281925,0.24014039999999998,"point {fill-color: blue}"],[7.488845013333333,0.0759958,"point {fill-color: red}"],[52.925085450000005,0.41132949999999996,"point {fill-color: blue}"],[6.531748058333333,0.44180929999999996,"point {fill-color: red}"],[21.633974655,3.5737514999999997,"point {fill-color: blue}"],[25.751592221666666,0.3723629,"point {fill-color: blue}"],[57.413430051666666,0.23598,"point {fill-color: blue}"],[48.22025750833333,2.1119955,"point {fill-color: blue}"],[2.4710389033333335,0.44573219999999997,"point {fill-color: red}"],[2.8164372933333333,0.11949109999999999,"point {fill-color: red}"],[29.739453695,1.3511077,"point {fill-color: blue}"],[59.85920454,3.6903542,"point {fill-color: blue}"],[5.759704396666667,0.0836941,"point {fill-color: red}"],[21.05319593,0.45559279999999996,"point {fill-color: blue}"],[20.98677186,0.3672477,"point {fill-color: blue}"],[14.277599351666668,1.5439053999999999,"point {fill-color: blue}"],[43.123278498333335,0.26792279999999996,"point {fill-color: blue}"],[7.085624431666667,0.074601,"point {fill-color: red}"],[12.454860156666667,5.1216139,"point {fill-color: blue}"],[13.457299926666668,0.5611008,"point {fill-color: blue}"],[30.474265956666667,0.20301829999999998,"point {fill-color: blue}"],[1.8632459883333334,0.07394099999999999,"point {fill-color: red}"],[4.897079163333333,0.1449926,"point {fill-color: red}"],[26.580458235000002,0.8365707,"point {fill-color: blue}"],[15.221323648333334,3.8955243,"point {fill-color: blue}"],[14.704649895000001,0.2722969,"point {fill-color: blue}"],[41.260677810000004,1.4169186,"point {fill-color: blue}"],[54.903552536666666,1.9525209,"point {fill-color: blue}"],[8.192975275,0.0726103,"point {fill-color: red}"],[31.99967567166667,0.2081352,"point {fill-color: blue}"],[36.805746365,3.8747236999999997,"point {fill-color: blue}"],[7.194568875,0.0575447,"point {fill-color: red}"],[17.264154758333333,0.272285,"point {fill-color: blue}"],[14.121679611666666,3.2790531,"point {fill-color: blue}"],[17.39999362,0.2981274,"point {fill-color: blue}"],[27.284415751666668,0.5823134,"point {fill-color: blue}"],[26.671761775,1.7537691,"point {fill-color: blue}"],[9.129264043333334,0.3617609,"point {fill-color: red}"],[3.6000143816666665,0.13209959999999998,"point {fill-color: red}"],[26.62542948,2.2690422999999997,"point {fill-color: blue}"],[0.4851755716666667,0.1090476,"point {fill-color: red}"],[17.10813361,3.6408894999999997,"point {fill-color: blue}"],[47.54190605,0.44462009999999996,"point {fill-color: blue}"],[45.536380155,0.26159869999999996,"point {fill-color: blue}"],[33.611594231666665,8.890205,"point {fill-color: blue}"],[41.142801545,0.23901019999999998,"point {fill-color: blue}"],[9.964273803333333,0.13344799999999998,"point {fill-color: red}"],[5.773736171666667,0.072779,"point {fill-color: red}"],[4.383194816666666,0.0799802,"point {fill-color: red}"],[2.3502029483333335,0.0455345,"point {fill-color: red}"],[28.327197445,0.3577012,"point {fill-color: blue}"],[21.279258655,0.2260094,"point {fill-color: blue}"],[5.166607976666667,0.08948779999999999,"point {fill-color: red}"],[18.53387572666667,4.200319299999999,"point {fill-color: blue}"],[18.792424438333335,4.4998143,"point {fill-color: blue}"],[2.407105663333333,0.152107,"point {fill-color: red}"],[14.70829689,1.97461,"point {fill-color: blue}"],[12.500928955000001,0.35082199999999997,"point {fill-color: blue}"],[37.29890278,0.2830051,"point {fill-color: blue}"],[5.763145691666667,0.08747669999999999,"point {fill-color: red}"],[10.517388195,0.2602002,"point {fill-color: blue}"],[19.645800401666666,1.9795189999999998,"point {fill-color: blue}"],[3.022166921666667,0.06750969999999999,"point {fill-color: red}"],[48.79278951333333,0.28050939999999996,"point {fill-color: blue}"],[18.927243258333334,0.3267169,"point {fill-color: blue}"],[47.107094028333336,5.2347157,"point {fill-color: blue}"],[26.29755139,0.2246791,"point {fill-color: blue}"],[10.797636368333334,3.6805765,"point {fill-color: blue}"],[34.219281305,2.0022965999999998,"point {fill-color: blue}"],[56.41317888666667,4.1769321999999995,"point {fill-color: blue}"],[4.5590787950000005,1.4886850999999999,"point {fill-color: red}"],[24.624643006666666,0.3440535,"point {fill-color: blue}"],[13.489810871666666,0.7923926,"point {fill-color: blue}"],[53.48551212,0.20091299999999998,"point {fill-color: blue}"],[1.24179509,0.08562399999999999,"point {fill-color: red}"],[3.9594451250000002,0.1512782,"point {fill-color: red}"],[26.92009459166667,0.3054511,"point {fill-color: blue}"],[25.25817851,1.9681855,"point {fill-color: blue}"],[17.85744654666667,0.3193709,"point {fill-color: blue}"],[46.36070672,0.2474633,"point {fill-color: blue}"],[58.90165328166667,0.2810061,"point {fill-color: blue}"],[18.364332798333333,0.4058022,"point {fill-color: blue}"],[12.203593395,10.4270149,"point {fill-color: blue}"],[21.78381891,0.24661989999999998,"point {fill-color: blue}"],[44.33441240166667,2.3375190999999997,"point {fill-color: blue}"],[48.22288827333333,0.3076607,"point {fill-color: blue}"],[25.653182428333334,0.3227139,"point {fill-color: blue}"],[6.282574893333334,0.07130449999999999,"point {fill-color: red}"],[28.147908983333334,4.9963152,"point {fill-color: blue}"],[3.3192418766666667,0.086087,"point {fill-color: red}"],[30.014725375,0.3139358,"point {fill-color: blue}"],[49.40947267333333,0.35711339999999997,"point {fill-color: blue}"],[13.388344886666667,1.9394331999999999,"point {fill-color: blue}"],[17.28361115,0.3440081,"point {fill-color: blue}"],[25.255335603333332,1.3016328,"point {fill-color: blue}"],[48.93193205,0.2049797,"point {fill-color: blue}"],[9.406192488333334,0.26661799999999997,"point {fill-color: red}"],[17.51506733,0.5035815,"point {fill-color: blue}"],[58.26915884666667,0.3745981,"point {fill-color: blue}"],[16.35743708,3.3837067,"point {fill-color: blue}"],[55.661343255,3.7016237999999997,"point {fill-color: blue}"],[26.425328968333332,0.3398334,"point {fill-color: blue}"],[12.009492093333334,1.2590331,"point {fill-color: blue}"],[13.432812096666668,0.2782039,"point {fill-color: blue}"],[18.260913696666666,2.4154196,"point {fill-color: blue}"],[9.698259533333333,0.25290809999999997,"point {fill-color: red}"],[16.484434635,0.4616938,"point {fill-color: blue}"],[5.092524091666667,0.2843421,"point {fill-color: red}"],[38.69841969666667,0.6556539,"point {fill-color: blue}"],[12.461642073333334,4.2124334999999995,"point {fill-color: blue}"],[11.512802061666667,0.2357192,"point {fill-color: blue}"],[22.023669928333334,2.3117810999999997,"point {fill-color: blue}"],[21.585668296666668,0.2358108,"point {fill-color: blue}"],[9.936649116666667,0.1610255,"point {fill-color: red}"],[43.849753195,5.2367669,"point {fill-color: blue}"],[4.758206058333333,0.14549969999999998,"point {fill-color: red}"],[3.2772610983333332,0.1238232,"point {fill-color: red}"],[51.32073028833334,1.9391188,"point {fill-color: blue}"],[24.802597218333332,0.2987158,"point {fill-color: blue}"],[30.024298853333335,0.2029456,"point {fill-color: blue}"],[23.994000766666666,0.662655,"point {fill-color: blue}"],[3.722260416666667,0.0834248,"point {fill-color: red}"],[45.91238661166667,0.8396652,"point {fill-color: blue}"],[5.921298566666667,0.0813429,"point {fill-color: red}"]]);
  options.hAxis = {
    title: 'minutes'
  };
  options.vAxis = {
    title: 'seconds'
  };
  return new google.visualization.ScatterChart(document.getElementById('chart_div_aws-lambda-10-minutes\/new_scatter'));
});
</script>
<figure>
  <div id="chart_div_aws-lambda-10-minutes/new_scatter"></div>
  <figcaption class="imageCaption"><h4>Examples of AWS Lambda idle instances lifecycle in August 2019</h4></figcaption>
</figure>
<p>There is no ambiguity anymore: every instance lives for precisely 10 minutes after the last request.</p>
<p>This behavior is similar to Azure Functions, but the value is two times shorter: 10 minutes for AWS versus 20 minutes for Azure.</p>
<h2 id="month-over-month-view">Month-over-month view</h2>
<p>I broke down the data set by month and calculated the periods when an idle instance is recycled with 10%, 50%, and 90% probabilities. Here is the chart that I produced, the higher line means the higher duration of survival:</p>




  







<script type="text/javascript">
addChart((data, options) => {
  data.addColumn('string', 'Time');

  const points = [{"items":[["March",29],["April",28],["May",29],["June",21],["July",11],["August",10]],"name":"90% survive"},{"items":[["March",44],["April",44],["May",46],["June",37],["July",15],["August",10]],"name":"50% survive"},{"items":[["March",59],["April",59],["May",60],["June",58],["July",16],["August",11]],"name":"10% survive"}];
  const seriesCount = points.length;
  for (var j = 0; j < seriesCount; j++) {
    data.addColumn('number', points[j].name);
  }

  const rows = [];
  for (var i = 0; i < points[0].items.length; i++)
  {
    const item = [points[0].items[i][0]];
    for (var j = 0; j < seriesCount; j++) {
      item.push(points[j].items[i][1]);
    }
    rows.push(item);
  }

  data.addRows(rows);
  console.debug(rows);
  options.lineWidth = 3;
  options.hAxis = {
    title: '2019 by month'
  };
  options.vAxis = {
    title: 'minutes',
    maxValue: 1.0
  };
  options.legend = { position: 'top' };
  options.series = [{"color":"green"},{"color":"yellow"},{"color":"red"}];
  return new google.visualization.AreaChart(document.getElementById('chart_div_aws-lambda-10-minutes\/interval_history'));
});
</script>
<figure>
  <div id="chart_div_aws-lambda-10-minutes/interval_history"></div>
  <figcaption class="imageCaption"><h4>The typical lifetime of an idle AWS Lambda</h4></figcaption>
</figure>
<p>Once again, we can see how the broader range of probabilistic lifespans collapsed into the deterministic 10-minute threshold.</p>
<h2 id="practical-takeaway">Practical Takeaway</h2>
<p>Applications with infrequent invocations running in AWS Lambda might experience more cold starts compared to pre-July time.</p>
<p>Cold starts are a major perceived pain point of serverless services. While cloud providers are fighting the cold starts on many fronts, it&rsquo;s interesting to see a change in AWS Lambda, which seemingly goes the opposite direction.</p>
<p>On the other hand, the predictability of a fixed lifespan makes the keep-it-warm workarounds more reliable. Whenever you feel a cold start might be a problem for your Lambda, you might want to issue a warm-up request every 5 minutes. You can find an example of doing so in a structured way in <a href="/2018/08/aws-lambda-warmer-as-pulumi-component/">AWS Lambda Warmer as Pulumi Component</a>.</p>
<p>Read more about <a href="/serverless/coldstarts/aws/">Cold Starts in AWS Lambda</a>.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/cold-starts" term="cold-starts" label="Cold Starts" />
                             
                                <category scheme="https://mikhail.io/tags/aws" term="aws" label="AWS" />
                             
                                <category scheme="https://mikhail.io/tags/aws-lambda" term="aws-lambda" label="AWS Lambda" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[How to Measure the Cost of Azure Functions]]></title>
            <link href="https://mikhail.io/2019/08/how-to-measure-the-cost-of-azure-functions/"/>
            <id>https://mikhail.io/2019/08/how-to-measure-the-cost-of-azure-functions/</id>
            
            <published>2019-08-07T00:00:00+00:00</published>
            <updated>2019-08-07T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Azure pricing can be complicated—to get the most value out of your cloud platform, you need to know how to track spend and measure the costs incurred by Azure Functions.</blockquote><p>Azure Functions can be hosted in multiple ways: there&rsquo;s an App Service plan with a fixed cost per hour, a new Premium plan with both fixed and variable price components, not to mention self-managed options based on container technologies. Instead of any of those three, this article focuses on Consumption plan—the classic serverless offering billed purely based on the actual usage.</p>
<h2 id="how-is-serverless-different">How Is Serverless Different?</h2>
<p>Serverless Functions have three important properties, each of them having a significant impact on the way we deal with the cost of an application:</p>
<ul>
<li>
<p><strong>Low management overhead</strong>: the cloud provider manages the service. The total cost of ownership is minimal: developers create code to solve business problems—the rest is taken care of.</p>
</li>
<li>
<p><strong>Pay-per-use</strong>: Functions are charged per actual executions. Nothing is reserved in advance, so the cost of running a Function App grows linearly with the application demand.</p>
</li>
<li>
<p><strong>Elastically scalable</strong>: when a Function is idle, Azure scales the infrastructure down to zero with no associated cost. Whenever the workload grows, Azure brings enough capacity to serve all the demand.</p>
</li>
</ul>
<h4 id="why-does-this-matter">Why does this matter?</h4>
<p>In the past, the cost of infrastructure running an application and the value that the application provides were separated. A company would run multiple applications, each having numerous components and services, on a shared infrastructure of dedicated hardware, a pool of VMs, or IaaS services in the cloud. It&rsquo;s quite complicated to tear the expenses apart and determine the exact cost of each application, let alone a particular component. Moreover, the investments are planned and executed in advance, so the infrastructure can&rsquo;t follow the elasticity of workload and ends up overprovisioned and underutilized.</p>
<p>Today, the application portfolio can run on serverless functions, each component becoming a separate Function App. In turn, each App may contain multiple functions. Since the company pays per actual use, they can understand and manage the exact cost of each component.</p>
<p>This ability enables the business to:</p>
<ul>
<li>See which features are most profitable, and which ones are too expensive;</li>
<li>Optimize the Functions that have high impact on the invoice and ignore the rest;</li>
<li>Make informed decisions of whether to spend engineering time on such optimizations or create new business value while paying a premium to the cloud provider.</li>
</ul>
<p>Note that the cost information is retrospective: the actual numbers come after the fact of spending. The lack of budgeting makes decision makers nervous: they are used to plan the cost of infrastructure long in advance. Therefore, our goal is to understand the cost structure and be able to predict the changes in invoices as applications and business evolve.</p>
<h2 id="billing-model-of-the-consumption-plan">Billing Model of the Consumption Plan</h2>
<p>Let&rsquo;s dissect the structure of the Consumption plan. There are two core components of the cost of serverless Functions in Azure: <strong>Execution Count</strong> and <strong>Execution Time</strong>.</p>
<p><em>Execution Count</em> is straightforward. Each Function defines a trigger—an event which causes the code to execute. It can be an incoming HTTP request or a message in a given queue. Every call counts: you get charged $0.20 per million executions. This component of the cost can be substantially reduced if you batch events: process several events in a single execution.</p>
<p>The second cost component is called <em>Execution Time</em> on the pricing page, which isn&rsquo;t exactly correct: it depends on both completion time and memory consumption and is metered in <em>GB-seconds</em>. You pay $16 per million GB-seconds. That is, if a Function runs 1 million times, it always consumes 1 GB of memory and completes in 1 second, you pay $16.</p>
<p>The memory consumption is always rounded up to the next 128 MB, and the minimum time charge is 100 milliseconds. Therefore, the minimal time charge is again $0.20 per million executions.</p>
<p>Holistically, there are other cost components of running serverless applications not tied directly to the Azure Functions service: I&rsquo;ll briefly touch on those at the end of the article.</p>
<h2 id="azure-bill-and-cost-analysis">Azure Bill and Cost Analysis</h2>
<p>The prominent place to see the cost of operating Azure Functions is the monthly bill. Open the Azure portal and navigate to your subscription&rsquo;s page, choose <em>Invoices</em> and then a period to look at. Here is a sample report from a subscription of mine:</p>
<p><img src="invoiced-units.png" alt="Consumed Function Units on an Azure invoice"></p>
<figcaption><h4>Consumed Function Units on an Azure invoice</h4></figcaption>
<p>You should be able to see the incurred change for the two metrics we discussed above: <em>Total Executions</em> and <em>Execution Time</em> (the metric in GB-seconds).</p>
<p>With <em>Cost Analysis</em> tool, you can see the billing data at per day granularity. Depending on your analysis goal, you might need more details than that. For instance, you may wonder how the cost is spread over periods within a day or be able to predict the future cost based on short trials before the application (or its newer version) goes into production.</p>
<p>Let&rsquo;s see how to dig deeper into the details.</p>
<h2 id="azure-monitor-metrics">Azure Monitor Metrics</h2>
<p><strong>Azure Monitor</strong> is a service for collecting, analyzing, and acting on telemetry from applications running in the Azure cloud. While mostly focusing on performance, it also collects some useful data related to service consumption.</p>
<p>Azure Functions issue two cost-related metrics into Azure Monitor: <em>Function Execution Count</em> and <em>Function Execution Units</em>. Each metric emits a value once every minute.</p>
<p>To see these metrics in the Azure portal, navigate to the <em>Monitor</em> service and select the <em>Metrics</em> item on the left. Click <em>Select a Resource</em> button and find the Function App that you want to investigate. Note that the <em>Resource Type</em> should be set to <em>App Service</em>: there&rsquo;s no value called <em>Function App</em> in that dropdown.</p>
<p>Select <em>Function Execution Count</em> in the Metrics dropdown, <em>Sum</em> as the aggregation type, and adjust the period selector as needed. You should now see a chart similar to this one:</p>
<p><img src="monitor-execution-count.png" alt="Function Execution Count in Azure Monitor"></p>
<figcaption><h4>Function Execution Count in Azure Monitor</h4></figcaption>
<p>In this particular case, there were about 4,940 executions in the last 30 minutes. You can view the stats per minute; in this example, all the executions come from a single spike—something that I might want to investigate.</p>
<p>Now, switch the metric to <em>Function Execution Units</em>. Alternatively, you can add it to the same chart, but the scale of the two metrics is so different that you won&rsquo;t be able to see both lines at the same time:</p>
<p><img src="monitor-execution-units.png" alt="Function Execution Units in Azure Monitor"></p>
<figcaption><h4>Function Execution Units in Azure Monitor</h4></figcaption>
<p>The value conversion gets a bit tricky here. The chart shows a total of 634.13 million <em>Function Execution Units</em> consumed in the last hour. These are not the GB-seconds mentioned above, though: the metric is nominated in MB-milliseconds. To convert this to GB-seconds, divide it by 1,024,000. So, in this case, my Function App consumed 634,130,000 / 1,024,000 = 619 GB-seconds in the last half-an-hour.</p>
<h2 id="sum-it-up">Sum It Up</h2>
<p>Let&rsquo;s estimate the monthly cost of the application based on the metrics above. We start with the  half-an-hour calculation:</p>
<pre tabindex="0"><code>Execution Count = 4,940 * $0.20 / 1,000,000 = $0.000988
Execution Time = 634,130,000 / 1,024,000 * $16 / 1,000,000 = $0.009908
30 Min Total = Execution Count + Execution Time = $0.010896
</code></pre><p>If I keep the same average workload over a month, the cost is going to be:</p>
<pre tabindex="0"><code>Monthly Cost = 30 Min Total * 2 * 24 * 30 = $15.69
</code></pre><p>My application costs about 2 cents an hour, or 15 bucks a month.</p>
<h2 id="dashboards">Dashboards</h2>
<p>If you only need to look at the data once, the <em>Metrics</em> screen above should be sufficient.</p>
<p>For continuous monitoring of the metrics, you can put the same charts onto your Azure Dashboard. On the same screen, Click <em>Pin to dashboard</em> button and then navigate to the <em>Dashboard</em> menu item of the portal. You should see your chart added:</p>
<p><img src="monitoring-dashboard.png" alt="Monitoring Dashboard"></p>
<figcaption><h4>Monitoring Dashboard</h4></figcaption>
<p>You might have several Function Apps to monitor at this point. You can either add a separate chart for each one of them, or add several lines to the same chart, or a combination of both. To customize the name of the dashboard item, click on the chart, edit the name, and click <em>Update Dashboard</em> button.</p>
<p>The dashboard has a period selector at the top, which allows changing the visible time interval of all charts on the fly—very handy to zoom in and zoom out to go from overview to a more nuanced view of the cost breakdown and back.</p>
<h2 id="api">API</h2>
<p>The user interface of the Azure portal is helpful, but you might want to integrate the data into other tools used in your organization. For that, you can still use metrics from Azure Monitor but retrieve the values programmatically. One way to do so is to request information from the REST API periodically. In the following template, replace the parameters in curly braces with your actual Azure resources:</p>
<pre tabindex="0"><code>GET /subscriptions/{subscription-id}/resourceGroups/{resource-group-name}/providers/Microsoft.Web/sites/{function-app-name}/providers/microsoft.insights/metrics?api-version=2018-01-01&amp;metricnames=FunctionExecutionUnits,FunctionExecutionCount
Host: management.azure.com
Authorization: Bearer {access-token}
</code></pre><p>The access token can be obtained from the Azure Command Line Interface (CLI) with the command <code>az account get-access-token</code>.</p>
<p>By the way, you can also get the same metric values within the CLI itself:</p>
<pre tabindex="0"><code>az monitor metrics list --resource /subscriptions/{subscription-id}/resourceGroups/{resource-group-name}/providers/Microsoft.Web/sites/{function-app-name} --metric FunctionExecutionUnits,FunctionExecutionCount --aggregation Total --interval PT1M
</code></pre><p>In both cases, you receive a JSON response with time series data. Here is a snippet to illustrate the most useful bits:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e">...</span>
</span></span><span style="display:flex;"><span><span style="color:#0a3069">&#34;name&#34;</span><span style="color:#f6f8fa;background-color:#82071e">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&#34;localizedValue&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;Function Execution Units&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&#34;value&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;FunctionExecutionUnits&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span><span style="color:#f6f8fa;background-color:#82071e">,</span>
</span></span><span style="display:flex;"><span><span style="color:#0a3069">&#34;timeseries&#34;</span><span style="color:#f6f8fa;background-color:#82071e">:</span> <span style="color:#1f2328">[</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&#34;data&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">[</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;timeStamp&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;2019-07-05T10:23:00+00:00&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;total&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0550ae">127.0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;timeStamp&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;2019-07-05T10:24:00+00:00&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;total&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0550ae">34.1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f6f8fa;background-color:#82071e">...</span>
</span></span></code></pre></div><p>The reported time interval and granularity are adjustable with HTTP query and command parameters.</p>
<p>Please note that Azure Monitor has a retention period of 30 days, which is as far as you can go to observe the historical data. At the moment, I couldn&rsquo;t find a built-in capability to stream the Function App execution metrics into long-term storage. Therefore, to store a copy of the data, you need to implement the integration based on periodic calls to Metrics API and persist the response into the storage of your choice. Azure Table Storage could be one pragmatic solution for this purpose.</p>
<h2 id="application-insights-metrics">Application Insights Metrics</h2>
<p>Azure Monitor is a great tool in addition to observing the monthly bill. However, its resolution is still limited in two ways:</p>
<ul>
<li>It reports aggregated values with a resolution of one minute</li>
<li>It combines metrics coming from all the Functions belonging to the same Function App into a single value</li>
</ul>
<p>There&rsquo;s currently no way to get the cost of <em>GB-seconds</em> consumption per single execution. However, you can get the duration of each execution from a tool called <strong>Application Insights</strong>. Since the metrics are reported individually per each Function and each execution, they should help you estimate the cost structure for a given Function App.</p>
<p>The best tool to explore this data is <em>Application Insights Logs</em>. Select the Application Insights account associated with your Function App and click <em>Logs (Analytics)</em> in the toolbar and put the following query into the editor:</p>
<pre tabindex="0"><code>customMetrics
| where name contains &#34;AvgDurationMs&#34;
| limit 100
</code></pre><p>The query retrieves a hundred sample metric values with the <em>name</em> column reflecting the Azure Function name, <em>Target</em> in this case, and <em>value</em> showing the execution duration in milliseconds:</p>
<p><img src="duration-logs.png" alt="Duration in Application Insights Logs"></p>
<figcaption><h4>Duration in Application Insights Logs</h4></figcaption>
<p>The same metric can be used to plot the duration distribution in time; here is a sample query:</p>
<pre tabindex="0"><code>customMetrics
| where name contains &#34;AvgDurationMs&#34;
| summarize sum(value) by name, bin(timestamp, 1m)
| render timechart
</code></pre><p><img src="duration-chart.png" alt="Duration over Time in Application Insights Logs"></p>
<figcaption><h4>Duration over Time in Application Insights Logs</h4></figcaption>
<p>You can see that the Function plotted in blue has spent much more execution time than the green one.</p>
<h2 id="cost-beyond-functions">Cost Beyond Functions</h2>
<p>It&rsquo;s important to note that we only discussed the direct cost of Azure Functions executions. There are several other potential costs associated with running an Azure Function App:</p>
<ul>
<li>Application Insights. Depending on the event volume and sampling settings, the cost of this monitoring service can become quite substantial and exceed the cost of Azure Functions themselves. Be careful and test your configuration before and soon after going to production.</li>
<li>Network traffic. If your Functions serve the traffic to the outside world, the networking fees apply. They are usually relatively low, but the cost may build up for high-volume Functions serving bulk data.</li>
<li>Storage. Azure Functions use a Storage Account for internal state and coordination. In my experience, these costs are negligible.</li>
</ul>
<p>I hope this article gives you enough perspective to start looking into the real cost of your serverless applications. May your Functions stay performant and the bill low!</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                             
                                <category scheme="https://mikhail.io/tags/cost" term="cost" label="Cost" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[7 Ways to Deal with Application Secrets in Azure]]></title>
            <link href="https://mikhail.io/2019/07/7-ways-to-deal-with-application-secrets-in-azure/"/>
            <id>https://mikhail.io/2019/07/7-ways-to-deal-with-application-secrets-in-azure/</id>
            
            <published>2019-07-26T00:00:00+00:00</published>
            <updated>2019-07-26T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>From config files to Key Vault and role-based access, learn how infrastructure as code helps manage application secrets in Azure.</blockquote><p>Every non-trivial application relies on configuration values that may depend on the current execution environment. Some of these values contain sensitive information that shouldn&rsquo;t be shared publicly. In general, the fewer parties that have access to those secret values, the safer the application will be—in fact, in an ideal world, no one would be granted direct access to those secrets.</p>
<p>Examples of secret configuration values include:</p>
<ul>
<li>A connection string to a message bus or a database</li>
<li>A SAS Token to an Azure Storage account</li>
<li>An access key for a third-party service</li>
</ul>
<p>There&rsquo;s no one universal way to manage secrets, as a lot depends on the context in which they are used. In this article, I go through seven ways to use secret values in a .NET Core application running in Azure. I start with naively hard-coded strings and build up from there to more secure options.</p>
<p>While the concepts are universally applicable, my code samples focus on a .NET application running in <strong>Azure App Service</strong> and configured with Pulumi.</p>
<h2 id="1-hard-coded-secrets">1. Hard-coded Secrets</h2>
<p>Whenever you want to try a new API requiring a secret access token, it&rsquo;s natural to copy-paste that secret into your code and run it—simply to make sure that the setup works.</p>
<p>For example, you are testing integration with a payment service:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">var</span> apiKey <span style="color:#1f2328">=</span> <span style="color:#0a3069">&#34;payment-service-key&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">var</span> paymentService <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> PaymentService<span style="color:#1f2328">(</span>apiKey<span style="color:#1f2328">);</span>
</span></span></code></pre></div><p>or you want to start sending messages to an <strong>Azure Storage Queue</strong>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">var</span> connectionString <span style="color:#1f2328">=</span> <span style="color:#0a3069">&#34;DefaultEndpointsProtocol=https;AccountName=account-name;AccountKey=account-key&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">var</span> storageAccount <span style="color:#1f2328">=</span> CloudStorageAccount<span style="color:#1f2328">.</span>Parse<span style="color:#1f2328">(</span>connectionString<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">var</span> queueClient <span style="color:#1f2328">=</span> storageAccount<span style="color:#1f2328">.</span>CreateCloudQueueClient<span style="color:#1f2328">();</span>
</span></span></code></pre></div><p>Both snippets are fine for a Hello-World application with a lifespan of two hours. However, I would strongly discourage doing so in any code that can potentially be checked into a source control system, even for a 10-minute experiment. Don&rsquo;t copy-paste secrets into files which are part of a git repository. One accidental <code>git commit &amp; git push</code>—and the secrets are compromised.</p>
<p>Avoid the &ldquo;Secrets as Code&rdquo; practice: there are bots scanning your GitHub repository for those.</p>
<h2 id="2-configuration-files">2. Configuration Files</h2>
<p>Traditionally, putting secrets in a configuration file is considered more secure. For classic .NET applications, this would be an <code>app.config</code> or a <code>web.config</code> file. The idea is that the values on a developer machine are different from the values in the production environment. Non-sensitive development settings are kept on disk and maybe in source control, while the real secrets are only injected by a deployment script or a CI/CD system, so they are not exposed publicly.</p>
<p>In the .NET Core world, a configuration is usually stored in an <code>appsettings.json</code> file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#0550ae">&#34;MyConfig&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#0550ae">&#34;PaymentApiKey&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;payment-service-key&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#0550ae">&#34;StorageConnectionString&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;DefaultEndpointsProtocol=https;AccountName=account-name;AccountKey=account-key&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>which is then mapped to a plain C# object in code:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">public</span> <span style="color:#cf222e">class</span> <span style="color:#1f2328">MyConfig</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">public</span> <span style="color:#cf222e">string</span> PaymentApiKey <span style="color:#1f2328">{</span> <span style="color:#cf222e">get</span><span style="color:#1f2328">;</span> <span style="color:#cf222e">set</span><span style="color:#1f2328">;</span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">public</span> <span style="color:#cf222e">string</span> StorageConnectionString <span style="color:#1f2328">{</span> <span style="color:#cf222e">get</span><span style="color:#1f2328">;</span> <span style="color:#cf222e">set</span><span style="color:#1f2328">;</span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>With some configuration not shown here, the properties are filled at startup time and can be used in the code as a plain object:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">var</span> apiKey <span style="color:#1f2328">=</span> config<span style="color:#1f2328">.</span>PaymentApiKey<span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">var</span> paymentService <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> PaymentService<span style="color:#1f2328">(</span>apiKey<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// ...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">var</span> connectionString <span style="color:#1f2328">=</span> config<span style="color:#1f2328">.</span>StorageConnectionString<span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">var</span> storageAccount <span style="color:#1f2328">=</span> CloudStorageAccount<span style="color:#1f2328">.</span>Parse<span style="color:#1f2328">(</span>connectionString<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">var</span> queueClient <span style="color:#1f2328">=</span> storageAccount<span style="color:#1f2328">.</span>CreateCloudQueueClient<span style="color:#1f2328">();</span>
</span></span></code></pre></div><p>In my experience, such setup still poses substantial risks. <code>appsettings.production.json</code> may still be accidentally checked into the source control. Developers tend to use real cloud resources for their local and test environments, which do contain sensitive information that can be exploited when leaked.</p>
<p>On top of that, it doesn&rsquo;t feel right to mix configuration—how many threads to run, or how big the message batches should be—with secret connection strings and API keys in the same file. These are separate kinds of configuration, and they warrant different workflows.</p>
<h2 id="3-environment-variables-and-application-settings">3. Environment Variables and Application Settings</h2>
<p>One alternative approach is to read the secret values from environment variables. .NET Core configuration system can parse environment variables instead of, or in addition to the settings files. Likewise, one could read such values with a one-liner in C#:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">var</span> apiKey <span style="color:#1f2328">=</span> Environment<span style="color:#1f2328">.</span>GetEnvironmentVariable<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;PAYMENT_API_KEY&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// ...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">var</span> connectionString <span style="color:#1f2328">=</span> Environment<span style="color:#1f2328">.</span>GetEnvironmentVariable<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;STORAGE_CONNECTION_STRING&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// ...</span>
</span></span></code></pre></div><p>Your CI/CD system should inject those values as part of the deployment pipeline.</p>
<p>The App Service gives you the ability to set environment variables via <strong>Application Settings</strong>. Here is a snippet of a Pulumi program that passes both secret values to <code>appSettings</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">cfg</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Config</span><span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">paymentApiKey</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">cfg</span><span style="color:#1f2328">.</span><span style="color:#1f2328">requireSecret</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;paymentApiKey&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">storageAccount</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Account</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;storage&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountReplicationType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;LRS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountTier</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">app</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">AppService</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;app&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">appServicePlanId</span>: <span style="color:#cf222e">appServicePlan.id</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">appSettings</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#0a3069">&#34;PAYMENT_API_KEY&#34;</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">paymentApiKey</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#0a3069">&#34;STORAGE_CONNECTION_STRING&#34;</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">storageAccount</span><span style="color:#1f2328">.</span><span style="color:#1f2328">primaryConnectionString</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>The Storage connection string is produced by the Pulumi program directly, so it doesn&rsquo;t have to be placed anywhere outside the program itself.</p>
<p>The payment service key is provided by a third party, so its encrypted value is stored in Pulumi configuration. Read <a href="https://www.pulumi.com/blog/managing-secrets-with-pulumi/">Managing Secrets with Pulumi</a> to learn about security options available for secrets in Pulumi config.</p>
<h2 id="4-azure-key-vault">4. Azure Key Vault</h2>
<p>In the previous example, both secrets end up in Application Settings. Every person with sufficient permissions may go to the App Service and see them in clear text. While this can be restricted, it&rsquo;s a good idea to grant full read access to the application developers and operators of the App Service to give them the full picture when troubleshooting issues.</p>
<p>In some cases, compliance to a certain standard may <em>require</em> the use of a certified key management service offering enhanced security for sensitive secrets.</p>
<p>Azure has a dedicated service for storing secrets, <strong>Azure Key Vault</strong>. You can create and populate a Key Vault with all the secrets from the same Pulumi program:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">vault</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">keyvault</span><span style="color:#1f2328">.</span><span style="color:#1f2328">KeyVault</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;vault&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">sku</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;standard&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">tenantId</span>: <span style="color:#cf222e">tenantId</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accessPolicies</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">tenantId</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#57606a">// The current principal has to be granted permissions to Key Vault so that it can actually add and then remove
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>       <span style="color:#57606a">// secrets to/from the Key Vault. Otherwise, &#39;pulumi up&#39; and &#39;pulumi destroy&#39; operations will fail.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>       <span style="color:#1f2328">objectId</span>: <span style="color:#cf222e">currentPrincipal</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">secretPermissions</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span><span style="color:#0a3069">&#34;delete&#34;</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;get&#34;</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;list&#34;</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;set&#34;</span><span style="color:#1f2328">],</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">secret</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">keyvault</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Secret</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;paymentApiKey&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">keyVaultId</span>: <span style="color:#cf222e">vault.id</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">value</span>: <span style="color:#cf222e">paymentApiKey</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Additionally, one or many <strong>Service Principals</strong> (SP) should be configured to access the Key Vault. One SP which has been granted secret management access deploys the infrastructure by running the Pulumi program. That&rsquo;s why the snippet above assigns an access policy to <code>currentPrincipal</code>.</p>
<p>Another SP is used by the application itself to read the secret values. The Client ID and Client Secret parameters are placed in the application configuration. Finally, .NET Core configuration gets hooked to the Key Vault at startup:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span>builder<span style="color:#1f2328">.</span>AddAzureKeyVault<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>   <span style="color:#0a3069">$&#34;https://{config[&#34;</span>azureKeyVault<span style="color:#1f2328">:</span>vault<span style="color:#0a3069">&#34;]}.vault.azure.net/&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   config<span style="color:#1f2328">[</span><span style="color:#0a3069">&#34;azureKeyVault:clientId&#34;</span><span style="color:#1f2328">],</span>
</span></span><span style="display:flex;"><span>   config<span style="color:#1f2328">[</span><span style="color:#0a3069">&#34;azureKeyVault:clientSecret&#34;</span><span style="color:#1f2328">]</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">);</span>
</span></span></code></pre></div><p>This solution is not entirely satisfying though, since we&rsquo;ve traded storing the secrets for storing SP credentials. Is that a big enough win?  Luckily, it&rsquo;s easy to get rid of those credentials with Managed identities.</p>
<h2 id="5-accessing-key-vault-with-managed-identities">5. Accessing Key Vault with Managed Identities</h2>
<p>With <strong>Managed identities</strong>, Azure takes care of creating a Service Principal, passing the credentials, rotating secrets, and so on. Enabling a managed identity on App Service is just an extra option:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">app</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">AppService</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;app&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">appServicePlanId</span>: <span style="color:#cf222e">appServicePlan.id</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#57606a">// A system-assigned managed identity
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>   <span style="color:#1f2328">identity</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">type</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;SystemAssigned&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>On top of that, the managed principal must be granted access to the Key Vault:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">principalId</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">app</span><span style="color:#1f2328">.</span><span style="color:#1f2328">identity</span><span style="color:#1f2328">.</span><span style="color:#1f2328">apply</span><span style="color:#1f2328">(</span><span style="color:#1f2328">id</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">id</span><span style="color:#1f2328">.</span><span style="color:#1f2328">principalId</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Grant App Service access to KV secrets
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">keyvault</span><span style="color:#1f2328">.</span><span style="color:#1f2328">AccessPolicy</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;app-policy&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">keyVaultId</span>: <span style="color:#cf222e">vault.id</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">tenantId</span>: <span style="color:#cf222e">tenantId</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">objectId</span>: <span style="color:#cf222e">principalId</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">secretPermissions</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span><span style="color:#0a3069">&#34;get&#34;</span><span style="color:#1f2328">],</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Now, the configuration block in the .NET Core app doesn&rsquo;t need to retrieve any secrets. <code>AzureServiceTokenProvider</code> helps with the authentication process:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">var</span> azureServiceTokenProvider <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> AzureServiceTokenProvider<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">var</span> keyVaultClient <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> KeyVaultClient<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">new</span> KeyVaultClient<span style="color:#1f2328">.</span>AuthenticationCallback<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>       azureServiceTokenProvider<span style="color:#1f2328">.</span>KeyVaultTokenCallback<span style="color:#1f2328">));</span>
</span></span><span style="display:flex;"><span>builder<span style="color:#1f2328">.</span>AddAzureKeyVault<span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>   keyVaultEndpoint<span style="color:#1f2328">,</span> keyVaultClient<span style="color:#1f2328">,</span> <span style="color:#cf222e">new</span> DefaultKeyVaultSecretManager<span style="color:#1f2328">());</span>
</span></span></code></pre></div><p>That&rsquo;s quite a bit of a boilerplate, but there is a way to get rid of it.</p>
<h2 id="6-accessing-key-vault-from-application-settings">6. Accessing Key Vault from Application Settings</h2>
<p>App Service has a neat feature of integrating its Application Settings with Key Vault. It allows us to combine #3 and #5&rsquo;s approaches and get the best of both:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#57606a">// Produce a URI of the KV secret defined above
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">const</span> <span style="color:#1f2328">secretUri</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">interpolate</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">secret</span><span style="color:#1f2328">.</span><span style="color:#1f2328">vaultUri</span><span style="color:#0a3069">}</span><span style="color:#0a3069">secrets/</span><span style="color:#0a3069">${</span><span style="color:#1f2328">secret</span><span style="color:#1f2328">.</span><span style="color:#1f2328">name</span><span style="color:#0a3069">}</span><span style="color:#0a3069">/</span><span style="color:#0a3069">${</span><span style="color:#1f2328">secret</span><span style="color:#1f2328">.</span><span style="color:#1f2328">version</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">app</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">AppService</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;app&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">appServicePlanId</span>: <span style="color:#cf222e">appServicePlan.id</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#57606a">// A system-assigned managed identity
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>   <span style="color:#1f2328">identity</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">type</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;SystemAssigned&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">appSettings</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#57606a">// The setting points directly to the KV setting
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>       <span style="color:#0a3069">&#34;PAYMENT_API_KEY&#34;</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">interpolate</span><span style="color:#0a3069">`@Microsoft.KeyVault(SecretUri=</span><span style="color:#0a3069">${</span><span style="color:#1f2328">secretUri</span><span style="color:#0a3069">}</span><span style="color:#0a3069">)`</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>With that, the API key is loaded into the App Service environment variable without its value being publicly exposed anywhere!</p>
<h2 id="7-role-based-access-control">7. Role-based Access Control</h2>
<p>What is the most secure way to deal with secrets? <em>Have no secrets</em>. The Storage Account connection string is a great example when it&rsquo;s possible to avoid storing and reading the sensitive value altogether.</p>
<p><strong>Role-based access control</strong> (RBAC) of <strong>Azure Active Directory</strong> (AAD) is a great tool to manage permissions in a declarative way. Let&rsquo;s assume our application only needs to send messages to one Storage Queue. Instead of storing a full connection string with an access token, the connection string should point to the account, and the identity behind the App Service should be granted write permissions to the required Storage Queue:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">permission</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">role</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Assignment</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;send&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">principalId</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">scope</span>: <span style="color:#cf222e">pulumi.interpolate</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">storageAccount</span><span style="color:#1f2328">.</span><span style="color:#1f2328">id</span><span style="color:#0a3069">}</span><span style="color:#0a3069">/queueServices/default/queues/</span><span style="color:#0a3069">${</span><span style="color:#1f2328">queue</span><span style="color:#1f2328">.</span><span style="color:#1f2328">name</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">roleDefinitionName</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Storage Queue Data Message Sender&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>We can put the queue URL into Application Settings because there&rsquo;s nothing secret in it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">queueUri</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">interpolate</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">storageAccount</span><span style="color:#1f2328">.</span><span style="color:#1f2328">primaryQueueEndpoint</span><span style="color:#0a3069">}${</span><span style="color:#1f2328">queue</span><span style="color:#1f2328">.</span><span style="color:#1f2328">name</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">app</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">AppService</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;app&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">appServicePlanId</span>: <span style="color:#cf222e">appServicePlan.id</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">identity</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">type</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;SystemAssigned&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">appSettings</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#0a3069">&#34;STORAGE_QUEUE_URL&#34;</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">queueUri</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span></code></pre></div><p>It takes a bit of C# boilerplate to send a message with role-based authorization:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#cf222e">var</span> provider <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> AzureServiceTokenProvider<span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">string</span> accessToken <span style="color:#1f2328">=</span> <span style="color:#cf222e">await</span> provider<span style="color:#1f2328">.</span>GetAccessTokenAsync<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;https://storage.azure.com/&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">var</span> tokenCredential <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> TokenCredential<span style="color:#1f2328">(</span>accessToken<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">var</span> storageCredentials <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> StorageCredentials<span style="color:#1f2328">(</span>tokenCredential<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">var</span> url <span style="color:#1f2328">=</span> Environment<span style="color:#1f2328">.</span>GetEnvironmentVariable<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;StorageBlobUrl&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">var</span> queue <span style="color:#1f2328">=</span> <span style="color:#cf222e">new</span> CloudQueue<span style="color:#1f2328">(</span><span style="color:#cf222e">new</span> Uri<span style="color:#1f2328">(</span>url<span style="color:#1f2328">),</span> storageCredentials<span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>queue<span style="color:#1f2328">.</span>AddMessage<span style="color:#1f2328">(</span><span style="color:#cf222e">new</span> CloudQueueMessage<span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;Hello&#34;</span><span style="color:#1f2328">));</span>
</span></span></code></pre></div><p>Let&rsquo;s hope another quality-of-life improvement is on the way.</p>
<h2 id="role-of-infrastructure-as-code">Role of Infrastructure as Code</h2>
<p>While security practices may vary depending on application requirements, Pulumi plays an essential role in the appropriate setup of service configuration and environment:</p>
<ul>
<li>It links an output of one resource to another one&rsquo;s input, avoiding the need to store the values.</li>
<li>It provides a built-in mechanism to manage external secrets.</li>
<li>It is a great way to take advantage of Azure features like Managed identities and RBAC in a cohesive way.</li>
</ul>
<p>Infrastructure as Code helps make your applications secure and reliable. Refer to <a href="https://github.com/pulumi/examples/tree/master/azure-ts-msi-keyvault-rbac">this full example</a> of using Key Vault, Managed identities, RBAC with App Service and Pulumi.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/security" term="security" label="Security" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Load-Testing Azure Functions with Loader.io]]></title>
            <link href="https://mikhail.io/2019/07/load-testing-azure-functions-with-loaderio/"/>
            <id>https://mikhail.io/2019/07/load-testing-azure-functions-with-loaderio/</id>
            
            <published>2019-07-04T00:00:00+00:00</published>
            <updated>2019-07-04T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Verifying your Function App as a valid target for the cloud load testing.</blockquote><p>When Azure Functions team presented the new <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-premium-plan">Premium plan</a>, they made a series of demos which compared the response time of a Function App running on the Consumption plan vs. an App running on the Premium plan. Both apps would receive a rapid growth of incoming requests, and then the percentiles of response latencies were compared.</p>
<p>The demos used a tool called <a href="https://loader.io/">Loader</a>, also referred to as &ldquo;Loader.io&rdquo;. The tool is visual and easy-to-use, but there is one step which might be just a little bit tricky to go through: verification. To avoid DDoS-ing somebody else&rsquo;s site, Loader requires users to verify each Target host by placing a key on a given URL of that domain.</p>
<p>If you want to follow along, it&rsquo;s time to create an account at <a href="https://loader.io/">loader.io</a>—they have a free plan. After signing in, go to <a href="https://loader.io/targets/new">New target host</a> and enter the URL of your Function App:</p>
<p><img src="new-target-host.png" alt="Azure Function App as a new target host"></p>
<figcaption><h4>Azure Function App as a new target host</h4></figcaption>
<p>Click &ldquo;Next: Verify&rdquo; button, and you are given a token to put inside your Function App to prove the ownership of it:</p>
<p><img src="target-verification.png" alt="Target Verification of Azure Function App"></p>
<figcaption><h4>Target Verification of Azure Function App</h4></figcaption>
<p>I know two ways to achieve verification.</p>
<h2 id="option-1-function-proxies">Option 1: Function Proxies</h2>
<p>With <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-proxies">Azure Functions Proxies</a>, you can specify endpoints in your Function App which aren&rsquo;t handled by Functions directly but redirect the requests to other endpoints or respond with a hard-coded response.</p>
<p>The later is exactly what we need. Create a <code>proxies.json</code> file in the root of your Function App, next to the <code>host.json</code> file, and put the following content in there:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&#34;$schema&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;http://json.schemastore.org/proxies&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">&#34;proxies&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0550ae">&#34;loaderio-verifier&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#0550ae">&#34;matchCondition&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                <span style="color:#0550ae">&#34;methods&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">[</span> <span style="color:#0a3069">&#34;GET&#34;</span> <span style="color:#1f2328">],</span>
</span></span><span style="display:flex;"><span>                <span style="color:#0550ae">&#34;route&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;/loaderio-your-token-goes-here&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>            <span style="color:#0550ae">&#34;responseOverrides&#34;</span><span style="color:#1f2328">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                <span style="color:#0550ae">&#34;response.body&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;loaderio-your-token-goes-here&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>                <span style="color:#0550ae">&#34;response.headers.Content-Type&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;text/plain&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>Don&rsquo;t forget to replace <code>loaderio-your-token-goes-here</code> value with the token that you received at the previous step. Now, the hard-coded response is returned at the endpoint which Loader uses for verification.</p>
<p>If your Function App is a .NET project, include <code>proxies.json</code> to the <code>csproj</code>/<code>fsproj</code> file, so that it becomes a part of the deployment artifact:</p>
<pre tabindex="0"><code>...
      &lt;None Update=&#34;proxies.json&#34;&gt;
         &lt;CopyToOutputDirectory&gt;PreserveNewest&lt;/CopyToOutputDirectory&gt;
      &lt;/None&gt;
   &lt;/ItemGroup&gt;
&lt;/Project&gt;
</code></pre><p>After re-publishing the Function App, you should be able to see a new proxy in the portal:</p>
<p><img src="loader-verifier-proxy.png" alt="Loader Verifier Proxy in the Azure Portal"></p>
<figcaption><h4>Loader Verifier Proxy in the Azure Portal</h4></figcaption>
<p>You are all set to go! Click the &ldquo;Verify&rdquo; button to see</p>
<p><img src="target-verification-congrats.png" alt="Target Verification Completed of Azure Function App"></p>
<h2 id="option-2-another-function">Option 2: Another Function</h2>
<p>Instead of dealing with Proxies, you could create another Azure Function in the same App and let it return the token.</p>
<p>I&rsquo;ll give you an example of defining such a Function with TypeScript and Pulumi.</p>
<p>Say, I want to load-test the following target HTTP function (it just pauses for 500 ms):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">target</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">HttpFunction</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;Target&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">req</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">await</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">Promise</span><span style="color:#1f2328">(</span><span style="color:#1f2328">resolve</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">setTimeout</span><span style="color:#1f2328">(</span><span style="color:#1f2328">resolve</span><span style="color:#1f2328">,</span> <span style="color:#0550ae">500</span><span style="color:#1f2328">));</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">status</span>: <span style="color:#cf222e">200</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">body</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Hello World!&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Now, I can add another HTTP function to handle the verification requests:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">verifier</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">HttpFunction</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;Verifier&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">route</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;loaderio-your-token-goes-here&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">req</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">status</span>: <span style="color:#cf222e">200</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">body</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;loaderio-your-token-goes-here&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Finally, I put both Functions into a Function App and deploy them together:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">app</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">MultiCallbackFunctionApp</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;loaderio&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">functions</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span><span style="color:#1f2328">target</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">verifier</span><span style="color:#1f2328">],</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">hostSettings</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">extensions</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">http</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">routePrefix</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;&#34;</span> <span style="color:#1f2328">}</span> <span style="color:#1f2328">}</span> <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">endpoint</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">app</span><span style="color:#1f2328">.</span><span style="color:#1f2328">endpoint</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><p>Note that I set <code>routePrefix</code> to an empty string to avoid <code>api</code> prefix in my URLs (I usually do it anyway). Now, as soon as Pulumi deploys my App, I can start testing it with Loader.</p>
<p>You can find a full example involving the <code>MultiCallbackFunctionApp</code> component <a href="https://github.com/pulumi/pulumi-azure/blob/master/examples/http-multi/index.ts">here</a>.</p>
<h2 id="running-the-load-tests">Running the Load Tests</h2>
<p>Regardless of which path you chose, you should now be good to start using Loader to run performance tests on your Azure Functions.</p>
<p>Here is a chart of a sample test that I ran to make sure the setup works:</p>
<p><img src="loader-results.png" alt="Loader.io Test Results"></p>
<figcaption><h4>Loader.io Test Results</h4></figcaption>
<p>Which test parameters to use and what kind of results you might get depends a lot on your application. That&rsquo;s a good topic for another post someday.</p>
<p>For now, happy testing!</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                             
                                <category scheme="https://mikhail.io/tags/performance" term="performance" label="Performance" />
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[How Azure CLI Manages Your Access Tokens]]></title>
            <link href="https://mikhail.io/2019/07/how-azure-cli-manages-access-tokens/"/>
            <id>https://mikhail.io/2019/07/how-azure-cli-manages-access-tokens/</id>
            
            <published>2019-07-03T00:00:00+00:00</published>
            <updated>2019-07-03T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Azure CLI is a powerful tool to manage your cloud resources. Where does it store the sensitive information and why might you want to care?</blockquote><p>Azure has several tools available to create and manage cloud resources. The Azure command-line interface (CLI) is probably the second most-used tool after the web portal. The CLI runs on any platform and covers a wide variety of actions. Here is a command to create a new Virtual Machine:</p>
<pre tabindex="0"><code>az vm create --resource-group Sample --name VM1 --image UbuntuLTS --generate-ssh-keys
</code></pre><p>If you use Azure for your day-to-day job or hobby projects, it&rsquo;s quite likely that you already have the Azure CLI installed on your computer.</p>
<h2 id="azure-login">Azure login</h2>
<p>The very first command that you run after installing the CLI on your development machine is going to be</p>
<pre tabindex="0"><code>az login
</code></pre><p>There are several sign-in flows, but most typically the CLI opens the default browser asking you to log in there. The login operation has a unique session identifier. Once you sign in with this session ID, the CLI receives a notification on its back channel. The notification contains a JWT access token.</p>
<p>From this point on, the access token is used by most other CLI commands to access Azure Management REST API. API uses OAuth protocol where the access token is passed in the <code>Authorization</code> HTTP header.</p>
<h2 id="storing-tokens-on-the-local-disk">Storing tokens on the local disk</h2>
<p>However, there is one problem. The CLI is a short-lived program: it runs for the duration of a single command execution and then quits. The process dies, so there&rsquo;s no way to keep access tokens in memory between the executions. To avoid continually asking for user credentials, the CLI keeps its state on disk. If you go to the <code>~/.azure/</code> directory (<code>%HOMEPATH%/.azure/</code> in Windows command-line) you can find several of such state files:</p>
<pre tabindex="0"><code>~/.azure&gt; ls

accessTokens.json
az.json
az.sess
azureProfile.json
clouds.configtelemetry.txt
</code></pre><p>Two of these files contain some relevant information. <code>azureProfile.json</code> lists the properties of your Azure subscriptions and users (no passwords or tokens).</p>
<p><code>accessTokens.json</code> is more interesting. As the name suggests, it contains all the tokens from the Azure CLI, right there in plain text. Here is the shape of JSON entries in there:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#0550ae">&#34;tokenType&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;Bearer&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#0550ae">&#34;expiresIn&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0550ae">3599</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#0550ae">&#34;expiresOn&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;2019-05-05 00:22:01.577315&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#0550ae">&#34;resource&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;https://management.core.windows.net/&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#0550ae">&#34;accessToken&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;...&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#0550ae">&#34;refreshToken&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;...&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#0550ae">&#34;identityProvider&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;live.com&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#0550ae">&#34;userId&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;...&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#0550ae">&#34;isMRRT&#34;</span><span style="color:#1f2328">:</span> <span style="color:#cf222e">true</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#0550ae">&#34;_clientId&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;...&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#0550ae">&#34;_authority&#34;</span><span style="color:#1f2328">:</span> <span style="color:#0a3069">&#34;https://login.microsoftonline.com/common&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>There are multiple entries like this for different combinations of <code>resource</code>, <code>_authority</code>, and others. Obviously, I removed all the sensitive values from this snippet, but you could see yours in plain text. Go ahead and copy-paste a value of <code>accessToken</code> property to &ldquo;Encoded&rdquo; the text box on <a href="https://jwt.io">https://jwt.io</a> page. The site decodes the token and shows you the properties:</p>
<p><img src="azure-jwt-decoded.png" alt="Azure access token decoded with JWT.io"></p>
<figcaption><h4>Azure access token decoded with JWT.io</h4></figcaption>
<p>The access token has a limited lifespan—mine are all 60 minutes. To avoid requiring to login after access expiration, there is another powerful token—a refresh token. Whenever an access token expires, CLI goes to the authentication service, presents the refresh token, and asks for a new access token. The lifetime of a refresh token is longer, and it&rsquo;s managed on the service side. There are some configurable policies to expire it: for instance, Azure might invalidate a token if it was inactive for more than X days. It can also be revoked manually at any time.</p>
<p>The refresh tokens are stored inside the same <code>accessTokens.json</code> file, right next to the access token (see the snippet above). It&rsquo;s not a JWT token: it is an opaque blob sent from Azure AD whose contents are not known to any client components. You cannot see what’s inside a refresh token but Azure can.</p>
<h2 id="token-reuse-by-other-tools">Token reuse by other tools</h2>
<p>We learned that access tokens are not specific to the Azure CLI and aren&rsquo;t used exclusively by it. Let&rsquo;s run a short experiment:</p>
<ol>
<li>
<p>Run <code>az login</code> or any other Azure CLI command to make sure there&rsquo;s a current access token.</p>
</li>
<li>
<p>Open <code>azureProfile.json</code> and copy-paste the subscription <code>id</code> field into the following URL: <code>https://management.azure.com/subscriptions/{subscription-id}/resourcegroups?api-version=2019-05-10</code>.</p>
</li>
<li>
<p>Open the <code>accessTokens.json</code> file, find the latest entry, and copy the <code>accessToken</code> field.</p>
</li>
</ol>
<p>Now, build the following cURL command out of these two values:</p>
<pre tabindex="0"><code>curl --header &#34;Authorization: Bearer {access-token}&#34; \
https://management.azure.com/subscriptions/{subscription-id}/resourcegroups?api-version=2019-05-10
</code></pre><p>Run the command, and you get the list of resource groups in your subscription!</p>
<p>By the way, you can also find both properties with the Azure CLI commands <code>az account list</code> and <code>az account get-access-token</code>. It doesn&rsquo;t feel as hacky as copy-pasting from JSON files, but it is more convenient :)</p>
<p>Multiple third-party tools use the fact that the Azure CLI can log in to Azure and then provide access tokens. Both Terraform and Pulumi have a default method of authenticating into Azure with the Azure CLI. They probably delegate to the CLI instead of accessing the <code>accessTokens.json</code> file directly, but that&rsquo;s mostly convenience and not the hard requirement. Effectively, they are able to reuse the tokens created by the Azure CLI for their own purpose.</p>
<h2 id="security-risks-of-token-exposure">Security risks of token exposure</h2>
<p>This is all handy and good. However, there is a dark side of convenience. The existence of a file with clear-text Azure access tokens means that you should be careful not to expose that file to anyone. Don&rsquo;t share your <code>~/.azure/</code> folder with anybody, don&rsquo;t put it on GitHub, don&rsquo;t upload it to random file-sharing applications.</p>
<p>Access tokens file could potentially become an attack vector. Say, you install a new shiny CLI tool from NPM (many developers have dozens of them). You run it from your user account, the tool does its job but also silently uploads <code>accessTokens.json</code> file to somebody&rsquo;s FTP. A month later, you get surprised while looking at your Azure bill.</p>
<p>I checked this scenario by uploading my <code>~/.azure</code> folder to a brand-new VM. A fresh installation of the Azure CLI happily welcomes me without the need for <code>az login</code>:</p>
<p><img src="cli-on-vm.png" alt="Azure CLI open on a new VM with copied credentials"></p>
<figcaption><h4>Azure CLI open on a new VM with copied credentials</h4></figcaption>
<h2 id="conclusion">Conclusion</h2>
<p>Know your tools, reuse the power of Azure CLI for those Azure Management REST API crunching sessions, and keep your machine safe.</p>
<p>Happy hacking!</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/azure-cli" term="azure-cli" label="Azure CLI" />
                             
                                <category scheme="https://mikhail.io/tags/security" term="security" label="Security" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Globally-distributed Serverless Application in 100 Lines of Code. Infrastructure Included!]]></title>
            <link href="https://mikhail.io/2019/07/globally-distributed-serverless-application-in-100-lines-of-code/"/>
            <id>https://mikhail.io/2019/07/globally-distributed-serverless-application-in-100-lines-of-code/</id>
            
            <published>2019-07-02T00:00:00+00:00</published>
            <updated>2019-07-02T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Building a serverless application on Azure with both the data store and the HTTP endpoint located close to end users for fast response time.</blockquote><p>Pulumi is excellent at connecting multiple cloud components into a cohesive application. In my <a href="https://blog.pulumi.com/serverless-as-simple-callbacks-with-pulumi-and-azure-functions">previous post</a>, I introduced the way to mix JavaScript or TypeScript serverless functions directly into the cloud infrastructure programs.</p>
<p>Today, I will build a serverless application with both the data store and the HTTP endpoint located close to end users to ensure prompt response time. The entire application runs on top of managed Azure services and is defined as a single Pulumi program in TypeScript.</p>
<h2 id="baseline">Baseline</h2>
<p>I&rsquo;m going to build a URL shortener: a simple HTTP endpoint which accepts a shortcode in the URL and then redirects a user to the full URL associated with the given short code.</p>
<p>I start simple and make a non-distributed version of the application first. It consists of two main components: a Cosmos DB container to store URL mappings and two Azure Functions to handle HTTP requests:</p>
<p><img src="pulumi-azure-url-shortener-basic.png" alt="The URL Shortener app deployed to a single location"></p>
<figcaption><h4>The URL Shortener app deployed to a single location</h4></figcaption>
<p>Let&rsquo;s define this infrastructure in a Pulumi program.</p>
<h3 id="cosmos-db-collection">Cosmos DB Collection</h3>
<p>Arguably, Cosmos DB is overkill in such a simple scenario: we just need a key-value store, so Table Storage would suffice. However, Cosmos DB comes handy at the multi-region setup later on.</p>
<p>Next to a standard resource group definition, I create a Cosmos DB account with location set to a single region and <code>Session</code> consistency level:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">pulumi</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/pulumi&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">azure</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/azure&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">cosmos</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@azure/cosmos&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">location</span> <span style="color:#0550ae">=</span> <span style="color:#0a3069">&#34;westus&#34;</span><span style="color:#1f2328">;</span> <span style="color:#57606a">// To be changed to multiple configurable locations
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#1f2328">resourceGroup</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">core</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ResourceGroup</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;UrlShorterner&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#1f2328">cosmosdb</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">cosmosdb</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Account</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;UrlStore&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">geoLocations</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span> <span style="color:#1f2328">location</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">failoverPriority</span>: <span style="color:#cf222e">0</span> <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">offerType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">consistencyPolicy</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">consistencyLevel</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Session&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">maxIntervalInSeconds</span>: <span style="color:#cf222e">5</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">maxStalenessPrefix</span>: <span style="color:#cf222e">100</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Cosmos DB has a hierarchical structure of Accounts, Databases, and Containers. Unfortunately, Cosmos DB containers can’t be defined as Pulumi resources yet. As a workaround, I defined a helper function <code>getContainer</code> to create a database and a container using Cosmos SDK, see <a href="https://github.com/pulumi/examples/blob/master/azure-ts-serverless-url-shortener-global/cosmosclient.ts">here</a>.</p>
<h3 id="function-app">Function App</h3>
<p>Serverless Azure Functions are going to handle the HTTP layer in my application. I use the technique of <a href="https://blog.pulumi.com/serverless-as-simple-callbacks-with-pulumi-and-azure-functions">serverless functions as callbacks</a> to define Azure Functions inline inside my Pulumi program:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">fn</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">HttpEventSubscription</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;GetUrl&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroup</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">route</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;{key}&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">_</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">request</span>: <span style="color:#cf222e">azure.appservice.HttpRequest</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">const</span> <span style="color:#1f2328">endpoint</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">cosmosdb</span><span style="color:#1f2328">.</span><span style="color:#1f2328">endpoint</span><span style="color:#1f2328">.</span><span style="color:#cf222e">get</span><span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">const</span> <span style="color:#1f2328">masterKey</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">cosmosdb</span><span style="color:#1f2328">.</span><span style="color:#1f2328">primaryMasterKey</span><span style="color:#1f2328">.</span><span style="color:#cf222e">get</span><span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">const</span> <span style="color:#1f2328">container</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">await</span> <span style="color:#1f2328">getContainer</span><span style="color:#1f2328">(</span><span style="color:#1f2328">endpoint</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">masterKey</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">const</span> <span style="color:#1f2328">key</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">request</span><span style="color:#1f2328">.</span><span style="color:#1f2328">params</span><span style="color:#1f2328">[</span><span style="color:#0a3069">&#39;key&#39;</span><span style="color:#1f2328">];</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">try</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>           <span style="color:#cf222e">const</span> <span style="color:#1f2328">response</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">await</span> <span style="color:#1f2328">container</span><span style="color:#1f2328">.</span><span style="color:#1f2328">item</span><span style="color:#1f2328">(</span><span style="color:#1f2328">key</span><span style="color:#1f2328">.</span><span style="color:#1f2328">toString</span><span style="color:#1f2328">()).</span><span style="color:#1f2328">read</span><span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>           <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>               <span style="color:#1f2328">status</span>: <span style="color:#cf222e">301</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>               <span style="color:#1f2328">headers</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#0a3069">&#34;location&#34;</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">response</span><span style="color:#1f2328">.</span><span style="color:#1f2328">body</span><span style="color:#1f2328">.</span><span style="color:#1f2328">url</span> <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>               <span style="color:#1f2328">body</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#39;&#39;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">}</span> <span style="color:#cf222e">catch</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">e</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>           <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">status</span>: <span style="color:#cf222e">404</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">body</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#39;Short URL not found&#39;</span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">url</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">fn</span><span style="color:#1f2328">.</span><span style="color:#1f2328">url</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><p>I supply the template <code>{key}</code> as <code>route</code> parameter so that the function accepts wildcard URLs and extract the wildcard value as a <code>key</code> parameter available inside the <code>request</code> object. Then, I use the received key to look up the full URL. The URL is returned to the client as a header of the <code>301</code> Redirect response. <code>404</code> Not Found is returned in case the requested document does not exist—the naughty Cosmos SDK throws an error in this scenario.</p>
<p>Note how the Cosmos DB parameters <code>endpoint</code> and <code>primaryMasterKey</code> are used directly inside the function. There is no need to manage the keys manually or explicitly put them into application settings: the generated keys are injected into the code at the time of deployment. In practice, a better idea is to store the key in a KeyVault and read it via application settings, but I&rsquo;ll leave this for a separate article.</p>
<p>A function to add a URL to the database looks quite similar to the above:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">fn</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">HttpEventSubscription</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;AddUrl&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroup</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">methods</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span><span style="color:#0a3069">&#34;POST&#34;</span><span style="color:#1f2328">],</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">_</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">request</span>: <span style="color:#cf222e">azure.appservice.HttpRequest</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">const</span> <span style="color:#1f2328">endpoint</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">cosmosdb</span><span style="color:#1f2328">.</span><span style="color:#1f2328">endpoint</span><span style="color:#1f2328">.</span><span style="color:#cf222e">get</span><span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">const</span> <span style="color:#1f2328">masterKey</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">cosmosdb</span><span style="color:#1f2328">.</span><span style="color:#1f2328">primaryMasterKey</span><span style="color:#1f2328">.</span><span style="color:#cf222e">get</span><span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">const</span> <span style="color:#1f2328">container</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">await</span> <span style="color:#1f2328">getContainer</span><span style="color:#1f2328">(</span><span style="color:#1f2328">endpoint</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">masterKey</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">location</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">await</span> <span style="color:#1f2328">container</span><span style="color:#1f2328">.</span><span style="color:#1f2328">items</span><span style="color:#1f2328">.</span><span style="color:#1f2328">create</span><span style="color:#1f2328">(</span><span style="color:#1f2328">request</span><span style="color:#1f2328">.</span><span style="color:#1f2328">body</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">status</span>: <span style="color:#cf222e">200</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">body</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#39;Short URL saved&#39;</span> <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">addEndpoint</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">fn</span><span style="color:#1f2328">.</span><span style="color:#1f2328">url</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><p>The key differences are:</p>
<ul>
<li>Using <code>POST</code> as the accepted HTTP method</li>
<li>Creating a new item in Cosmos DB</li>
<li>Using <code>request.body</code> as the payload to save</li>
</ul>
<p>I might need to add some validation in a real application—skipped for now.</p>
<h3 id="trying-it-out">Trying It Out</h3>
<p>Now, when a user navigates to an address like <code>https://geturl-xyz.azurewebsites.net/api/URLKEY</code>, they get redirected to the full URL associated with the key. The URL is not quite short yet, but we could make it so with a custom domain configuration.</p>
<p>Presumably, we expect our URL shortener to be popular around the world. Therefore, one key aspect is to make sure that the service responds fast and enables a smooth user experience. Let&rsquo;s measure the response time of our first version from different spots around the world:</p>
<table class="table table-striped">
  <thead>
      <tr>
          <th>Location</th>
          <th>Response time</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>San Francisco</td>
          <td>133 ms</td>
      </tr>
      <tr>
          <td>New York</td>
          <td>272 ms</td>
      </tr>
      <tr>
          <td>London</td>
          <td>383 ms</td>
      </tr>
      <tr>
          <td>Frankfurt</td>
          <td>420 ms</td>
      </tr>
      <tr>
          <td>Tel-Aviv</td>
          <td>470 ms</td>
      </tr>
      <tr>
          <td>Hong-Kong</td>
          <td>437 ms</td>
      </tr>
      <tr>
          <td>Brisbane</td>
          <td>449 ms</td>
      </tr>
  </tbody>
</table>
<p>The further we get from the West US region, the slower the responses become.</p>
<p>How can we do better?</p>
<h2 id="bring-compute-and-data-closer-to-users">Bring Compute and Data Closer to Users</h2>
<p>It&rsquo;s hard to beat the speed of light, so if we want to respond fast, we need to bring both code and data close to any target user.</p>
<p>Luckily, the most involved aspect of that—the data distribution—can be handled by Cosmos DB multi-region accounts. We need to take care of deploying the code in multiple locations and routing the traffic to the nearest one. Here is the plan:</p>
<p><img src="pulumi-azure-url-shortener-distributed.png" alt="The multi-region URL Shortener app"></p>
<figcaption><h4>The multi-region URL Shortener app</h4></figcaption>
<p>I don&rsquo;t care too much about the latency of Add URL function, so I left it out of the picture.</p>
<h3 id="configuring-the-locations">Configuring the locations</h3>
<p>The above picture shows three Azure regions, but since I&rsquo;m using a general-purpose programming language, I can handle any number of locations in the same way. Actually, I put the list of target regions in Pulumi config file and read it at execution time:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">config</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Config</span><span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">locations</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">config</span><span style="color:#1f2328">.</span><span style="color:#cf222e">require</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;locations&#34;</span><span style="color:#1f2328">).</span><span style="color:#1f2328">split</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#39;,&#39;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">primaryLocation</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">locations</span><span style="color:#1f2328">[</span><span style="color:#0550ae">0</span><span style="color:#1f2328">];</span>
</span></span></code></pre></div><p>The order of regions defines the priority.</p>
<h3 id="multi-region-cosmos-db-account">Multi-region Cosmos DB Account</h3>
<p>Since <code>locations</code> is a simple array, I can <code>map</code> this array to produce the array of <code>geoLocations</code> to be set for my Cosmos DB:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#1f2328">cosmosdb</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">cosmosdb</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Account</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;url-cosmos&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">location</span>: <span style="color:#cf222e">primaryLocation</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">geoLocations</span>: <span style="color:#cf222e">locations.map</span><span style="color:#1f2328">((</span><span style="color:#1f2328">location</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">failoverPriority</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">({</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">failoverPriority</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">})),</span>
</span></span><span style="display:flex;"><span>   <span style="color:#57606a">/* ... other properties stay the same ... */</span>
</span></span></code></pre></div><p>Right here, the full power of a programming language at my fingertips!</p>
<h3 id="multi-region-serverless-app">Multi-region Serverless App</h3>
<blockquote>
<p>Azure Traffic Manager is a DNS-based traffic load balancer that enables you to distribute traffic optimally to services across global Azure regions while providing high availability and responsiveness.</p></blockquote>
<p>Sounds like what I need for my global application! Let&rsquo;s define a Traffic Manager profile:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">profile</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">trafficmanager</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Profile</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;UrlShortEndpoint&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">trafficRoutingMethod</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#39;Performance&#39;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">dnsConfigs</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">relativeName</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;shorturls&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">ttl</span>: <span style="color:#cf222e">60</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">monitorConfigs</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">protocol</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#39;HTTP&#39;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">port</span>: <span style="color:#cf222e">80</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">path</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#39;/api/ping&#39;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}]</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>I set the routing method to Performance:</p>
<blockquote>
<p>Select Performance when you have endpoints in different geographic locations and you want end users to use the &ldquo;closest&rdquo; endpoint in terms of the lowest network latency.</p></blockquote>
<p>Now, I need to create a Function App in each of the target regions. That&rsquo;s easy to achieve with a <code>for..of</code> loop:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">for</span> <span style="color:#1f2328">(</span><span style="color:#cf222e">const</span> <span style="color:#1f2328">location</span> <span style="color:#cf222e">of</span> <span style="color:#1f2328">locations</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">const</span> <span style="color:#1f2328">fn</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">HttpEventSubscription</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`GetUrl-</span><span style="color:#0a3069">${</span><span style="color:#1f2328">location</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">resourceGroup</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">route</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;{key}&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">callback</span><span style="color:#0550ae">:</span> <span style="color:#57606a">/* ... the function stays the same ... */</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>Finally, I set up an Endpoint for each Function App to bind them to the Traffic Manager. I do so in the same loop as above:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">for</span> <span style="color:#1f2328">(</span><span style="color:#cf222e">const</span> <span style="color:#1f2328">location</span> <span style="color:#cf222e">of</span> <span style="color:#1f2328">locations</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">const</span> <span style="color:#1f2328">fn</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">HttpEventSubscription</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`GetUrl-</span><span style="color:#0a3069">${</span><span style="color:#1f2328">location</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#57606a">/* ... function definition as shown above ... */</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">const</span> <span style="color:#1f2328">app</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">fn</span><span style="color:#1f2328">.</span><span style="color:#1f2328">functionApp</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">trafficmanager</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Endpoint</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`tme</span><span style="color:#0a3069">${</span><span style="color:#1f2328">location</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">profileName</span>: <span style="color:#cf222e">profile.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">type</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#39;azureEndpoints&#39;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">targetResourceId</span>: <span style="color:#cf222e">app.id</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">target</span>: <span style="color:#cf222e">app.defaultHostname</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">endpointLocation</span>: <span style="color:#cf222e">app.location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>I assign the properties of each Function App to the corresponding Endpoint with a simple object initializer expression.</p>
<p>Everything is in place. I can export the Traffic Manager endpoint:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">endpoint</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">interpolate</span> <span style="color:#0a3069">`http://</span><span style="color:#0a3069">${</span><span style="color:#1f2328">profile</span><span style="color:#1f2328">.</span><span style="color:#1f2328">fqdn</span><span style="color:#0a3069">}</span><span style="color:#0a3069">/api/{key}`</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><p>Time to re-run the latency tests.</p>
<h2 id="results">Results</h2>
<p>I deployed the distributed infrastructure to five Azure regions: West US, East US, West Europe, East Asia, and Australia East. The results are shown in the table below, next to the values from the initial run:</p>
<table class="table table-striped">
  <thead>
      <tr>
          <th>Location</th>
          <th>Single region</th>
          <th>Multiple regions</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>San Francisco</td>
          <td>133 ms</td>
          <td>140 ms</td>
      </tr>
      <tr>
          <td>New York</td>
          <td>272 ms</td>
          <td>152 ms</td>
      </tr>
      <tr>
          <td>London</td>
          <td>383 ms</td>
          <td>150 ms</td>
      </tr>
      <tr>
          <td>Frankfurt</td>
          <td>420 ms</td>
          <td>130 ms</td>
      </tr>
      <tr>
          <td>Tel-Aviv</td>
          <td>470 ms</td>
          <td>307 ms</td>
      </tr>
      <tr>
          <td>Hong-Kong</td>
          <td>437 ms</td>
          <td>149 ms</td>
      </tr>
      <tr>
          <td>Brisbane</td>
          <td>449 ms</td>
          <td>529 ms</td>
      </tr>
  </tbody>
</table>
<p>We got a much smoother distribution of response time. There&rsquo;s no region close enough to Tel-Aviv, so its latency is still high-ish. I checked Brisbane, and the traffic was somehow directed to the East US region, so the latency hasn&rsquo;t improved at all. For today, I guess I&rsquo;ll stick to the hypothesis &ldquo;Australian internet is slow&rdquo; and advise to always test your target scenarios without trusting the providers blindly.</p>
<p>More importantly, I walked you through a scenario of developing an end-to-end application with Pulumi. I highlighted the power of leveraging familiar techniques coming from the TypeScript realm. You can find the full code in <a href="https://github.com/pulumi/examples/tree/master/azure-ts-serverless-url-shortener-global">Pulumi examples</a>.</p>
<p>Cloud brings superpowers to developer&rsquo;s hands. You just need to use those efficiently.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/infrastructure-as-code" term="infrastructure-as-code" label="Infrastructure as Code" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                             
                                <category scheme="https://mikhail.io/tags/azure-cosmos-db" term="azure-cosmos-db" label="Azure Cosmos DB" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Hosting a Static Website on Azure with Pulumi]]></title>
            <link href="https://mikhail.io/2019/06/hosting-a-static-website-on-azure-with-pulumi/"/>
            <id>https://mikhail.io/2019/06/hosting-a-static-website-on-azure-with-pulumi/</id>
            
            <published>2019-06-27T00:00:00+00:00</published>
            <updated>2019-06-27T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Static websites are back in the mainstream these days. Setting up the infrastructure to serve a static website in Azure is a task where Pulumi shines.</blockquote><p>Static websites are back in the mainstream these days. Website generators like Jekyll, Hugo, or Gatsby, make it fairly easy to combine templates and markdown pages to produce static HTML files. Static assets are the simplest thing to serve and cache, so the whole setup ends up being fast and cost-efficient.</p>
<p>Many platforms offer services to host such static websites. This post explains the steps to create the infrastructure to do so on Microsoft Azure.</p>
<p>Setting up the infrastructure to serve a static website doesn&rsquo;t sound like it would be all that difficult, but when you consider HTTPS certificates, content distribution networks, and attaching it to a custom domain, integrating all the components can be quite daunting.</p>
<p>Fortunately, this is a task where Pulumi shines. Pulumi&rsquo;s code-centric approach not only makes configuring cloud resources easier to do and maintain, but it also eliminates the pain of integrating multiple services.</p>
<h2 id="overview">Overview</h2>
<p>Our goal is to create a static website with a custom domain—I&rsquo;ll use an imaginary <code>demo.pulumi.com</code> for this article. In 2019, my website has to support HTTPS, so we need to create a custom TLS certificate too.</p>
<p>The final solution consists of several Azure services:</p>
<ul>
<li>Static files will be stored in a <strong>Blob Container</strong> inside a <strong>Storage Account</strong></li>
<li>The Storage Account will have <strong>Static Website feature</strong> enabled to have some basic URL rewrite rules</li>
<li>We&rsquo;ll put an <strong>Azure CDN Endpoint</strong> in front of the container to support the custom domain over TLS</li>
<li>Azure CDN will self-manage the TLS certificate</li>
<li>Our custom DNS provider will have the rule to point to the CDN endpoint (that&rsquo;s a manual step)</li>
</ul>
<p>The diagram below outlines the interaction of these components:</p>
<p><img src="pulumi-azure-static-website.png" alt="Static website running on Azure and defined in Pulumi"></p>
<figcaption><h4>Static website running on Azure and defined in Pulumi</h4></figcaption>
<p>Let&rsquo;s break down how to configure each component using Pulumi.</p>
<h2 id="resource-group">Resource Group</h2>
<p>Let&rsquo;s start a new Pulumi program, import the Pulumi packages, and define a new resource group:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">pulumi</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/pulumi&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">azure</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/azure&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">resourceGroup</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">core</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ResourceGroup</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;demo-rg&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">location</span>: <span style="color:#cf222e">azure.WestEurope</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><h2 id="storage-account">Storage Account</h2>
<p>The Storage Account will contain our static website&rsquo;s assets:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">storageAccount</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Account</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;demopulumi&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountReplicationType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;LRS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountTier</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountKind</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;StorageV2&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>The only trick here is to make sure that the account kind is V2; otherwise, it won&rsquo;t support the static website feature.</p>
<h2 id="static-website-hosting-in-azure-storage">Static Website Hosting in Azure Storage</h2>
<p>Any storage container could be exposed as a web endpoint. However, that&rsquo;s not flexible enough. The URL would always include the container name and the exact file name, so the user would have to ask for <code>https://demo.pulumi.com/containername/index.html</code> instead of simply <code>https://demo.pulumi.com/</code>.</p>
<p>Enabling Static Website Hosting in Azure Storage improves this experience. A dedicated container <strong><code>$web</code></strong> gets automatically created, which also has special treatment for <code>index</code> and <code>404</code> documents.</p>
<p>The bad news is that Static Website Hosting is not a part of Azure Resource Manager API, and therefore, it&rsquo;s not available out-of-the-box in ARM templates, Terraform, or Pulumi. We can enable this feature with Azure CLI, so the solution is to create a dynamic Pulumi resource which enables Pulumi experience while delegating the work to the CLI. You can find the full source code for the dynamic resource in <a href="https://github.com/pulumi/examples/blob/master/azure-ts-static-website/staticWebsite.ts">this example</a>, but the usage is quite trivial:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">staticWebsite</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">StorageStaticWebsite</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;demopulumi-static&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">{</span> <span style="color:#1f2328">accountName</span>: <span style="color:#cf222e">storageAccount.name</span> <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">{</span> <span style="color:#1f2328">parent</span>: <span style="color:#cf222e">storageAccount</span> <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Web endpoint to the website
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">staticEndpoint</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">staticWebsite</span><span style="color:#1f2328">.</span><span style="color:#1f2328">endpoint</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><p>The last line exports the static website endpoint. It will look something like <code>https://demopulumi01234abc.z6.web.core.windows.net/</code>—no custom domain yet, but already functional—once we deploy some static files in there.</p>
<h2 id="static-files">Static Files</h2>
<p>Now, it&rsquo;s time to upload the static files to the <code>$web</code> Blob Container.</p>
<p>For this demo, I&rsquo;ve created a folder <code>wwwroot</code> with two files in it: <code>index.html</code> and <code>404.html</code>. I can upload those files with the following Pulumi snippet:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#1f2328">[</span><span style="color:#0a3069">&#34;index.html&#34;</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;404.html&#34;</span><span style="color:#1f2328">].</span><span style="color:#1f2328">map</span><span style="color:#1f2328">(</span><span style="color:#1f2328">name</span> <span style="color:#0550ae">=&gt;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Blob</span><span style="color:#1f2328">(</span><span style="color:#1f2328">name</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">storageAccountName</span>: <span style="color:#cf222e">storageAccount.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">storageContainerName</span>: <span style="color:#cf222e">staticWebsite.webContainerName</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">type</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;block&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">source</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">`./wwwroot/</span><span style="color:#0a3069">${</span><span style="color:#1f2328">name</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">contentType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;text/html&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">})</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">);</span>
</span></span></code></pre></div><p>In practice, your static website might contain hundreds or thousands of files. At that point, you might want to split the file upload operation from Pulumi and do it as a separate step in your CI/CD pipeline.</p>
<h2 id="azure-cdn">Azure CDN</h2>
<p>To make the static website files available over our custom domain and HTTPS, we need to create an Azure CDN Endpoint and to point it to the storage account:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">cdn</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">cdn</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Profile</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;demo-cdn&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">sku</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard_Microsoft&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">endpoint</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">cdn</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Endpoint</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;demo-cdn-ep&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;demopulumi&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">profileName</span>: <span style="color:#cf222e">cdn.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">isHttpAllowed</span>: <span style="color:#cf222e">true</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">isHttpsAllowed</span>: <span style="color:#cf222e">true</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">originHostHeader</span>: <span style="color:#cf222e">staticWebsite.hostName</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">origins</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;blobstorage&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">hostName</span>: <span style="color:#cf222e">staticWebsite.hostName</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}],</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// CDN endpoint to the website. Allow it some time after the deployment to get ready.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">cdnEndpoint</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">interpolate</span><span style="color:#0a3069">`https://</span><span style="color:#0a3069">${</span><span style="color:#1f2328">endpoint</span><span style="color:#1f2328">.</span><span style="color:#1f2328">hostName</span><span style="color:#0a3069">}</span><span style="color:#0a3069">/`</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><p>I specified an explicit name for the CDN Endpoint: <code>demopulumi</code>. This name has to be globally unique because it is a part of the endpoint URL <code>https://demopulumi.azureedge.net</code>. Please pick a custom name before running the program.</p>
<p>I pointed the CDN origin to the static website name with <code>staticWebsite.hostName</code>. As usual, it&rsquo;s easy to link resources in Pulumi code!</p>
<p>You might need to wait a few minutes before your content is visible as the CDN configuration is not immediately executed. I&rsquo;ve set <code>isHttpAllowed</code> to <code>true</code> because HTTP is available sooner than HTTPS; feel free to switch it off for your production configuration.</p>
<h2 id="configure-a-domain-dns-rule">Configure a Domain DNS Rule</h2>
<p>You&rsquo;ve probably registered your domain with some third-party provider. Follow the instructions of your provider to configure a CNAME rule for the website&rsquo;s DNS. For a custom domain <code>demo.pulumi.com</code>, the CNAME entry <code>demo</code> would be linked to the endpoint <code>demopulumi.azureedge.net</code>.</p>
<p>CNAME entries don&rsquo;t support &ldquo;naked&rdquo; domains like <code>pulumi.com</code>. If you want to set up a top-level domain to be served from the static Azure website, you&rsquo;d have to use an <a href="https://support.dnsimple.com/articles/alias-record/">Alias DNS record</a>, which isn&rsquo;t supported by some DNS providers. Please check with your provider for available options.</p>
<p>The following step assumes that a CNAME record is configured; otherwise, it would fail with a validation error.</p>
<h2 id="custom-domain-and-tls">Custom Domain and TLS</h2>
<p>The final step is to point our custom domain to the CDN endpoint and provision a TLS certificate to enable HTTPS support. Once again, these operations are not parts of the ARM API surface, so another <a href="https://github.com/pulumi/examples/blob/master/azure-ts-dynamicresource/cdnCustomDomain.ts">dynamic resource</a> was created to support them.</p>
<p>The usage is quite straightforward, just make sure to use your own domain in the following snippet:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">customDomain</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">CDNCustomDomainResource</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;cdn-custom-domain&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#57606a">// Ensure that there is a CNAME record for demo pointing.pulumi.com to demopulumi.azureedge.net.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>   <span style="color:#57606a">// You would do that in your domain registrar&#39;s portal.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>   <span style="color:#1f2328">customDomainHostName</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;demo.pulumi.com&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">profileName</span>: <span style="color:#cf222e">cdn.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">endpointName</span>: <span style="color:#cf222e">endpoint.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#57606a">// This will enable HTTPS through Azure&#39;s one-click automated certificate deployment.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>   <span style="color:#57606a">// The certificate is fully managed by Azure from provisioning to automatic renewal
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>   <span style="color:#57606a">// at no additional cost to you.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>   <span style="color:#1f2328">httpsEnabled</span>: <span style="color:#cf222e">true</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">},</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">parent</span>: <span style="color:#cf222e">endpoint</span> <span style="color:#1f2328">});</span>
</span></span></code></pre></div><h2 id="bring-it-live">Bring It Live!</h2>
<p>And we are done! Run <code>pulumi up</code> and make sure that all resources get created successfully.</p>
<p>Start testing with <code>staticEndpoint</code>—it&rsquo;s the first one to become available. <code>cdnEndpoint</code> might return some <code>404</code>&rsquo;s at first, be patient. Then, try your custom domain with <code>HTTP</code>. Finally, the TLS certificate takes quite a while to be registered, so come back in an hour or so to test <code>HTTPS</code>.</p>
<p>While a &ldquo;static website&rdquo; may sound simple, we went through a rather complicated process to wire all the components together. Some Azure services and features are more straightforward to automate than others, but in the end, we combined all of them into one cohesive Pulumi program to host a static website, served over HTTPS and from a worldwide CDN. That&rsquo;s the power of a general-purpose programming language applied to the task of sophisticated infrastructure automation. Once the reusable components are in place, reliable and reproducible deployments become a reality.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/static-website" term="static-website" label="Static Website" />
                             
                                <category scheme="https://mikhail.io/tags/infrastructure-as-code" term="infrastructure-as-code" label="Infrastructure-as-Code" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Serverless as Simple Callbacks with Pulumi and Azure Functions]]></title>
            <link href="https://mikhail.io/2019/05/serverless-as-simple-callbacks-with-pulumi-and-azure-functions/"/>
            <id>https://mikhail.io/2019/05/serverless-as-simple-callbacks-with-pulumi-and-azure-functions/</id>
            
            <published>2019-05-07T00:00:00+00:00</published>
            <updated>2019-05-07T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>The simplest way to take a Node.js function and deploy it to Azure cloud as an HTTP endpoint using Pulumi.</blockquote><p>Serverless compute services, like Azure Functions, offer an amazing power to application developers to leverage: highly available, automatically scaled, low-ceremony, pay-per-value functions created in several lines of code.</p>
<p>So, what&rsquo;s <strong>the simplest</strong> way to take a Node.js function and deploy it to Azure cloud as an HTTP endpoint? How about this little tutorial:</p>
<h4 id="1-create-a-new-pulumi-program">1. Create a new Pulumi program:</h4>
<pre tabindex="0"><code>$ pulumi new azure-typescript
</code></pre><h4 id="2-define-an-http-endpoint-in-indexts">2. Define an HTTP endpoint in <code>index.ts</code>:</h4>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">azure</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#39;@pulumi/azure&#39;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">resourceGroup</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">core</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ResourceGroup</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#39;example&#39;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">location</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#39;West US&#39;</span> <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">greeting</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">HttpEventSubscription</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#39;greeting&#39;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span> <span style="color:#1f2328">resourceGroup</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span> <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">req</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>     <span style="color:#1f2328">status</span>: <span style="color:#cf222e">200</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>     <span style="color:#1f2328">body</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">`Hello </span><span style="color:#0a3069">${</span><span style="color:#1f2328">req</span><span style="color:#1f2328">.</span><span style="color:#1f2328">query</span><span style="color:#1f2328">[</span><span style="color:#0a3069">&#39;name&#39;</span><span style="color:#1f2328">]</span> <span style="color:#0550ae">||</span> <span style="color:#0a3069">&#39;World&#39;</span><span style="color:#0a3069">}</span><span style="color:#0a3069">!`</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">url</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">greeting</span><span style="color:#1f2328">.</span><span style="color:#1f2328">url</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><h4 id="3-deploy">3. Deploy:</h4>
<pre tabindex="0"><code>$ pulumi up

Updating:
   Type
 + pulumi:pulumi:Stack
 + ├─ azure:appservice:HttpEventSubscription
 + │  ├─ azure:storage:Account
 + │  ├─ azure:appservice:Plan
 + │  ├─ azure:storage:Container
 + │  ├─ azure:storage:ZipBlob
 + │  └─ azure:appservice:FunctionApp
 + └─ azure:core:ResourceGroup

Outputs:
   url: &#34;https://greetingc21a23fe.azurewebsites.net/api/greeting&#34;
Resources:
   + 8 created
</code></pre><h4 id="4-access-your-function-via-http">4. Access your function via HTTP:</h4>
<pre tabindex="0"><code>$ curl https://greetingc21a23fe.azurewebsites.net/api/greeting?name=Pulumi
Hello Pulumi!
</code></pre><p>With 12 lines of code and two CLI commands, I&rsquo;ve created all Azure resources required to host a serverless function without an explicit configuration of Azure services. Okay, I had to define a location for my resource group, but that could also be accomplished via <code>pulumi config</code>.</p>
<p>Pulumi compiled my TypeScript function, serialized it to a JavaScript file, created the bindings in a <code>function.json</code> file, hosting configuration in a <code>host.json</code> file, uploaded all these assets to Blob Storage, and configured a Consumption Plan and a Function App to run my function. An automated and reproducible deployment in less than two minutes.</p>
<h2 id="beyond-hello-world">Beyond Hello-World</h2>
<p>The power of Node.js comes from the richness of its library ecosystem. There&rsquo;s an NPM package for everything, so you definitely want to use those.</p>
<p>Serverless-function-as-callback imports dependencies in a transparent way. Install the NPM packages of your choice and use them inside the callback:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">moment</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#39;moment&#39;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">greeting</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">HttpEventSubscription</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#39;greeting&#39;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span> <span style="color:#1f2328">resourceGroup</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span> <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">req</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">return</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>     <span style="color:#1f2328">status</span>: <span style="color:#cf222e">200</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>     <span style="color:#1f2328">body</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">`Hello </span><span style="color:#0a3069">${</span><span style="color:#1f2328">req</span><span style="color:#1f2328">.</span><span style="color:#1f2328">query</span><span style="color:#1f2328">[</span><span style="color:#0a3069">&#39;name&#39;</span><span style="color:#1f2328">]</span> <span style="color:#0550ae">||</span> <span style="color:#0a3069">&#39;World&#39;</span><span style="color:#0a3069">}</span><span style="color:#0a3069"> at </span><span style="color:#0a3069">${</span><span style="color:#1f2328">moment</span><span style="color:#1f2328">().</span><span style="color:#1f2328">format</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#39;LLLL&#39;</span><span style="color:#1f2328">)</span><span style="color:#0a3069">}</span><span style="color:#0a3069">!`</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>The packages get zipped up inside the deployment artifact automatically so that the Function App can find them at the startup. So there&rsquo;s no need to manually figure out how to produce the archive, get it uploaded, and maintain it as your libraries get updated.</p>
<p>Stay tuned for another blog post with a full implementation of a serverless URL shortener application deployed into multiple Azure regions for fast response time and improved resiliency.</p>
<h2 id="not-only-http">Not Only HTTP</h2>
<p>Your application might not be a bunch of HTTP functions. You probably want to leverage queues for asynchronous message passing. How about defining a callback on the queue object itself:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">storageAccount</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Account</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;storage&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">location</span>: <span style="color:#cf222e">resourceGroup.location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountReplicationType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;LRS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountTier</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">queue</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Queue</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;myqueue&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">storageAccountName</span>: <span style="color:#cf222e">storageAccount.name</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">queue</span><span style="color:#1f2328">.</span><span style="color:#1f2328">onEvent</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;newMessage&#34;</span><span style="color:#1f2328">,</span>  <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">msg</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#57606a">// code to process &#39;msg&#39; however you want here
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>   <span style="color:#1f2328">console</span><span style="color:#1f2328">.</span><span style="color:#1f2328">log</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;Message received: &#34;</span> <span style="color:#0550ae">+</span> <span style="color:#1f2328">msg</span><span style="color:#1f2328">.</span><span style="color:#1f2328">toString</span><span style="color:#1f2328">());</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Alternatively, define a ServiceBus topic and immediately subscribe to the messages:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">servicebus</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/azure/eventhub&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#cf222e">namespace</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">servicebus</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Namespace</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;test&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">sku</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;standard&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">topic</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">servicebus</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Topic</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;mytopic&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">namespaceName</span>: <span style="color:#cf222e">namespace.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">subscription</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">topic</span><span style="color:#1f2328">.</span><span style="color:#1f2328">onEvent</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;mysubscription&#34;</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">msg</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">console</span><span style="color:#1f2328">.</span><span style="color:#1f2328">log</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;Received: &#34;</span> <span style="color:#0550ae">+</span> <span style="color:#1f2328">msg</span><span style="color:#1f2328">.</span><span style="color:#1f2328">toString</span><span style="color:#1f2328">());</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>In addition, get notified when a new PNG image is uploaded to a Blob Container:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">storageContainer</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Container</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;images-container&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">storageAccountName</span>: <span style="color:#cf222e">storageAccount.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;images&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">storageContainer</span><span style="color:#1f2328">.</span><span style="color:#1f2328">onBlobEvent</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;newImage&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">callback</span>: <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">context</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">blob</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">console</span><span style="color:#1f2328">.</span><span style="color:#1f2328">log</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;File size: &#34;</span> <span style="color:#0550ae">+</span> <span style="color:#1f2328">context</span><span style="color:#1f2328">.</span><span style="color:#1f2328">bindingData</span><span style="color:#1f2328">.</span><span style="color:#1f2328">properties</span><span style="color:#1f2328">.</span><span style="color:#1f2328">length</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">filterSuffix</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;.png&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>In all these examples, you get a fully configured Function App on Consumption  Plan ramped up and bound to the trigger of choice. Your callback runs in the cloud handling every event with no manual work of hooking these different components together.</p>
<p>Using a general-purpose language like TypeScript provides one consistent approach to defining and delivering serverless applications and infrastructure as one cohesive application.</p>
<p>We strive to make cloud development as simple as everyday JavaScript development that made the language so successful. A lot is happening behind the scenes, but the code still looks like &ldquo;normal code&rdquo;. Composition of cloud resources should be as straightforward as hooking up components in any traditional application.</p>
<h2 id="looking-ahead">Looking Ahead</h2>
<p>Pulumi serverless programming model for Azure Functions is just ramping up. There is only a handful of trigger types supported right now, and some configuration parameters are not exposed yet.</p>
<p>So, today is the perfect time to chime in and join the discussion! Help us answer the questions:</p>
<ul>
<li>Is this programming model beneficial to your scenarios?</li>
<li>Which trigger types do you want to be supported?</li>
<li>How should we package multiple functions into Function App(s)?</li>
<li>Do you need input and output bindings to be supported, and if yes, in which shape?</li>
</ul>
<p>Feel free to leave a comment below, create an issue on <a href="https://github.com/pulumi/pulumi-azure/">GitHub</a>, tag me on <a href="https://twitter.com/MikhailShilkov">Twitter</a>, or join <a href="https://slack.pulumi.io/">Pulumi community Slack channel</a>.</p>
<p>Happy serverless programming!</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/infrastructure-as-code" term="infrastructure-as-code" label="Infrastructure-as-Code" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Level up your Azure Platform as a Service applications with Pulumi]]></title>
            <link href="https://mikhail.io/2019/05/level-up-your-azure-platform-as-a-service-applications-with-pulumi/"/>
            <id>https://mikhail.io/2019/05/level-up-your-azure-platform-as-a-service-applications-with-pulumi/</id>
            
            <published>2019-05-06T00:00:00+00:00</published>
            <updated>2019-05-06T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Leverage Pulumi for continuous delivery of code and infrastructure to Azure PaaS. An ASP.NET Core application running on App Service and DevOps pipelines.</blockquote><p>Pulumi enables developers to define cloud infrastructure using general purpose programming languages. Pulumi works with multiple cloud providers and has first-class support for all services in Microsoft Azure.</p>
<p>Today I want to guide you through the process of developing Pulumi programs to leverage Azure <a href="https://azure.microsoft.com/overview/what-is-paas/">Platform-as-a-Service</a> (PaaS) services. My language of choice is TypeScript—a powerful and expressive typed language, which is very familiar to many Azure users.</p>
<h2 id="azure-platform-as-a-service">Azure Platform as a Service</h2>
<p>Azure consists of dozens of cloud services, from VMs to Kubernetes to Serverless. In my experience, a lot of customers choose Azure for its strong portfolio of PaaS-level services.</p>
<p><a href="https://azure.microsoft.com/services/app-service/">Azure App Service</a> is a well-established managed compute offering to run web applications, RESTful APIs, or background workers. <a href="https://azure.microsoft.com/services/sql-database/">Azure SQL Database</a> is a fully managed service to run relational databases with features like high availability and backups available out-of-the-box. Enriched by services like <a href="https://azure.microsoft.com/services/devops/">Azure DevOps</a> for CI/CD and <a href="https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview">Application Insights</a> for APM, PaaS is a powerful way to get the benefits of the cloud without the need to fully re-architect software solutions.</p>
<p>The power of relying on PaaS is evidenced by significant customer adoption. App Service is among the most popular compute services in Azure:</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">If you use automation (ARM, scripts, TF, ...) to define and deploy Azure infrastructure, which services are your primary target? Vote &amp; RT!</p>&mdash; Mikhail Shilkov (@MikhailShilkov) <a href="https://twitter.com/MikhailShilkov/status/1120592994351099904?ref_src=twsrc%5Etfw">April 23, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>Nonetheless, PaaS services pose different challenges to application developers. In particular, the usage of multiple cloud services demands an investment in infrastructure automation. That&rsquo;s where Pulumi comes to the rescue.</p>
<h2 id="a-sample-application">A Sample Application</h2>
<p>For this walkthrough, I took an existing application from Azure Samples GitHub: <a href="https://github.com/azure-samples/dotnetcore-sqldb-tutorial">.NET Core MVC sample for Azure App Service</a>. Predictably enough, it&rsquo;s a Todo List application, and this time it is a web app built with ASP.NET Core, Entity Framework Core and a SQL database. <a href="https://docs.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-dotnetcore-sqldb">Build an ASP.NET Core and SQL Database app in Azure App Service</a> describes how to deploy such application to Azure App Service by means of clicking buttons in the Azure portal.</p>
<blockquote>
<p>Friends don&rsquo;t let friends right-click publish</p></blockquote>
<p>Instead, I suggest relying on infrastructure as code. I built a Pulumi program and integrated it into a fully automated build and deployment pipeline in Azure DevOps.</p>
<p>Here is a sketch of the solution:</p>
<p><img src="pulumi-app-service.png" alt="Pulumi and Azure PaaS - Application Diagram"></p>
<figcaption><h4>Pulumi and Azure PaaS - Application Diagram</h4></figcaption>
<p>Let&rsquo;s get started building together!</p>
<h2 id="solution-structure">Solution Structure</h2>
<p>The following snippet shows the essential elements of the solution:</p>
<pre tabindex="0"><code>\infra                   # Cloud infrastructure definition goes here
   \index.ts             # Pulumi program in TypeScript
\src
   \Controllers          # \
   \Models               #  ASP.NET Core web app
   \Views                # /
   \Data                 # EF Core Data Context
   \wwwroot              # Static assets (JavaScript/CSS/Images)
\azure-pipelines.yml     # Azure DevOps pipeline definition
</code></pre><p>As a first step, I cloned the <a href="https://github.com/azure-samples/dotnetcore-sqldb-tutorial">Todo List app</a> into the <code>src</code> folder. There&rsquo;s nothing specific to Pulumi here: it&rsquo;s just an ASP.NET Core app. It could be your application instead.</p>
<h2 id="bootstrapping-a-pulumi-program">Bootstrapping a Pulumi Program</h2>
<p>The Pulumi development experience is powered by the <a href="https://pulumi.io/reference/commands.html">Pulumi CLI</a>. After <a href="https://pulumi.io/quickstart/install.html">installing the CLI</a>, I jump into an empty <code>infra</code> folder and run <code>pulumi new azure-typescript</code> accepting all the default answers. The CLI bootstraps a skeleton of a TypeScript NodeJS application. The code looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">pulumi</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/pulumi&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">azure</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/azure&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Create an Azure Resource Group
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">const</span> <span style="color:#1f2328">resourceGroup</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">core</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ResourceGroup</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;resourceGroup&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">location</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;WestUS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Create an Azure resource (Storage Account)
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">const</span> <span style="color:#1f2328">account</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Account</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;storage&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">location</span>: <span style="color:#cf222e">resourceGroup.location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountTier</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountReplicationType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;LRS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Export the connection string for the storage account
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">export</span> <span style="color:#cf222e">const</span> <span style="color:#1f2328">connectionString</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">account</span><span style="color:#1f2328">.</span><span style="color:#1f2328">primaryConnectionString</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><p>The infrastructure pieces are defined by instantiating objects of appropriate types: <code>ResourceGroup</code> and <code>Account</code> in this example.</p>
<h2 id="stacks">Stacks</h2>
<p>If an application is developed to run and evolve for months and years, it&rsquo;s smart to invest in practices like Continuous Integration and Deployment (CI/CD) and Infrastructure as Code (IaC). It&rsquo;s quite likely that such an application will run in multiple environments: production, staging, development, and so on.</p>
<p>Pulumi comes with a handy concept of <a href="https://pulumi.io/reference/stack.html">stacks</a>— isolated, independently configurable instances of a Pulumi program. A separate stack can be designated for each deployment environment.</p>
<p>We can take the notion of the stack into the program and apply the stack name to definitions of infrastructure resources:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#57606a">// Use first 10 characters of the stackname as prefix for resource names
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">const</span> <span style="color:#1f2328">prefix</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">getStack</span><span style="color:#1f2328">().</span><span style="color:#1f2328">substring</span><span style="color:#1f2328">(</span><span style="color:#0550ae">0</span><span style="color:#1f2328">,</span> <span style="color:#0550ae">9</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">resourceGroup</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">core</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ResourceGroup</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">prefix</span><span style="color:#0a3069">}</span><span style="color:#0a3069">-rg`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">location</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;WestUS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">resourceGroupArgs</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">location</span>: <span style="color:#cf222e">resourceGroup.location</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Storage Account name must be lowercase and cannot have any dash characters
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">const</span> <span style="color:#1f2328">storageAccountName</span> <span style="color:#0550ae">=</span> <span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">prefix</span><span style="color:#1f2328">.</span><span style="color:#1f2328">toLowerCase</span><span style="color:#1f2328">().</span><span style="color:#1f2328">replace</span><span style="color:#1f2328">(</span><span style="color:#0a3069">/-/g</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;&#34;</span><span style="color:#1f2328">)</span><span style="color:#0a3069">}</span><span style="color:#0a3069">sa`</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">storageAccount</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Account</span><span style="color:#1f2328">(</span><span style="color:#1f2328">storageAccountName</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">...</span><span style="color:#1f2328">resourceGroupArgs</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountKind</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;StorageV2&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountTier</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Standard&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">accountReplicationType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;LRS&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Note how I use the power of the general purpose programming language to</p>
<ul>
<li>Interact with the environment by reading the stack name</li>
<li>Encode custom rules for resource naming</li>
<li>Work around the shortcomings of the cloud, namely, the restricted set of characters to use in Storage Accounts</li>
<li>Extract the value <code>resourceGroupArgs</code> to reuse the same definition for upcoming resources.</li>
</ul>
<p>As a result, the stacks <code>production</code> and <code>dev</code> will be deployed to separate resource groups with clean and consistent naming throughout the resources.</p>
<h2 id="deploying-the-application-to-app-service">Deploying the Application to App Service</h2>
<p>Now, it&rsquo;s time to define the infrastructure to host my ASP.NET Core app. There are three pieces of the puzzle to fit together.</p>
<h3 id="1-app-service-plan">1. App Service Plan</h3>
<p>An App Service Plan defines the pricing tier, instance size and other parameters related to performance, scalability, and cost of the hosted application. My definition is relatively straightforward:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">appServicePlan</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Plan</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">prefix</span><span style="color:#0a3069">}</span><span style="color:#0a3069">-asp`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">...</span><span style="color:#1f2328">resourceGroupArgs</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">kind</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;App&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">sku</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">tier</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Basic&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">size</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;B1&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>In a more advanced scenario, I could provision different performance tiers based on the target environment identified by the stack name.</p>
<h3 id="2-deployment-artifact">2. Deployment Artifact</h3>
<p>App Service is a mature Azure service with a long history, so it has numerous options for deployment methods. Arguably, the newest <a href="https://github.com/Azure/app-service-announcements/issues/110">Run from Package</a> is the most friendly way to practice Infrastructure as Code automation.</p>
<p>Essentially, I prepare a zip file with the published .NET Core assemblies and upload it to Azure Blob Storage:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">storageContainer</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Container</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">prefix</span><span style="color:#0a3069">}</span><span style="color:#0a3069">-c`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">storageAccountName</span>: <span style="color:#cf222e">storageAccount.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">containerAccessType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;private&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">blob</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">storage</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ZipBlob</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">prefix</span><span style="color:#0a3069">}</span><span style="color:#0a3069">-b`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">storageAccountName</span>: <span style="color:#cf222e">storageAccount.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">storageContainerName</span>: <span style="color:#cf222e">storageContainer.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#cf222e">type</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;block&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">content</span>: <span style="color:#cf222e">new</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">asset</span><span style="color:#1f2328">.</span><span style="color:#1f2328">FileArchive</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;../src/bin/Debug/netcoreapp2.1/publish.zip&#34;</span><span style="color:#1f2328">)</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><h3 id="3-app-service">3. App Service</h3>
<p>Now, I can define an App Service and instruct it to use this package to run the website. I do so by linking its application settings to the shared access signature of the blob:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">codeBlobUrl</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">signedBlobReadUrl</span><span style="color:#1f2328">(</span><span style="color:#1f2328">blob</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">storageAccount</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">storageContainer</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">app</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">AppService</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">prefix</span><span style="color:#0a3069">}</span><span style="color:#0a3069">-as`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">...</span><span style="color:#1f2328">resourceGroupArgs</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">appServicePlanId</span>: <span style="color:#cf222e">appServicePlan.id</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">appSettings</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#0a3069">&#34;WEBSITE_RUN_FROM_PACKAGE&#34;</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">codeBlobUrl</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>At startup, App Service downloads the zip and mounts it as a local read-only disk to store the application binaries.</p>
<h2 id="adding-a-sql-database">Adding a SQL Database</h2>
<p>The application host is now defined, but I also need a relational database to store and query Todo Items. Staying true to the PaaS path, I&rsquo;m using Azure SQL Database service.</p>
<h3 id="reading-configuration-parameters">Reading Configuration Parameters</h3>
<p>Setting up a SQL Server requires a couple of parameter values that might change between execution environments, for instance, a username and a password for the connection string. Pulumi provides <a href="https://pulumi.io/reference/config.html">a way to configure</a> the program&rsquo;s parameters per stack.</p>
<p>The configuration itself will happen in my CI/CD pipeline. For now, I can query the values with <code>pulumi.Config</code> helper tool:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#57606a">// Get the username and the password to use for SQL from config.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">const</span> <span style="color:#1f2328">config</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Config</span><span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">username</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">config</span><span style="color:#1f2328">.</span><span style="color:#cf222e">require</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;sqlUsername&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">pwd</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">config</span><span style="color:#1f2328">.</span><span style="color:#cf222e">require</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;sqlPassword&#34;</span><span style="color:#1f2328">);</span>
</span></span></code></pre></div><h3 id="azure-sql-server-and-database">Azure SQL Server and Database</h3>
<p>I&rsquo;m all set to code the SQL infrastructure. Two resources need to be defined:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">sqlServer</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">sql</span><span style="color:#1f2328">.</span><span style="color:#1f2328">SqlServer</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">prefix</span><span style="color:#0a3069">}</span><span style="color:#0a3069">-sql`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">...</span><span style="color:#1f2328">resourceGroupArgs</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">administratorLogin</span>: <span style="color:#cf222e">username</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">administratorLoginPassword</span>: <span style="color:#cf222e">pwd</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">version</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;12.0&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">database</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">sql</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Database</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">prefix</span><span style="color:#0a3069">}</span><span style="color:#0a3069">-db`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">...</span><span style="color:#1f2328">resourceGroupArgs</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">serverName</span>: <span style="color:#cf222e">sqlServer.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">requestedServiceObjectiveName</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;S0&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Note that <code>requestedServiceObjectiveName</code> defines the performance tier and the price of the Azure SQL Database.</p>
<h3 id="wiring-app-service-to-the-database">Wiring App Service to the Database</h3>
<p>By default, Azure SQL Database is configured to deny any incoming connections for security reasons. One approach is to allow access to all Azure services. However, a more secure method is to white-list the Outbound IPs of the App Service:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">firewallRules</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">app</span><span style="color:#1f2328">.</span><span style="color:#1f2328">outboundIpAddresses</span><span style="color:#1f2328">.</span><span style="color:#1f2328">apply</span><span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">ips</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">ips</span><span style="color:#1f2328">.</span><span style="color:#1f2328">split</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#39;,&#39;</span><span style="color:#1f2328">).</span><span style="color:#1f2328">map</span><span style="color:#1f2328">(</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">ip</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">sql</span><span style="color:#1f2328">.</span><span style="color:#1f2328">FirewallRule</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`FR</span><span style="color:#0a3069">${</span><span style="color:#1f2328">ip</span><span style="color:#0a3069">}</span><span style="color:#0a3069">`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">endIpAddress</span>: <span style="color:#cf222e">ip</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">resourceGroupName</span>: <span style="color:#cf222e">resourceGroup.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">serverName</span>: <span style="color:#cf222e">sqlServer.name</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>           <span style="color:#1f2328">startIpAddress</span>: <span style="color:#cf222e">ip</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">})</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">));</span>
</span></span></code></pre></div><p>Note that the actual IPs of the service are unknown at the time of writing the program. Nonetheless, the combination of <code>apply</code>, <code>split</code>, and <code>map</code> functions enables me to wire the runtime value of IPs to the proper set of firewall rules.</p>
<p>Additional firewall rules may be implemented to allow administrative access from outside the App Service.</p>
<p>Finally, our ASP.NET Core application expects a connection string with <code>MyDbConnection</code>. To create one, I join the database server and the database name to produce the connection string and add it to the App Service configuration:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">app</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">AppService</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">prefix</span><span style="color:#0a3069">}</span><span style="color:#0a3069">-as`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#57606a">// ... other parameters as defined above
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">connectionStrings</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;MyDbConnection&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>       <span style="color:#1f2328">value</span>:
</span></span><span style="display:flex;"><span>           <span style="color:#cf222e">pulumi.all</span><span style="color:#1f2328">([</span><span style="color:#1f2328">sqlServer</span><span style="color:#1f2328">.</span><span style="color:#1f2328">name</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">database</span><span style="color:#1f2328">.</span><span style="color:#1f2328">name</span><span style="color:#1f2328">]).</span><span style="color:#1f2328">apply</span><span style="color:#1f2328">(([</span><span style="color:#1f2328">server</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">db</span><span style="color:#1f2328">])</span> <span style="color:#0550ae">=&gt;</span>
</span></span><span style="display:flex;"><span>               <span style="color:#0a3069">`Server=tcp:</span><span style="color:#0a3069">${</span><span style="color:#1f2328">server</span><span style="color:#0a3069">}</span><span style="color:#0a3069">.database.windows.net;initial catalog=</span><span style="color:#0a3069">${</span><span style="color:#1f2328">db</span><span style="color:#0a3069">}</span><span style="color:#0a3069">;user ID=</span><span style="color:#0a3069">${</span><span style="color:#1f2328">username</span><span style="color:#0a3069">}</span><span style="color:#0a3069">;password=</span><span style="color:#0a3069">${</span><span style="color:#1f2328">pwd</span><span style="color:#0a3069">}</span><span style="color:#0a3069">;Min Pool Size=0;Max Pool Size=30;Persist Security Info=true;`</span><span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>       <span style="color:#cf222e">type</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;SQLAzure&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">}]</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><h2 id="collecting-metrics-with-application-insights">Collecting Metrics with Application Insights</h2>
<p>Application Insights is an Application Performance Management (APM) service to be used for collecting metrics from cloud applications.</p>
<p>After adding Application Insights NuGet packages into my ASP.NET solution, I can go ahead and define the App Insights resource and link it to the App Service with an instrumentation key:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">appInsights</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appinsights</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Insights</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">prefix</span><span style="color:#0a3069">}</span><span style="color:#0a3069">-ai`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">...</span><span style="color:#1f2328">resourceGroupArgs</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">applicationType</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;Web&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">app</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">azure</span><span style="color:#1f2328">.</span><span style="color:#1f2328">appservice</span><span style="color:#1f2328">.</span><span style="color:#1f2328">AppService</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">prefix</span><span style="color:#0a3069">}</span><span style="color:#0a3069">-as`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#57606a">// ... other parameters as defined above
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>
</span></span><span style="display:flex;"><span>   <span style="color:#1f2328">appSettings</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#0a3069">&#34;ApplicationInsights:InstrumentationKey&#34;</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">appInsights</span><span style="color:#1f2328">.</span><span style="color:#1f2328">instrumentationKey</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#57606a">// ... other settings as defined above
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>   <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><h2 id="continuous-deployment-with-azure-devops">Continuous Deployment with Azure DevOps</h2>
<p>While Pulumi CLI works great during development; a production deployment should rather be executed in a CI/CD pipeline. This time I&rsquo;m using Azure DevOps—a managed CI/CD service to build, test, and deploy cloud applications.</p>
<p>In particular, I defined an Azure Pipeline consisting of three steps:</p>
<ul>
<li>Build &amp; Publish the .NET Core application;</li>
<li>Restore NPM packages for the infrastructure program;</li>
<li>Provision the cloud infrastructure with Pulumi.</li>
</ul>
<h3 id="build--publish-the-net-application">Build &amp; Publish the .NET Application</h3>
<p>The first step utilizes a built-in task which triggers the .NET Core CLI to build the source code in the <code>src</code> folder and publish the assemblies as a zip file.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#0550ae">steps</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"></span>- <span style="color:#0550ae">task</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>DotNetCoreCLI@2<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"> </span><span style="color:#0550ae">inputs</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">   </span><span style="color:#0550ae">command</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#39;publish&#39;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">   </span><span style="color:#0550ae">projects</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#39;src&#39;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff"> </span><span style="color:#0550ae">displayName</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#39;Build and publish the ASP.NET Core app&#39;</span><span style="color:#fff">
</span></span></span></code></pre></div><h3 id="restore-npm-packages-for-the-infrastructure-program">Restore NPM packages for the infrastructure program</h3>
<p>The second step is a simple <code>npm install</code> step to restore the NodeJS dependencies:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#fff"> </span>- <span style="color:#0550ae">task</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>Npm@1<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">   </span><span style="color:#0550ae">inputs</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">     </span><span style="color:#0550ae">command</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#39;install&#39;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">     </span><span style="color:#0550ae">workingDir</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#39;infra&#39;</span><span style="color:#fff"> 
</span></span></span><span style="display:flex;"><span><span style="color:#fff">   </span><span style="color:#0550ae">displayName</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#39;NPM install packages&#39;</span><span style="color:#fff">
</span></span></span></code></pre></div><h3 id="install-pulumi-and-run-infrastructure-code">Install Pulumi and Run Infrastructure Code</h3>
<p>There is a Pulumi task available in Azure Marketplace: <a href="https://marketplace.visualstudio.com/items?itemName=pulumi.build-and-release-task">Pulumi Azure Pipelines Task</a>. After installing it to your organization, you should be able to utilize a simple task like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#fff"> </span>- <span style="color:#0550ae">task</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>Pulumi@0<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">   </span><span style="color:#0550ae">inputs</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">     </span><span style="color:#0550ae">azureSubscription</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#39;Your Azure Subscription(aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee)&#39;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">     </span><span style="color:#0550ae">command</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#39;up&#39;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">     </span><span style="color:#0550ae">args</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#39;--yes&#39;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">     </span><span style="color:#0550ae">cwd</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#39;infra&#39;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">     </span><span style="color:#0550ae">stack</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#39;dev&#39;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">   </span><span style="color:#0550ae">displayName</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#39;Install pulumi and run infra code&#39;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">   </span><span style="color:#0550ae">name</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>pulumi<span style="color:#fff">
</span></span></span></code></pre></div><p>Alternatively, if you can&rsquo;t install third-party tasks from Azure Marketplace, you can call the Pulumi CLI from a custom shell script, see <a href="https://github.com/pulumi/examples/tree/master/azure-ts-appservice-devops/alternative-pipeline/">this pipeline definition</a>.</p>
<p>When everything is wired correctly, I see this screen in Azure DevOps:</p>
<p><img src="pulumi-devops-build.png" alt="Green Build of a Pulumi Program in DevOps"></p>
<figcaption><h4>Green Build of a Pulumi Program in Azure DevOps</h4></figcaption>
<p>The newly created resource group contains six resources:</p>
<p><img src="pulumi-resource-group.png" alt="Azure Resource created by Pulumi"></p>
<figcaption><h4>Azure Resources created by Pulumi</h4></figcaption>
<p>The application is up and running:</p>
<p><img src="pulumi-todo-app.png" alt="Todo List App deployed to Azure with Pulumi"></p>
<figcaption><h4>Application Screenshot</h4></figcaption>
<p>The telemetry is flowing into Application Insights:</p>
<p><img src="pulumi-application-map.png" alt="Application Map from App Insights"></p>
<figcaption><h4>Application Map from Azure App Insights</h4></figcaption>
<p>You can find the full code of the application, infrastructure definition, and deployment pipeline in <a href="https://github.com/pulumi/examples/tree/master/azure-ts-appservice-devops/">Pulumi Examples repository</a>.</p>
<h2 id="pulumi--azure-paas">Pulumi ❤️ Azure PaaS</h2>
<p>Azure App Service and friends are a great way to deploy web applications and APIs without worrying about the nitty-gritty details of the underlying hardware.</p>
<p>Writing a TypeScript program to compose an application out of the cloud building blocks feels like a breeze to me. I can reuse all my skills and stay productive by defining cloud resources as code in a familiar language.</p>
<p>You can get going with these resources:</p>
<ul>
<li><a href="https://pulumi.io/quickstart/">Getting Started with Pulumi</a></li>
<li><a href="https://pulumi.io/quickstart/azure/setup.html">Setup Pulumi to work with Azure</a></li>
<li><a href="https://pulumi.io/quickstart/azure/index.html">Walkthroughs and Examples</a></li>
</ul>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/pulumi" term="pulumi" label="Pulumi" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/azure-app-service" term="azure-app-service" label="Azure App Service" />
                             
                                <category scheme="https://mikhail.io/tags/infrastructure-as-code" term="infrastructure-as-code" label="Infrastructure-as-Code" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Less Frequent Cold Starts in Google Cloud Functions]]></title>
            <link href="https://mikhail.io/2019/04/less-frequent-cold-starts-in-google-cloud-functions/"/>
            <id>https://mikhail.io/2019/04/less-frequent-cold-starts-in-google-cloud-functions/</id>
            
            <published>2019-04-27T00:00:00+00:00</published>
            <updated>2019-04-27T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Google keeps idle instances of Cloud Functions alive for many hours.</blockquote><p>Several days ago, I released an update to the <a href="/serverless/coldstarts/">Serverless Cold Starts</a> section of my website. The most significant change to the previous dataset seems to be in how GCP treats idle instances of Google Cloud Functions.</p>
<p>Cold starts are expensive, so all cloud providers preserve a warm instance of a cloud function even when the application is idle. If the function stays unused for an extended period, such idle instance might eventually be recycled.</p>
<p>Azure Functions recycles its instances after 20 minutes of idling. AWS Lambda has no fixed value, but in practice, it&rsquo;s between 25 and 65 minutes of idling.</p>
<h2 id="recycling-of-idle-instances-in-google-cloud-functions">Recycling of Idle Instances in Google Cloud Functions</h2>
<p>The behavior of Google Cloud Functions used to look quite random: an idle instance could survive for 5 hours or die after 5 minutes.</p>




  





<script type="text/javascript">
addChart((data, options) => {
  data.addColumn('number', 'ID');
  data.addColumn('number', 'Value');
  data.addColumn({ 'type': 'string', 'role': 'style' });
  data.addRows([[87.223692415,1.0566834,"point {fill-color: blue}"],[137.43873814833333,0.1484199,"point {fill-color: red}"],[74.57167498833333,1.1565178999999999,"point {fill-color: blue}"],[146.68497998166666,0.1531141,"point {fill-color: red}"],[139.61548119166667,0.1174578,"point {fill-color: red}"],[176.277201315,0.1454798,"point {fill-color: red}"],[24.758321308333333,0.41548179999999996,"point {fill-color: red}"],[249.36039055833334,0.11653999999999999,"point {fill-color: red}"],[43.489343070000004,0.19265649999999998,"point {fill-color: red}"],[43.41242365,0.15041849999999998,"point {fill-color: red}"],[76.81456954833334,0.16244519999999998,"point {fill-color: red}"],[169.590103985,1.228108,"point {fill-color: blue}"],[136.26404786,0.1420197,"point {fill-color: red}"],[242.24137742,1.4241983,"point {fill-color: blue}"],[95.82704468,0.1570009,"point {fill-color: red}"],[296.88630615333335,1.0368719,"point {fill-color: blue}"],[269.39025931000003,0.24895779999999998,"point {fill-color: red}"],[269.5663538983333,0.154046,"point {fill-color: red}"],[110.24344410333333,0.184665,"point {fill-color: red}"],[287.08118408,0.1465811,"point {fill-color: red}"],[182.57397214,0.1486031,"point {fill-color: red}"],[135.42568823666667,0.1216621,"point {fill-color: red}"],[56.938099396666665,0.1625989,"point {fill-color: red}"],[271.71979930000003,0.15939309999999998,"point {fill-color: red}"],[205.55865500000002,0.1577032,"point {fill-color: red}"],[108.23185526666667,0.1475897,"point {fill-color: red}"],[135.89114960166668,0.1184686,"point {fill-color: red}"],[169.26750183166666,0.1807258,"point {fill-color: red}"],[258.737404565,0.1196749,"point {fill-color: red}"],[226.63771318833335,0.2045243,"point {fill-color: red}"],[262.84643950000003,0.22819299999999998,"point {fill-color: red}"],[293.13809461166665,0.1507894,"point {fill-color: red}"],[182.968682575,0.22145299999999998,"point {fill-color: red}"],[34.215864195,0.1764276,"point {fill-color: red}"],[295.15763217,1.1933494999999998,"point {fill-color: blue}"],[150.96640485833333,0.199534,"point {fill-color: red}"],[207.39917468166666,0.15333259999999999,"point {fill-color: red}"],[118.16966400166667,0.3628223,"point {fill-color: red}"],[61.919341743333334,0.2657411,"point {fill-color: red}"],[181.75221923666666,0.36230989999999996,"point {fill-color: red}"],[68.03008951,1.3742744999999998,"point {fill-color: blue}"],[147.19282296166668,1.0319287,"point {fill-color: blue}"],[126.44732700833333,1.0576056999999999,"point {fill-color: blue}"],[256.82481317500003,1.0792119,"point {fill-color: blue}"],[82.73822200333333,0.9017957999999999,"point {fill-color: blue}"],[52.60437289,1.1126448,"point {fill-color: blue}"],[173.00318578833333,1.1043473,"point {fill-color: blue}"],[250.74365347666668,1.7999507,"point {fill-color: blue}"],[36.959685485,0.9355346,"point {fill-color: blue}"],[67.08367706166666,1.0031276999999998,"point {fill-color: blue}"],[214.060766645,1.0029251,"point {fill-color: blue}"],[274.704438615,1.5827122,"point {fill-color: blue}"],[297.27548389833333,1.0178862,"point {fill-color: blue}"],[288.79592982500003,1.3866809999999998,"point {fill-color: blue}"],[162.93987092166668,1.0028139,"point {fill-color: blue}"],[88.22329438833333,1.0690956999999999,"point {fill-color: blue}"],[252.26362830833335,1.7261803,"point {fill-color: blue}"],[43.80041404666667,1.135055,"point {fill-color: blue}"],[176.38321055,1.6311339,"point {fill-color: blue}"],[166.31704522333334,1.0412077,"point {fill-color: blue}"],[268.71009862333335,0.9906495,"point {fill-color: blue}"],[140.79376768666668,1.0657199,"point {fill-color: blue}"],[13.77464807,0.2530493,"point {fill-color: red}"],[49.03659896333333,1.1385117,"point {fill-color: blue}"],[227.057888775,1.0844878,"point {fill-color: blue}"],[96.44963171833334,1.1102583,"point {fill-color: blue}"],[28.147415018333334,0.2045428,"point {fill-color: red}"],[33.67353681833333,0.1552207,"point {fill-color: red}"],[194.540898155,1.2751652999999998,"point {fill-color: blue}"],[70.188071235,1.1101413,"point {fill-color: blue}"],[287.72277695,1.1031204,"point {fill-color: blue}"],[117.14435473833333,1.4884906999999998,"point {fill-color: blue}"],[146.13995391666666,1.1824472,"point {fill-color: blue}"],[134.848917125,1.1503706,"point {fill-color: blue}"],[6.198198615,0.1200643,"point {fill-color: red}"],[272.22082465833336,1.8174244,"point {fill-color: blue}"],[272.076837705,0.9099334,"point {fill-color: blue}"],[254.25126218333335,1.4116381999999998,"point {fill-color: blue}"],[3.5562576150000003,0.20559239999999998,"point {fill-color: red}"],[241.24663554666668,1.0376678,"point {fill-color: blue}"],[36.133117425,0.1855914,"point {fill-color: red}"],[127.88293258,1.1324634999999998,"point {fill-color: blue}"],[72.64254592,1.1979092999999998,"point {fill-color: blue}"],[239.33737744166666,1.5689089999999999,"point {fill-color: blue}"],[17.293930013333334,0.1762161,"point {fill-color: red}"],[259.06780773,1.0922498,"point {fill-color: blue}"],[153.27434680333334,1.0615386,"point {fill-color: blue}"],[30.384787253333332,0.1454522,"point {fill-color: red}"],[68.49212499666667,1.4926811,"point {fill-color: blue}"],[74.86667819166667,0.18071959999999998,"point {fill-color: red}"],[116.54087381333333,1.0320007,"point {fill-color: blue}"],[72.80991923833334,1.1393955,"point {fill-color: blue}"],[36.416669435,0.116703,"point {fill-color: red}"],[280.0228915416667,0.9303334,"point {fill-color: blue}"],[175.68889323,1.0084794,"point {fill-color: blue}"],[212.01485436333334,1.0117771,"point {fill-color: blue}"],[193.552334275,0.1980939,"point {fill-color: red}"],[164.46697412833333,1.0523803,"point {fill-color: blue}"],[209.78850674333333,1.4035106,"point {fill-color: blue}"],[204.98935508833333,1.4994581999999999,"point {fill-color: blue}"],[138.03401118666667,0.7861942,"point {fill-color: blue}"],[33.46733526166667,1.1454414,"point {fill-color: blue}"],[172.42431594833334,0.9291914,"point {fill-color: blue}"],[29.078624281666666,0.11532049999999999,"point {fill-color: red}"],[154.01439743666668,1.572323,"point {fill-color: blue}"],[167.65361145166668,1.236796,"point {fill-color: blue}"],[137.95378280833333,1.3952571999999999,"point {fill-color: blue}"],[28.3963188,0.1842472,"point {fill-color: red}"],[297.96170652166666,1.2337631999999998,"point {fill-color: blue}"],[269.0737857266667,1.053574,"point {fill-color: blue}"],[19.886915153333334,0.176781,"point {fill-color: red}"],[130.82699470666668,1.066201,"point {fill-color: blue}"],[284.098351775,1.2528922,"point {fill-color: blue}"],[178.32934875166666,1.6881826,"point {fill-color: blue}"],[85.90160536500001,0.1221119,"point {fill-color: red}"],[97.33458936666666,0.2349732,"point {fill-color: red}"],[21.844685518333335,0.1500091,"point {fill-color: red}"],[289.79008637,1.2539475,"point {fill-color: blue}"],[74.59488160833334,0.1467291,"point {fill-color: red}"],[244.024279135,1.3677051,"point {fill-color: blue}"],[227.55774270333333,1.443601,"point {fill-color: blue}"],[256.68853661833333,1.1714261,"point {fill-color: blue}"],[222.25834139333332,1.2432119,"point {fill-color: blue}"],[238.951189995,0.1795877,"point {fill-color: red}"],[226.38173262666666,1.5045482,"point {fill-color: blue}"],[294.67955841,1.8456959,"point {fill-color: blue}"],[99.18901662166667,0.12764129999999999,"point {fill-color: red}"],[278.0834623466667,1.741083,"point {fill-color: blue}"],[7.885343441666667,0.1728361,"point {fill-color: red}"],[132.85020800833334,0.1453505,"point {fill-color: red}"],[291.766222225,1.1987883,"point {fill-color: blue}"],[60.31657987166667,0.1488767,"point {fill-color: red}"],[204.11946903,1.4050178,"point {fill-color: blue}"],[148.49542282666667,1.2760323999999998,"point {fill-color: blue}"],[205.053457925,1.1489859999999998,"point {fill-color: blue}"],[275.93359578166667,0.2407251,"point {fill-color: red}"],[289.6393913283333,1.1696649,"point {fill-color: blue}"],[252.21820598833335,1.177824,"point {fill-color: blue}"],[113.748031235,0.12556509999999999,"point {fill-color: red}"],[255.34628755666668,1.0744639999999999,"point {fill-color: blue}"],[36.74066517,0.17008189999999998,"point {fill-color: red}"],[65.204398995,0.1508945,"point {fill-color: red}"],[270.79117415,0.1551312,"point {fill-color: red}"],[274.1876650766667,0.23926399999999998,"point {fill-color: red}"],[149.2896922,0.1408284,"point {fill-color: red}"],[225.5523986,1.0925729,"point {fill-color: blue}"],[255.08454178166667,0.11930769999999999,"point {fill-color: red}"],[25.89336998,0.1916564,"point {fill-color: red}"],[53.060380305,0.1555498,"point {fill-color: red}"],[22.638511823333335,0.14676509999999998,"point {fill-color: red}"],[250.4308637,1.0357667,"point {fill-color: blue}"],[296.71878613166666,1.6848896,"point {fill-color: blue}"],[96.12677505666667,0.22341409999999998,"point {fill-color: red}"],[60.99219259833333,0.23702569999999998,"point {fill-color: red}"],[256.08434925333336,1.0510951,"point {fill-color: blue}"],[232.84060575,0.9961182,"point {fill-color: blue}"],[193.792260715,1.079712,"point {fill-color: blue}"],[227.11498299166666,0.13687349999999998,"point {fill-color: red}"],[205.03767552666667,0.17410679999999998,"point {fill-color: red}"],[208.71672330833334,1.4457699,"point {fill-color: blue}"],[49.20881353666667,0.15569439999999998,"point {fill-color: red}"],[26.572466488333333,0.1615075,"point {fill-color: red}"],[253.27134801833333,0.9896400999999999,"point {fill-color: blue}"],[14.550253915,0.22347689999999998,"point {fill-color: red}"],[251.430971375,1.023903,"point {fill-color: blue}"],[241.509584625,1.0028181999999999,"point {fill-color: blue}"],[50.858438168333336,0.1484766,"point {fill-color: red}"],[20.344326083333335,0.1212916,"point {fill-color: red}"],[185.39100854833333,0.9185295,"point {fill-color: blue}"],[191.149828305,0.9071406,"point {fill-color: blue}"],[262.28844481833335,1.1471826,"point {fill-color: blue}"],[237.73719925333333,0.205426,"point {fill-color: red}"],[231.25718542666667,1.2968628,"point {fill-color: blue}"],[257.90647813333334,1.0902935,"point {fill-color: blue}"],[178.65077327333333,1.2822594,"point {fill-color: blue}"],[11.301954238333334,0.1603836,"point {fill-color: red}"],[28.448314925000002,0.11855929999999999,"point {fill-color: red}"],[128.696263445,0.1841691,"point {fill-color: red}"],[163.056119335,0.1605257,"point {fill-color: red}"],[63.73438352666667,0.1544774,"point {fill-color: red}"],[176.20631566666668,1.0941598,"point {fill-color: blue}"],[165.77568253166666,0.16040089999999999,"point {fill-color: red}"],[234.37890517166667,0.1544748,"point {fill-color: red}"],[2.377874686666667,0.1444221,"point {fill-color: red}"],[135.08549020666666,0.37593279999999996,"point {fill-color: red}"],[19.96737894,0.1507161,"point {fill-color: red}"],[231.42486994,1.1990307,"point {fill-color: blue}"],[189.52361637,1.3003206999999999,"point {fill-color: blue}"],[73.909583905,0.1869411,"point {fill-color: red}"],[258.52357387666666,1.096418,"point {fill-color: blue}"],[81.82668368666667,0.21103159999999999,"point {fill-color: red}"],[46.96224949,0.1642302,"point {fill-color: red}"],[236.200729345,1.5489064,"point {fill-color: blue}"],[190.525326435,0.9660270999999999,"point {fill-color: blue}"],[152.95266797833332,1.4237691,"point {fill-color: blue}"],[146.73557154666668,1.0967350999999999,"point {fill-color: blue}"],[81.84676565333334,1.0320433,"point {fill-color: blue}"],[199.31243362,1.1142024,"point {fill-color: blue}"],[162.43113410333333,1.7961365,"point {fill-color: blue}"],[152.42296678166667,1.0829096,"point {fill-color: blue}"],[260.8737841016667,1.3939412,"point {fill-color: blue}"],[83.15109560833334,0.1481192,"point {fill-color: red}"],[4.754378658333334,0.1164431,"point {fill-color: red}"],[230.05418345666666,1.3426825,"point {fill-color: blue}"],[97.95829273833334,0.9152414,"point {fill-color: blue}"],[254.96736956666666,1.2632387,"point {fill-color: blue}"],[71.00711805333333,0.9282672,"point {fill-color: blue}"],[149.44824248333333,1.5421642,"point {fill-color: blue}"],[10.964756038333334,0.12348819999999999,"point {fill-color: red}"],[113.28522191333333,0.9677684999999999,"point {fill-color: blue}"],[111.04770184833333,0.8861671,"point {fill-color: blue}"],[90.77294012,1.0757079,"point {fill-color: blue}"],[162.21318272333335,2.2409909,"point {fill-color: blue}"],[280.9290627583333,1.0695527,"point {fill-color: blue}"],[207.02659628666666,1.3483219,"point {fill-color: blue}"],[201.39565389,1.6691806999999999,"point {fill-color: blue}"],[20.94819503,0.23510419999999999,"point {fill-color: red}"],[239.73240256333335,1.2233766,"point {fill-color: blue}"],[27.619186505000002,0.17290519999999998,"point {fill-color: red}"],[46.553182801666665,0.13262569999999999,"point {fill-color: red}"],[56.34648819833333,1.1173647,"point {fill-color: blue}"],[256.62954586666666,1.3539396,"point {fill-color: blue}"],[62.831499021666666,0.1505993,"point {fill-color: red}"],[275.6406538416667,1.0714481,"point {fill-color: blue}"],[45.538203165,0.2169278,"point {fill-color: red}"],[0.8791947266666666,0.1221535,"point {fill-color: red}"],[272.39604312166665,1.2284472,"point {fill-color: blue}"],[62.40914327,0.18116949999999998,"point {fill-color: red}"],[225.56585732833335,2.1722106,"point {fill-color: blue}"],[99.052418505,1.265823,"point {fill-color: blue}"],[16.871225671666668,0.1770967,"point {fill-color: red}"],[123.94542724333334,1.288328,"point {fill-color: blue}"],[18.31800064666667,0.14440429999999999,"point {fill-color: red}"],[263.280077865,1.3339486,"point {fill-color: blue}"],[21.436637155,0.18642889999999998,"point {fill-color: red}"],[94.34754320333333,1.4543393,"point {fill-color: blue}"],[117.11585929166667,1.579083,"point {fill-color: blue}"],[55.707697638333336,0.159154,"point {fill-color: red}"],[8.979015023333334,0.2419159,"point {fill-color: red}"],[103.80108434666667,1.2468008,"point {fill-color: blue}"],[64.35289877833334,1.4614425,"point {fill-color: blue}"],[60.88839642333333,1.6228939,"point {fill-color: blue}"],[58.26925609166667,1.260687,"point {fill-color: blue}"],[17.559311201666667,1.3031960999999999,"point {fill-color: blue}"],[29.71336813,1.5216835,"point {fill-color: blue}"],[68.59336281,1.3916693,"point {fill-color: blue}"],[49.31385596166667,0.1928222,"point {fill-color: red}"],[87.86838761833333,1.199423,"point {fill-color: blue}"],[20.8073951,0.18657579999999999,"point {fill-color: red}"],[9.728972058333333,0.1812152,"point {fill-color: red}"],[37.32750240166667,0.16315949999999999,"point {fill-color: red}"],[2.435776011666667,0.2014597,"point {fill-color: red}"],[98.49926338333333,1.3995349,"point {fill-color: blue}"],[3.312975311666667,0.1625811,"point {fill-color: red}"],[48.003949785,1.2099632,"point {fill-color: blue}"],[41.49195675666667,0.21326679999999998,"point {fill-color: red}"],[69.02042072500001,0.1828999,"point {fill-color: red}"],[42.921844271666664,1.5311708,"point {fill-color: blue}"],[11.416963851666667,0.4844766,"point {fill-color: red}"],[96.02089690166667,1.3422952,"point {fill-color: blue}"],[77.82533934,1.3206909999999998,"point {fill-color: blue}"],[41.08763415333333,0.21799559999999998,"point {fill-color: red}"],[72.87805657166666,0.1853696,"point {fill-color: red}"],[61.59633903666667,0.20174219999999998,"point {fill-color: red}"],[5.371281215,0.1731836,"point {fill-color: red}"],[103.329508625,1.2128503,"point {fill-color: blue}"],[7.731169465,0.1785291,"point {fill-color: red}"],[12.499948448333333,0.1760302,"point {fill-color: red}"],[27.205666341666667,0.1528651,"point {fill-color: red}"],[4.478275166666667,0.1201376,"point {fill-color: red}"],[90.247879785,1.2016961,"point {fill-color: blue}"],[17.030285595,0.1538317,"point {fill-color: red}"],[44.22267518333334,1.2068003,"point {fill-color: blue}"],[56.06800488166667,0.221573,"point {fill-color: red}"],[112.187806425,1.2410694,"point {fill-color: blue}"],[12.938699230000001,0.1768649,"point {fill-color: red}"],[78.61715465166667,1.2571913,"point {fill-color: blue}"],[57.56987962,0.170096,"point {fill-color: red}"],[107.38113058500001,1.2842683,"point {fill-color: blue}"],[114.92149421,1.4791484,"point {fill-color: blue}"],[85.92122537333333,0.2157517,"point {fill-color: red}"],[106.57325469,1.110651,"point {fill-color: blue}"],[95.087340755,1.6116157,"point {fill-color: blue}"],[58.16652578166667,0.1537105,"point {fill-color: red}"],[110.46850410666667,1.2520277,"point {fill-color: blue}"],[111.87444235166667,1.2963,"point {fill-color: blue}"],[83.92497317333334,1.2266662,"point {fill-color: blue}"],[24.970793208333333,0.1525029,"point {fill-color: red}"],[60.58494389166667,1.1332303,"point {fill-color: blue}"],[110.44966615666667,1.1732307,"point {fill-color: blue}"],[39.15555298833333,0.1905078,"point {fill-color: red}"],[74.14524927333333,1.3574221,"point {fill-color: blue}"],[44.85535599666667,0.2110926,"point {fill-color: red}"],[33.47827237166667,0.2085166,"point {fill-color: red}"],[26.261190368333335,0.22051959999999998,"point {fill-color: red}"],[23.586615993333332,0.151187,"point {fill-color: red}"],[108.43388421166667,1.2534212,"point {fill-color: blue}"],[117.38849716666667,0.1715093,"point {fill-color: red}"],[12.16588387,0.2208625,"point {fill-color: red}"],[290.60522265000003,0.1548714,"point {fill-color: red}"]]);
  options.hAxis = {
    title: 'minutes'
  };
  options.vAxis = {
    title: 'seconds'
  };
  return new google.visualization.ScatterChart(document.getElementById('chart_div_less-frequent-cold-starts-in-google-cloud-functions\/old_scatter'));
});
</script>
<figure>
  <div id="chart_div_less-frequent-cold-starts-in-google-cloud-functions/old_scatter"></div>
  <figcaption class="imageCaption"><h4>Examples of GCF idle instances lifecycle in February 2019</h4></figcaption>
</figure>
<p>This chart plots the response duration (Y-axis) by the interval since the previous requests (X-axis). Each point represents a single request in the dataset. Blue points are cold starts, and red points are responses from warm instances.</p>
<p>The chart has changed in April:</p>




  





<script type="text/javascript">
addChart((data, options) => {
  data.addColumn('number', 'ID');
  data.addColumn('number', 'Value');
  data.addColumn({ 'type': 'string', 'role': 'style' });
  data.addRows([[105.81351983166667,0.0756384,"point {fill-color: red}"],[96.59532183333333,0.1220762,"point {fill-color: red}"],[82.52338920333334,0.030621699999999998,"point {fill-color: red}"],[92.53022229,0.037190299999999996,"point {fill-color: red}"],[49.42072908666667,0.1941552,"point {fill-color: red}"],[25.237515588333334,0.0778483,"point {fill-color: red}"],[113.05062448,0.1585164,"point {fill-color: red}"],[148.71092069333335,0.10826279999999999,"point {fill-color: red}"],[161.80196170166667,0.0821906,"point {fill-color: red}"],[61.79145604833334,0.0837918,"point {fill-color: red}"],[104.493495525,0.0686647,"point {fill-color: red}"],[267.7564307283333,0.1744695,"point {fill-color: red}"],[277.05766174166666,0.0600386,"point {fill-color: red}"],[48.45887947666667,0.06616659999999999,"point {fill-color: red}"],[28.87558840666667,0.10678259999999999,"point {fill-color: red}"],[17.322959245,0.0730233,"point {fill-color: red}"],[50.60919381666667,0.08554289999999999,"point {fill-color: red}"],[29.05453872,0.0727881,"point {fill-color: red}"],[194.61954192333334,0.1215207,"point {fill-color: red}"],[198.44057850333334,0.07615469999999999,"point {fill-color: red}"],[42.062566546666666,0.0598208,"point {fill-color: red}"],[99.04740697,0.1055122,"point {fill-color: red}"],[250.83990179,0.055309,"point {fill-color: red}"],[186.19912747166666,0.0986976,"point {fill-color: red}"],[174.37558632333332,0.1468729,"point {fill-color: red}"],[128.72715744333334,0.0805317,"point {fill-color: red}"],[9.008557521666667,0.0900523,"point {fill-color: red}"],[90.82616570500001,0.031956,"point {fill-color: red}"],[227.20806170666668,0.07217219999999999,"point {fill-color: red}"],[278.90462053833335,0.06417819999999999,"point {fill-color: red}"],[146.42299223333333,0.0648044,"point {fill-color: red}"],[128.49140270166666,0.08831399999999999,"point {fill-color: red}"],[11.661693435,0.0358562,"point {fill-color: red}"],[33.35804462833333,0.1572796,"point {fill-color: red}"],[13.464274306666667,0.08522819999999999,"point {fill-color: red}"],[65.31287226,0.17993779999999998,"point {fill-color: red}"],[89.57953647666668,0.09941599999999999,"point {fill-color: red}"],[89.44180861833334,0.0357816,"point {fill-color: red}"],[7.539028968333334,0.0741588,"point {fill-color: red}"],[19.235294858333333,0.0291206,"point {fill-color: red}"],[14.664884098333333,0.0339854,"point {fill-color: red}"],[4.666798855,6.980028099999999,"point {fill-color: blue}"],[256.80711591333335,0.09284329999999999,"point {fill-color: red}"],[254.14269916833334,0.1315284,"point {fill-color: red}"],[2.810462406666667,0.1026319,"point {fill-color: red}"],[91.52725614166667,0.0323332,"point {fill-color: red}"],[23.913188358333333,0.0982982,"point {fill-color: red}"],[239.61879878333335,0.0689915,"point {fill-color: red}"],[143.94349646666666,0.031173299999999998,"point {fill-color: red}"],[285.88628766666665,0.07416099999999999,"point {fill-color: red}"],[46.74573943833333,0.03362,"point {fill-color: red}"],[185.33489473166668,0.0900142,"point {fill-color: red}"],[131.54717144833333,0.0687513,"point {fill-color: red}"],[0.4547042333333333,0.0433171,"point {fill-color: red}"],[84.41282356833334,0.0642108,"point {fill-color: red}"],[20.79435291,0.0906395,"point {fill-color: red}"],[85.97549925,0.1084324,"point {fill-color: red}"],[28.659735068333333,0.079486,"point {fill-color: red}"],[52.35472729166667,0.1346619,"point {fill-color: red}"],[82.98398236,0.0371929,"point {fill-color: red}"],[1.3526582516666668,0.0913151,"point {fill-color: red}"],[54.75896879333334,0.19168249999999998,"point {fill-color: red}"],[293.9280581483333,0.0962229,"point {fill-color: red}"],[68.94369074333333,0.0978478,"point {fill-color: red}"],[75.60866382,0.07683269999999999,"point {fill-color: red}"],[202.33829910333333,0.081773,"point {fill-color: red}"],[121.30490491,0.18148599999999998,"point {fill-color: red}"],[37.13650702166667,0.1053168,"point {fill-color: red}"],[190.06912669833335,0.061008,"point {fill-color: red}"],[58.60327119166667,0.0332668,"point {fill-color: red}"],[2.8519092383333335,0.059394999999999996,"point {fill-color: red}"],[28.325070738333334,0.27229729999999996,"point {fill-color: red}"],[58.603558103333334,0.037759999999999995,"point {fill-color: red}"],[66.35570874833333,0.050254099999999996,"point {fill-color: red}"],[238.27509040666666,6.5182281,"point {fill-color: blue}"],[200.938462915,0.0299812,"point {fill-color: red}"],[65.55312997333334,0.029154,"point {fill-color: red}"],[190.12284801333334,0.1362965,"point {fill-color: red}"],[118.76757765333333,0.07271559999999999,"point {fill-color: red}"],[6.294241638333333,0.0368406,"point {fill-color: red}"],[167.25433674,0.06291519999999999,"point {fill-color: red}"],[33.319838510000004,0.0922081,"point {fill-color: red}"],[31.099837768333334,0.1234913,"point {fill-color: red}"],[50.805670795000005,0.095215,"point {fill-color: red}"],[74.38146856666667,0.0835172,"point {fill-color: red}"],[69.08117308666667,0.337584,"point {fill-color: red}"],[152.90356230833333,7.0634695999999995,"point {fill-color: blue}"],[281.59012889666667,2.7039446,"point {fill-color: blue}"],[39.28964179,0.09857819999999999,"point {fill-color: red}"],[265.620545325,0.0808412,"point {fill-color: red}"],[232.68172477666667,2.656344,"point {fill-color: blue}"],[69.8941448,6.5705288,"point {fill-color: blue}"],[89.23974576833334,0.09917559999999999,"point {fill-color: red}"],[0.11678145666666667,0.030070399999999997,"point {fill-color: red}"],[45.84248363333334,0.0748359,"point {fill-color: red}"],[62.602150665,0.038969199999999996,"point {fill-color: red}"],[291.207324705,0.030794,"point {fill-color: red}"],[89.33692783000001,0.0452252,"point {fill-color: red}"],[289.40809041833336,0.07129529999999999,"point {fill-color: red}"],[9.629405533333333,0.1372096,"point {fill-color: red}"],[87.47228188333334,0.0468071,"point {fill-color: red}"],[5.910260966666667,0.0381505,"point {fill-color: red}"],[34.517435375,0.0393636,"point {fill-color: red}"],[69.07650127166667,0.033339799999999996,"point {fill-color: red}"],[205.29608801666666,0.1650988,"point {fill-color: red}"],[125.51832391666667,0.031141099999999998,"point {fill-color: red}"],[121.45256027833334,0.0735676,"point {fill-color: red}"],[110.02986198666667,0.059318199999999995,"point {fill-color: red}"],[231.31997054333334,0.1615936,"point {fill-color: red}"],[74.23321002333333,0.049579899999999996,"point {fill-color: red}"],[36.708360685,0.0401793,"point {fill-color: red}"],[168.56717707,0.0903011,"point {fill-color: red}"],[55.10216827833334,0.0286761,"point {fill-color: red}"],[134.78821275833334,0.0861863,"point {fill-color: red}"],[83.05524857166667,0.0576843,"point {fill-color: red}"],[271.048924885,0.1313359,"point {fill-color: red}"],[214.3299092,0.0390118,"point {fill-color: red}"],[106.01154154166667,0.0725082,"point {fill-color: red}"],[159.82971452166666,0.10186469999999999,"point {fill-color: red}"],[43.648102978333334,0.0321774,"point {fill-color: red}"],[275.1847367666667,0.07704799999999999,"point {fill-color: red}"],[170.71661992166668,0.11647769999999999,"point {fill-color: red}"],[73.730849805,0.13615739999999998,"point {fill-color: red}"],[43.474220425,0.0817236,"point {fill-color: red}"],[106.73321274666667,0.2084662,"point {fill-color: red}"],[110.01190759833334,0.11550389999999999,"point {fill-color: red}"],[28.577336391666666,0.06347649999999999,"point {fill-color: red}"],[87.38391993,0.0876777,"point {fill-color: red}"],[53.46984533,0.1550376,"point {fill-color: red}"],[23.178145048333334,0.0749867,"point {fill-color: red}"],[255.28641159,0.0738566,"point {fill-color: red}"],[67.88033142500001,0.030962999999999997,"point {fill-color: red}"],[8.106229165,0.0616259,"point {fill-color: red}"],[43.638769276666665,0.07224599999999999,"point {fill-color: red}"],[218.93553511833335,0.2332842,"point {fill-color: red}"],[119.78381243,0.0319818,"point {fill-color: red}"],[2.3599741583333333,0.0670926,"point {fill-color: red}"],[108.38747658666666,0.11651009999999999,"point {fill-color: red}"],[70.59545352333333,0.0803028,"point {fill-color: red}"],[12.77326927,0.1274871,"point {fill-color: red}"],[64.41290596,0.0681489,"point {fill-color: red}"],[122.67954707166668,0.0865753,"point {fill-color: red}"],[145.48412965333333,0.13749609999999998,"point {fill-color: red}"],[152.58686381666666,0.1228137,"point {fill-color: red}"],[20.062284493333333,0.0743832,"point {fill-color: red}"],[46.22047467666667,0.0348724,"point {fill-color: red}"],[242.49706731,0.073849,"point {fill-color: red}"],[140.83951085833334,0.030178,"point {fill-color: red}"],[39.258356801666665,0.0796812,"point {fill-color: red}"],[21.402082025000002,0.0740144,"point {fill-color: red}"],[26.288567381666667,0.0338959,"point {fill-color: red}"],[129.705454585,0.10233239999999999,"point {fill-color: red}"],[28.582631195,0.1118455,"point {fill-color: red}"],[87.26183483333334,0.0603161,"point {fill-color: red}"],[71.37898861000001,0.0736233,"point {fill-color: red}"],[184.275699095,0.0336586,"point {fill-color: red}"],[89.53028749833334,0.0643133,"point {fill-color: red}"],[111.17367596333334,0.2814329,"point {fill-color: red}"],[88.51148966666666,0.0680727,"point {fill-color: red}"],[91.28765077666667,0.0327099,"point {fill-color: red}"],[90.21515986166666,0.0334349,"point {fill-color: red}"],[50.89858688166667,0.1992212,"point {fill-color: red}"],[112.94588925333333,0.0390063,"point {fill-color: red}"],[109.74530879833334,0.14959509999999998,"point {fill-color: red}"],[207.89940760000002,0.1019056,"point {fill-color: red}"],[83.06639447166667,0.031587,"point {fill-color: red}"],[27.896309353333333,0.07082809999999999,"point {fill-color: red}"],[294.96954953666665,0.0330855,"point {fill-color: red}"],[9.821824455,0.0336211,"point {fill-color: red}"],[108.79262868333333,0.031684199999999996,"point {fill-color: red}"],[57.92157143833334,0.1813988,"point {fill-color: red}"],[81.56983929666667,0.0928734,"point {fill-color: red}"],[197.52008209166667,0.19999229999999998,"point {fill-color: red}"],[4.2182380083333335,0.13001759999999998,"point {fill-color: red}"],[137.65967530166668,0.07651379999999999,"point {fill-color: red}"],[163.81422214666668,0.07674109999999999,"point {fill-color: red}"],[139.857681185,0.0337813,"point {fill-color: red}"],[78.58233377666667,0.10925359999999999,"point {fill-color: red}"],[60.320831160000004,0.1105591,"point {fill-color: red}"],[23.282000846666666,0.0864105,"point {fill-color: red}"],[174.62606653,0.0876941,"point {fill-color: red}"],[60.899337360000004,0.0912352,"point {fill-color: red}"],[111.52586705,0.0971148,"point {fill-color: red}"],[27.709486526666666,0.09527369999999999,"point {fill-color: red}"],[41.14815743333333,0.0693879,"point {fill-color: red}"],[87.26228773333334,0.0631099,"point {fill-color: red}"],[62.65977429666667,0.3168045,"point {fill-color: red}"],[268.7295370416667,0.032436,"point {fill-color: red}"],[234.550810375,0.1829933,"point {fill-color: red}"],[58.31415157166667,0.0716393,"point {fill-color: red}"],[90.18688921333333,0.0731839,"point {fill-color: red}"],[95.03349283333334,0.0384339,"point {fill-color: red}"],[51.45620064166667,0.039131,"point {fill-color: red}"],[41.723880065,0.1274523,"point {fill-color: red}"],[176.55088172833334,0.060309699999999994,"point {fill-color: red}"],[93.56903528833334,0.0623134,"point {fill-color: red}"],[78.03119414833334,0.0296536,"point {fill-color: red}"],[85.15402862333333,0.0334091,"point {fill-color: red}"],[73.89229103833334,0.059188399999999995,"point {fill-color: red}"],[118.84997997,0.063896,"point {fill-color: red}"],[142.58135959166668,6.0924115,"point {fill-color: blue}"],[137.46751286333333,0.0342234,"point {fill-color: red}"],[203.777611675,0.0738489,"point {fill-color: red}"],[236.29880228000002,6.3002049,"point {fill-color: blue}"],[55.065055156666666,0.0334912,"point {fill-color: red}"],[246.47420958666666,0.0662546,"point {fill-color: red}"],[173.37935109166668,0.0356946,"point {fill-color: red}"],[223.66577324666667,0.033801899999999996,"point {fill-color: red}"],[115.40981442666667,0.0327512,"point {fill-color: red}"],[64.06819050666667,0.0329217,"point {fill-color: red}"],[0.9286138583333333,0.0335447,"point {fill-color: red}"],[73.22287179166666,0.043589199999999995,"point {fill-color: red}"],[78.34373812,0.0695628,"point {fill-color: red}"],[94.09217755,0.0567748,"point {fill-color: red}"],[97.37234852666667,0.030153899999999997,"point {fill-color: red}"],[1.698437135,0.061472599999999995,"point {fill-color: red}"],[58.79156018333333,0.1026291,"point {fill-color: red}"],[98.97854800333333,0.0304702,"point {fill-color: red}"],[120.18787187833334,0.0305964,"point {fill-color: red}"],[194.53364270666668,0.1206409,"point {fill-color: red}"],[226.27955292000001,0.117092,"point {fill-color: red}"],[27.553007660000002,0.0607601,"point {fill-color: red}"],[57.903099553333334,0.0933208,"point {fill-color: red}"],[254.07820787166668,0.1338666,"point {fill-color: red}"],[24.944446166666665,0.0319367,"point {fill-color: red}"],[116.57615356333334,0.10434099999999999,"point {fill-color: red}"],[26.112880526666668,0.030970099999999997,"point {fill-color: red}"],[91.77510080333333,0.08029879999999999,"point {fill-color: red}"],[104.97564567333333,0.0717674,"point {fill-color: red}"],[84.70984694333333,0.0701931,"point {fill-color: red}"],[286.99773266166665,0.1295696,"point {fill-color: red}"],[2.486252855,0.0409411,"point {fill-color: red}"],[83.22916459333334,0.1168819,"point {fill-color: red}"],[108.305834295,0.0309126,"point {fill-color: red}"],[12.426002753333334,0.09964179999999999,"point {fill-color: red}"],[99.22689499,0.0663468,"point {fill-color: red}"],[287.7314649016667,0.0690353,"point {fill-color: red}"],[75.18027952166666,0.084482,"point {fill-color: red}"],[21.594071166666666,0.0317514,"point {fill-color: red}"],[232.58802146,0.082247,"point {fill-color: red}"],[259.43584583166665,0.06594169999999999,"point {fill-color: red}"],[35.609715625,0.074631,"point {fill-color: red}"],[232.028917645,0.1386652,"point {fill-color: red}"],[94.04295043,0.0821861,"point {fill-color: red}"],[176.30368083166667,0.0625462,"point {fill-color: red}"],[67.16896412166666,0.0312915,"point {fill-color: red}"],[70.81787476666666,0.5033162,"point {fill-color: red}"],[158.06874901833334,0.059058299999999994,"point {fill-color: red}"],[184.45501218500002,0.1037413,"point {fill-color: red}"],[39.86585632166667,0.0825002,"point {fill-color: red}"],[252.97296709,0.0618168,"point {fill-color: red}"],[130.48510006666666,0.2066771,"point {fill-color: red}"],[53.98758316,0.089819,"point {fill-color: red}"],[78.41117205,0.0321674,"point {fill-color: red}"],[155.796365075,3.444598,"point {fill-color: blue}"],[62.37979291166667,0.0435409,"point {fill-color: red}"],[36.828457155,0.09497219999999999,"point {fill-color: red}"],[107.28061628,0.0323972,"point {fill-color: red}"],[72.95397739,0.09296979999999999,"point {fill-color: red}"],[99.9561877,6.3112324,"point {fill-color: blue}"],[66.28731935666667,0.0317737,"point {fill-color: red}"],[14.904075666666667,0.08947469999999999,"point {fill-color: red}"],[64.45072178666666,0.0308092,"point {fill-color: red}"],[114.92463681,0.1229376,"point {fill-color: red}"],[274.20573200166666,0.1003845,"point {fill-color: red}"],[40.549934633333336,0.0434837,"point {fill-color: red}"],[145.70529646666668,0.0372578,"point {fill-color: red}"],[11.509618235,0.1150585,"point {fill-color: red}"],[68.15808847333334,0.19566599999999998,"point {fill-color: red}"],[85.93793673333333,0.0403523,"point {fill-color: red}"],[42.64762271833333,0.0314727,"point {fill-color: red}"],[243.59423202,0.9177206,"point {fill-color: blue}"],[156.29282349,0.10082819999999999,"point {fill-color: red}"],[116.76705087,0.032882999999999996,"point {fill-color: red}"],[39.818408555,0.063784,"point {fill-color: red}"],[259.16311091833336,0.10827869999999999,"point {fill-color: red}"],[62.44099923,0.0757595,"point {fill-color: red}"],[237.23580263333335,1.3148431999999999,"point {fill-color: blue}"],[299.5862769716667,0.06334,"point {fill-color: red}"],[230.721367455,0.1531416,"point {fill-color: red}"],[102.057633025,0.1124321,"point {fill-color: red}"],[268.4158676033333,0.0656916,"point {fill-color: red}"],[242.78164158166666,2.0297392,"point {fill-color: blue}"],[87.21862182166667,0.1239132,"point {fill-color: red}"],[234.23839701166668,6.368559599999999,"point {fill-color: blue}"],[127.67222922833334,0.15714999999999998,"point {fill-color: red}"],[1.4884433283333334,0.0300426,"point {fill-color: red}"],[88.98313027,0.0810384,"point {fill-color: red}"],[178.53583606,0.0954443,"point {fill-color: red}"],[102.22187692166666,0.0338313,"point {fill-color: red}"],[11.321818361666667,0.066672,"point {fill-color: red}"],[20.171261223333335,0.1244933,"point {fill-color: red}"],[243.64260562166666,0.10686029999999999,"point {fill-color: red}"],[32.73472273666667,0.0861528,"point {fill-color: red}"],[225.19487173000002,0.10225419999999999,"point {fill-color: red}"],[45.27914278,6.4535791,"point {fill-color: blue}"],[223.28896599666666,0.0844892,"point {fill-color: red}"],[39.470883595000004,0.0352363,"point {fill-color: red}"],[283.02859960333336,0.062008799999999996,"point {fill-color: red}"],[34.10210403166667,0.0335551,"point {fill-color: red}"]]);
  options.hAxis = {
    title: 'minutes'
  };
  options.vAxis = {
    title: 'seconds'
  };
  return new google.visualization.ScatterChart(document.getElementById('chart_div_less-frequent-cold-starts-in-google-cloud-functions\/new_scatter'));
});
</script>
<figure>
  <div id="chart_div_less-frequent-cold-starts-in-google-cloud-functions/new_scatter"></div>
  <figcaption class="imageCaption"><h4>Examples of GCF idle instances lifecycle in April 2019</h4></figcaption>
</figure>
<p>It seems that this chart shows fewer points, but that&rsquo;s deceptive: the points are just much denser and almost all of the responses are warm! Even after 4-5 hours of idling, the instance survives most of the time.</p>
<p>The following chart estimates the probability (Y-axis) of a cold start by the interval between two subsequent requests (X-axis). A higher line means higher chances of an instance being recycled:</p>




  







<script type="text/javascript">
addChart((data, options) => {
  data.addColumn('string', 'Time');

  const points = [{"items":[[0,0],[20,0.05434782608695652],[40,0.19753086419753085],[60,0.2768361581920904],[80,0.4968944099378882],[100,0.5942028985507246],[120,0.7128112936964913],[140,0.7128112936964913],[160,0.7128112936964913],[180,0.7128112936964913],[200,0.8402777777777778],[220,0.8402777777777778],[240,0.8573666308628715],[260,0.8573666308628715],[280,0.8573666308628715],[300,0.8573666308628715]],"name":"February 2019"},{"items":[[0,0],[20,0.017605633802816902],[40,0.02041190237905736],[60,0.02041190237905736],[80,0.026619343389529725],[100,0.040280210157618214],[120,0.043941411451398134],[140,0.07396449704142012],[160,0.08843537414965986],[180,0.09942753945359485],[200,0.09942753945359485],[220,0.09942753945359485],[240,0.146006600660066],[260,0.146006600660066],[280,0.15975440222428175],[300,0.15975440222428175]],"name":"April 2019"}];
  const seriesCount = points.length;
  for (var j = 0; j < seriesCount; j++) {
    data.addColumn('number', points[j].name);
  }

  const rows = [];
  for (var i = 0; i < points[0].items.length; i++)
  {
    const item = [points[0].items[i][0]];
    for (var j = 0; j < seriesCount; j++) {
      item.push(points[j].items[i][1]);
    }
    rows.push(item);
  }

  data.addRows(rows);
  console.debug(rows);
  options.lineWidth = 3;
  options.hAxis = {
    title: 'Time since the previous invocation (minutes)'
  };
  options.vAxis = {
    title: 'probability',
    maxValue: 1.0
  };
  options.legend = { position: 'top' };
  options.series = [{"color":"#3572A5"},{"color":"#339933"}];
  return new google.visualization.AreaChart(document.getElementById('chart_div_less-frequent-cold-starts-in-google-cloud-functions\/bytime_interval'));
});
</script>
<figure>
  <div id="chart_div_less-frequent-cold-starts-in-google-cloud-functions/bytime_interval"></div>
  <figcaption class="imageCaption"><h4>Probability of a cold start happening before minute X in February vs. April</h4></figcaption>
</figure>
<p>While the chance of surviving 5 hours of inactivity used to be about 15%, now it&rsquo;s flipped, and an idle instance survives for 5 hours in almost 85% of cases.</p>
<h2 id="what-are-the-other-factors">What Are The Other Factors?</h2>
<p>It looks like larger instances in terms of provisioned memory are more likely to survive than smaller instances:</p>




  







<script type="text/javascript">
addChart((data, options) => {
  data.addColumn('string', 'Time');

  const points = [{"items":[[0,0],[20,0.0821917808219178],[40,0.0802936834335126],[60,0.0802936834335126],[80,0.0802936834335126],[100,0.12101449275362319],[120,0.12101449275362319],[140,0.21212121212121213],[160,0.2124637155297533],[180,0.2124637155297533],[200,0.2124637155297533],[220,0.2124637155297533],[240,0.26444927253197925],[260,0.26444927253197925],[280,0.26444927253197925],[300,0.26444927253197925]],"name":"128 MB"},{"items":[[0,0],[20,0],[40,0.004329004329004329],[60,0.00819672131147541],[80,0.013215859030837005],[100,0.029045643153526972],[120,0.045454545454545456],[140,0.05316606929510155],[160,0.05316606929510155],[180,0.13080886675657918],[200,0.13080886675657918],[220,0.13080886675657918],[240,0.1966800356506239],[260,0.1966800356506239],[280,0.1966800356506239],[300,0.1966800356506239]],"name":"256 MB"},{"items":[[0,0],[20,0],[40,0.00425531914893617],[60,0.00975609756097561],[80,0.012605042016806723],[100,0.02358490566037736],[120,0.037037037037037035],[140,0.03840508806262231],[160,0.03840508806262231],[180,0.06191417937953207],[200,0.06191417937953207],[220,0.06191417937953207],[240,0.06191417937953207],[260,0.06191417937953207],[280,0.15384615384615385],[300,0.15384615384615385]],"name":"512 MB"},{"items":[[0,0],[20,0.0044444444444444444],[40,0.0044444444444444444],[60,0.010275250265528774],[80,0.010275250265528774],[100,0.010275250265528774],[120,0.023809523809523808],[140,0.025],[160,0.05864696374971139],[180,0.05864696374971139],[200,0.06411201179071481],[220,0.06411201179071481],[240,0.10268317853457172],[260,0.10268317853457172],[280,0.1261904761904762],[300,0.1261904761904762]],"name":"1 GB"},{"items":[[0,0],[20,0],[40,0],[60,0.011192616660354416],[80,0.011192616660354416],[100,0.011875968992248062],[120,0.011875968992248062],[140,0.03076923076923077],[160,0.04830188679245283],[180,0.04830188679245283],[200,0.05616483881688268],[220,0.05616483881688268],[240,0.10339281266786597],[260,0.10339281266786597],[280,0.10339281266786597],[300,0.10339281266786597]],"name":"2 GB"}];
  const seriesCount = points.length;
  for (var j = 0; j < seriesCount; j++) {
    data.addColumn('number', points[j].name);
  }

  const rows = [];
  for (var i = 0; i < points[0].items.length; i++)
  {
    const item = [points[0].items[i][0]];
    for (var j = 0; j < seriesCount; j++) {
      item.push(points[j].items[i][1]);
    }
    rows.push(item);
  }

  data.addRows(rows);
  console.debug(rows);
  options.lineWidth = 3;
  options.hAxis = {
    title: 'Time since the previous invocation (minutes)'
  };
  options.vAxis = {
    title: 'probability',
    maxValue: 1.0
  };
  options.legend = { position: 'top' };
  options.series =  null ;
  return new google.visualization.AreaChart(document.getElementById('chart_div_less-frequent-cold-starts-in-google-cloud-functions\/bymemory_interval'));
});
</script>
<figure>
  <div id="chart_div_less-frequent-cold-starts-in-google-cloud-functions/bymemory_interval"></div>
  <figcaption class="imageCaption"><h4>Probability of a cold start by instance size</h4></figcaption>
</figure>
<p>After 5 hours of idling, an instance of 128 MB is 2.5x more likely to be recycled compared to a 2 GB instance.</p>
<p>A similar effect is observed for language runtimes:</p>




  







<script type="text/javascript">
addChart((data, options) => {
  data.addColumn('string', 'Time');

  const points = [{"items":[[0,0],[20,0],[40,0],[60,0.0199203187250996],[80,0.0211864406779661],[100,0.03769291053773812],[120,0.03769291053773812],[140,0.05],[160,0.11067193675889328],[180,0.11067193675889328],[200,0.14390851449275363],[220,0.14390851449275363],[240,0.14533799533799535],[260,0.14533799533799535],[280,0.1917808219178082],[300,0.23333333333333334]],"name":"Python"},{"items":[[0,0],[20,0],[40,0.004347826086956522],[60,0.010575341486090253],[80,0.010575341486090253],[100,0.01282051282051282],[120,0.04827586206896552],[140,0.07206273258904837],[160,0.07206273258904837],[180,0.07206273258904837],[200,0.09540318438623524],[220,0.09540318438623524],[240,0.13775780010576413],[260,0.13775780010576413],[280,0.16354723707664884],[300,0.16354723707664884]],"name":"JavaScript"},{"items":[[0,0],[20,0],[40,0.008620689655172414],[60,0.011042649465678251],[80,0.011042649465678251],[100,0.019982737425784006],[120,0.019982737425784006],[140,0.03115998952605394],[160,0.03115998952605394],[180,0.0573719885161873],[200,0.0573719885161873],[220,0.0573719885161873],[240,0.0573719885161873],[260,0.0573719885161873],[280,0.0573719885161873],[300,0.0573719885161873]],"name":"Go"}];
  const seriesCount = points.length;
  for (var j = 0; j < seriesCount; j++) {
    data.addColumn('number', points[j].name);
  }

  const rows = [];
  for (var i = 0; i < points[0].items.length; i++)
  {
    const item = [points[0].items[i][0]];
    for (var j = 0; j < seriesCount; j++) {
      item.push(points[j].items[i][1]);
    }
    rows.push(item);
  }

  data.addRows(rows);
  console.debug(rows);
  options.lineWidth = 3;
  options.hAxis = {
    title: 'Time since the previous invocation (minutes)'
  };
  options.vAxis = {
    title: 'probability',
    maxValue: 1.0
  };
  options.legend = { position: 'top' };
  options.series = [{"color":"#3572A5"},{"color":"#F1E05A"},{"color":"#339933"}];
  return new google.visualization.AreaChart(document.getElementById('chart_div_less-frequent-cold-starts-in-google-cloud-functions\/bylanguage_interval'));
});
</script>
<figure>
  <div id="chart_div_less-frequent-cold-starts-in-google-cloud-functions/bylanguage_interval"></div>
  <figcaption class="imageCaption"><h4>Probability of a cold start by instance size by language</h4></figcaption>
</figure>
<p>While Python and Javascript are in the same ballpark, Go functions seem to survive longer and more reliably.</p>
<h2 id="practical-takeaway">Practical Takeaway</h2>
<p>Cloud providers are fighting the cold starts on many fronts. Now, it seems that Google found a way to keep warm container instances idle for extended periods without too much overhead for their infrastructure.</p>
<p>With this recent improvement, the practice of having a dummy timer-based trigger to keep function instances warm gets less useful. Which is a good thing: less hand-holding and management means more time on developing useful features! One tiny step closer to a serverless nirvana.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/cold-starts" term="cold-starts" label="Cold Starts" />
                             
                                <category scheme="https://mikhail.io/tags/gcp" term="gcp" label="GCP" />
                             
                                <category scheme="https://mikhail.io/tags/google-cloud-functions" term="google-cloud-functions" label="Google Cloud Functions" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Visualizing (Absence Of) Cold Starts in Binaris]]></title>
            <link href="https://mikhail.io/2019/04/visualizing-absence-of-cold-starts-binaris/"/>
            <id>https://mikhail.io/2019/04/visualizing-absence-of-cold-starts-binaris/</id>
            
            <published>2019-04-03T00:00:00+00:00</published>
            <updated>2019-04-03T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Running visual map experiment on Binaris cloud functions.</blockquote><p>In my previous article, <a href="/2019/03/visualizing-cold-starts/">Visualizing Cold Starts</a>, I visually demonstrated the impact of <a href="/serverless/coldstarts/">serverless cold starts</a> on map loading time. When the serverless function serving the map tiles is cold, users experience a noticeable delay before they can see the selected map view.</p>
<p>I then ran the map loading test on  AWS Lambda, Google Cloud Functions, and Azure Functions. The duration of the perceptible delay varied between cloud providers, ranging from 1.5 to 8 seconds.</p>
<p>Is this effect unfortunately inherent to all Function-as-a-Service offerings? Or is it possible to avoid cold starts altogether?</p>
<h2 id="binaris">Binaris</h2>
<p><a href="https://www.binaris.com/">Binaris</a> is a new player in the serverless functions market. Binaris removes performance barriers to improve developer productivity and enable any application to be built on top of cloud functions.</p>
<p>One of the clear benefits and differentiators from other providers is the absence of cold starts in Binaris functions:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Another great post by Mikhail. Cold start is a deal breaker for many apps, especially anything user facing. Check out <a href="https://twitter.com/gobinaris?ref_src=twsrc%5Etfw">@gobinaris</a> if you&#39;re looking for serverless but want to get rid of cold starts altogether. <a href="https://t.co/3MGH9gEr4c">https://t.co/3MGH9gEr4c</a></p>&mdash; Avner (@avnerbraverman) <a href="https://twitter.com/avnerbraverman/status/1106616656602685440?ref_src=twsrc%5Etfw">March 15, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


<p>In light of this, I decided to rerun my map visualization using Binaris functions to illustrate the difference:</p>




<figure style="cursor: pointer">
    <img src="binaris.png" alt="Map loads from Binaris backend" data-alt="binaris.gif">
    <figcaption><h4>Map loads from Binaris backend (click to play or replay)</h4></figcaption>
</figure>
<p>The initial map loads immediately after redeployment of the Binaris function, so it would cause a cold start for other providers. There’s no noticeable delay for the Binaris function: All map tiles are loaded after 0.4 seconds, similar to the loading times on subsequent zoom levels during the same test.</p>
<p>In case the warm-up happens during the deployment, I reran the experiment after several hours of inactivity. The idle function would have been &ldquo;frozen&rdquo; by other cloud providers,  but I got no cold start for my Binaris function.</p>
<p>Note that the map tiles are stored in AWS S3. For fairness, the S3 resides in the same region where the Binaris function runs (us-east-1). The client ran on a VM in Azure’s US East region.</p>
<h2 id="how-does-binaris-handle-parallel-requests">How does Binaris handle parallel requests?</h2>
<p>The map control fires 12 parallel requests. Similar to the technique used in my initial <a href="/2019/03/visualizing-cold-starts/">Visualizing Cold Starts</a> article, I modified the function to color-code each tile based on the function’s instance ID and to overlay that ID on the image. This way, it’s easy to see how parallel requests were spread over the instances.</p>
<p>As explained in <a href="/2019/03/concurrency-and-isolation-in-serverless-functions/">Serverless Concurrency Models</a>, Binaris functions can run in two concurrency modes: concurrent or exclusive.</p>
<h3 id="concurrent-mode">Concurrent Mode</h3>
<p>When a Binaris function is configured to run in the concurrent mode, the same instance (a.k.a. <em>function unit</em>) can handle up to 10 requests at the same time. For I/O-bound functions, this can lead to more efficient resource utilization and lower cost.</p>
<p>When running in concurrent mode, a single instance may handle the entire color-coded map:</p>
<p><img src="binaris-concurrent-mode.png" alt="Concurrent Mode"></p>
<figcaption><h4>The same function unit served all 12 requests concurrently</h4></figcaption>
<p>The maximum concurrency level is currently 10, so some of the tiles in the picture above must have been processed sequentially. Indeed, sometimes it happens that two instances return the tiles for the same view:</p>
<p><img src="binaris-concurrent-mode-mixed.png" alt="Concurrent Mode Mixed"></p>
<figcaption><h4>Two function units served 12 parallel requests</h4></figcaption>
<p>How is the exclusive mode different?</p>
<h3 id="exclusive-mode">Exclusive Mode</h3>
<p>When a Binaris function is set to run in the exclusive mode, each instance handles a single request at a time. Therefore, multiple instances are needed to process concurrent requests.</p>
<p>As expected, the map gets more colorful for functions in the exclusive mode:</p>
<p><img src="binaris-exclusive-mode.png" alt="Exclusive Mode"></p>
<figcaption><h4>Four function units served 12 parallel requests</h4></figcaption>
<p>However, the number of instances doesn’t get close to the number of tiles: 12 tiles are usually served by 3-5 function units. I suspect this is partially explained by the fact that Binaris functions use HTTP/1.1 protocol, and the browser would only open a limited number of connections to the same host:</p>
<p><img src="binaris-http-connections.png" alt="HTTP Connections to Binaris function"></p>
<figcaption><h4>12 requests are cascaded by Chrome because they connect to the same host</h4></figcaption>
<p>A switch to HTTP/2 would shave some extra latency off the overall map loading time.</p>
<h2 id="conclusions">Conclusions</h2>
<p>The cold start issue is getting less significant for all cloud providers over time. Binaris may be ahead of others in this race.</p>
<p>My simple test does not necessarily prove the complete absence of cold starts in Binaris functions, but it looks optimistic. I look forward to a world where developers would be able to build applications, including user-facing latency-sensitive ones, out of composable managed serverless functions.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/cold-starts" term="cold-starts" label="Cold Starts" />
                             
                                <category scheme="https://mikhail.io/tags/binaris" term="binaris" label="Binaris" />
                             
                                <category scheme="https://mikhail.io/tags/maps" term="maps" label="Maps" />
                             
                                <category scheme="https://mikhail.io/tags/dataviz" term="dataviz" label="Dataviz" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Reducing Cold Start Duration in Azure Functions]]></title>
            <link href="https://mikhail.io/2019/03/reducing-azure-functions-cold-start-time/"/>
            <id>https://mikhail.io/2019/03/reducing-azure-functions-cold-start-time/</id>
            
            <published>2019-03-27T00:00:00+00:00</published>
            <updated>2019-03-27T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>The influence of the deployment method, application insights, and more on Azure Functions cold starts.</blockquote><p>Back in February, I published the first version of <a href="/serverless/coldstarts/azure/">Cold Starts in Azure Functions</a>—the detailed analysis of cold start durations in serverless Azure. The article showed the following numbers for C# and JavaScript functions:</p>





  






<script type="text/javascript">
addChart((data, options) => {
  const points = [["C#",4.940131306,"Median: 4.9s",3.5689002093333335,8.574869002666652,3.5689002093333335,8.574869002666652,"{color: #178600; fill-color: #178600}"],["JavaScript",7.546261614666666,"Median: 7.5s",5.676060145333333,11.99859984266667,5.676060145333333,11.99859984266667,"{color: #F1E05A; fill-color: #F1E05A}"]];

  data.addColumn('string', 'x');
  data.addColumn('number', 'values');
  
  
  data.addColumn({type: 'string', role: 'tooltip'});
  
  
  
  options.series[0].tooltip = true;
  
  data.addColumn({id:'i0', type:'number', role:'interval'});
  data.addColumn({id:'i1', type:'number', role:'interval'});
  data.addColumn({id:'i2', type:'number', role:'interval'});
  data.addColumn({id:'i3', type:'number', role:'interval'});
  data.addColumn({ 'type': 'string', 'role': 'style' });
  data.addRows(points);

  options.lineWidth = 0;
  options.intervals = { 'style':'boxes' };
  options.vAxis = {
    title: 'seconds',
    viewWindow: { min: 0 }
  };

  
  return new google.visualization.ScatterChart(document.getElementById('chart_div_reducing-azure-functions-cold-start-time\/languages'));
  
});
</script>
<figure>
  <div id="chart_div_reducing-azure-functions-cold-start-time/languages"></div>
  <figcaption class="imageCaption"><h4>Typical cold start durations per language (February 2019)</h4></figcaption>
</figure>
<p>Note that I amended the format of the original chart: the range shows the most common 67% of values, and the dot shows the median value. This change makes the visual comparison easier for the rest of today&rsquo;s post.</p>
<p>My numbers triggered several discussions on twitter. In one of them, Jeff Hollan (a program manager on Functions team) responded:</p>

<blockquote class="twitter-tweet" data-conversation="none"><a href="https://twitter.com/x/status/1100474640026132480"></a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>The team collects the stats of cold starts internally, and their numbers were lower than mine. We started an e-mail thread to reconcile the results. I won&rsquo;t publish any messages from the private thread, but I&rsquo;m posting the main findings below.</p>
<h2 id="check-the-deployment-method">Check the deployment method</h2>
<p>For my tests, I&rsquo;ve been using &ldquo;Run from external package&rdquo; deployment method, where the function deployment artifact is stored as a zip file on blob storage. This method is the most friendly for automation and infrastructure-as-code pipelines.</p>
<p>Apparently, it also increases the cold start duration. I believe the situation already improved since my original article, but here are the current numbers from mid-March.</p>
<p>.NET:</p>





  






<script type="text/javascript">
addChart((data, options) => {
  const points = [["No Zip",2.3750335866666665,"Median: 2.4s",2.1337158226666664,2.673247721333334,2.1337158226666664,2.673247721333333,"{color: #008F95; fill-color: #008F95}"],["Local Zip",2.6825472653333335,"Median: 2.7s",2.4944191559999997,2.9249525773333334,2.4944191559999997,2.9249525773333334,"{color: #E9B000; fill-color: #E9B000}"],["External Zip",3.089673053333333,"Median: 3.1s",2.6488860746666667,3.755905496,2.6488860746666667,3.755905496,"{color: #E24E42; fill-color: #E24E42}"]];

  data.addColumn('string', 'x');
  data.addColumn('number', 'values');
  
  
  data.addColumn({type: 'string', role: 'tooltip'});
  
  
  
  options.series[0].tooltip = true;
  
  data.addColumn({id:'i0', type:'number', role:'interval'});
  data.addColumn({id:'i1', type:'number', role:'interval'});
  data.addColumn({id:'i2', type:'number', role:'interval'});
  data.addColumn({id:'i3', type:'number', role:'interval'});
  data.addColumn({ 'type': 'string', 'role': 'style' });
  data.addRows(points);

  options.lineWidth = 0;
  options.intervals = { 'style':'boxes' };
  options.vAxis = {
    title: 'seconds',
    viewWindow: { min: 0 }
  };

  
  return new google.visualization.ScatterChart(document.getElementById('chart_div_reducing-azure-functions-cold-start-time\/deploymentcs'));
  
});
</script>
<figure>
  <div id="chart_div_reducing-azure-functions-cold-start-time/deploymentcs"></div>
  <figcaption class="imageCaption"><h4>Cold start durations per deployment method for C# functions (March 2019)</h4></figcaption>
</figure>
<p>Node.js:</p>





  






<script type="text/javascript">
addChart((data, options) => {
  const points = [["No Zip",3.9645696746666665,"Median: 4.0s",3.0125055733333332,5.792564922666666,3.0125055733333332,5.792564922666666,"{color: #008F95; fill-color: #008F95}"],["Local Zip",4.180329562666666,"Median: 4.2s",3.302554292,5.856474070666667,3.302554292,5.856474070666667,"{color: #E9B000; fill-color: #E9B000}"],["External Zip",5.0813236066666665,"Median: 5.1s",3.8377469759999996,7.825438031999999,3.8377469759999996,7.825438031999999,"{color: #E24E42; fill-color: #E24E42}"]];

  data.addColumn('string', 'x');
  data.addColumn('number', 'values');
  
  
  data.addColumn({type: 'string', role: 'tooltip'});
  
  
  
  options.series[0].tooltip = true;
  
  data.addColumn({id:'i0', type:'number', role:'interval'});
  data.addColumn({id:'i1', type:'number', role:'interval'});
  data.addColumn({id:'i2', type:'number', role:'interval'});
  data.addColumn({id:'i3', type:'number', role:'interval'});
  data.addColumn({ 'type': 'string', 'role': 'style' });
  data.addRows(points);

  options.lineWidth = 0;
  options.intervals = { 'style':'boxes' };
  options.vAxis = {
    title: 'seconds',
    viewWindow: { min: 0 }
  };

  
  return new google.visualization.ScatterChart(document.getElementById('chart_div_reducing-azure-functions-cold-start-time\/deploymentjs'));
  
});
</script>
<figure>
  <div id="chart_div_reducing-azure-functions-cold-start-time/deploymentjs"></div>
  <figcaption class="imageCaption"><h4>Cold start durations per deployment method for JavaScript functions (March 2019)</h4></figcaption>
</figure>
<p>Run-from-external-zip deployment increases the cold start by approximately 1 second.</p>
<h2 id="application-insights">Application Insights</h2>
<p>I always configured my Function Apps to write telemetry to Application Insights. However, this adds a second to the cold start:</p>





  






<script type="text/javascript">
addChart((data, options) => {
  const points = [["No AppInsights",2.6825472653333335,"Median: 2.7s",2.4944191559999997,2.9249525773333334,2.4944191559999997,2.9249525773333334,"{color: #5BA3F1; fill-color: #5BA3F1}"],["With AppInsights",3.488305096666666,"Median: 3.5s",3.0500858440000003,4.097649741333333,3.0500858440000003,4.097649741333333,"{color: #003D8B; fill-color: #003D8B}"]];

  data.addColumn('string', 'x');
  data.addColumn('number', 'values');
  
  
  data.addColumn({type: 'string', role: 'tooltip'});
  
  
  
  options.series[0].tooltip = true;
  
  data.addColumn({id:'i0', type:'number', role:'interval'});
  data.addColumn({id:'i1', type:'number', role:'interval'});
  data.addColumn({id:'i2', type:'number', role:'interval'});
  data.addColumn({id:'i3', type:'number', role:'interval'});
  data.addColumn({ 'type': 'string', 'role': 'style' });
  data.addRows(points);

  options.lineWidth = 0;
  options.intervals = { 'style':'boxes' };
  options.vAxis = {
    title: 'seconds',
    viewWindow: { min: 0 }
  };

  
  return new google.visualization.ScatterChart(document.getElementById('chart_div_reducing-azure-functions-cold-start-time\/appinsights'));
  
});
</script>
<figure>
  <div id="chart_div_reducing-azure-functions-cold-start-time/appinsights"></div>
  <figcaption class="imageCaption"><h4>Cold start durations with and without Application Insights integration</h4></figcaption>
</figure>
<p>I can&rsquo;t really recommend &ldquo;Don&rsquo;t use Application Insights&rdquo; because the telemetry service is vital in most scenarios. Anyway, keep this fact in mind and watch <a href="https://github.com/Azure/azure-functions-host/issues/4183">the corresponding issue</a>.</p>
<h2 id="keep-bugging-the-team">Keep bugging the team</h2>
<p>While you can use the information above, the effect is still going to be limited. Obviously, the power to reduce the cold starts lies within the Azure Functions engineering team.</p>
<p>Coincidence or not, the numbers have already significantly improved since early February, and <a href="https://github.com/Azure/azure-functions-host/issues/4184">more work</a> is <a href="https://github.com/Azure/azure-functions-host/commit/792bb463b4bc48d67570d5b44b69c89b9d43f86d">in progress</a>.</p>
<p>I consider this to be a part of my mission: spotlighting the issues in public gives that nudge to prioritize performance improvements over other backlog items.</p>
<p>Meanwhile, the data in <a href="/serverless/coldstarts/azure/">Cold Starts in Azure Functions</a> and <a href="/serverless/coldstarts/big3/">Comparison of Cold Starts in Serverless Functions across AWS, Azure, and GCP</a> are updated: I&rsquo;m keeping them up-to-date as promised before.</p>
<h4 id="ps">P.S.</h4>

<blockquote class="twitter-tweet" data-conversation="none"><a href="https://twitter.com/x/status/1110905674714669059"></a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                             
                                <category scheme="https://mikhail.io/tags/cold-starts" term="cold-starts" label="Cold Starts" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Concurrency and Isolation in Serverless Functions]]></title>
            <link href="https://mikhail.io/2019/03/concurrency-and-isolation-in-serverless-functions/"/>
            <id>https://mikhail.io/2019/03/concurrency-and-isolation-in-serverless-functions/</id>
            
            <published>2019-03-24T00:00:00+00:00</published>
            <updated>2019-03-24T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Exploring approaches to sharing or isolating resources between multiple executions of the same cloud function and the associated trade-offs.</blockquote><p>Serverless vendors have different approaches when it comes to sharing or isolating resources between multiple executions of the same cloud function. In this article, I&rsquo;ll explore the execution concurrency models of three FaaS offerings and the associated trade-offs.</p>
<h2 id="aws-lambda">AWS Lambda</h2>
<p>AWS Lambda executions are always entirely isolated from each other. Simple enough, right?</p>
<p>A function execution maps 1:1 to a function instance. Each execution runs on a separate host, i.e., a dedicated container with its own instance of the runtime. All the resources of this host (CPU, RAM, scratch disk space) are dedicated solely to this execution.</p>
<p>AWS Lambda spins up as many hosts as needed to process all concurrent requests:</p>
<p><img src="isolated-executions.png" alt="Isolated Executions in AWS Lambda"></p>
<figcaption><h4>3 overlapping executions running on 3 isolated hosts</h4></figcaption>
<p>This model has a benefit of predictability: The resource manager allocates the same amount of resources to each execution. Therefore, the variability between executions is low (with the cold start being a remarkable exception).</p>
<p>Resource allocation has to be preconfigured by selecting the memory limit. CPU cycles are allocated proportionally to RAM. Performance variation might increase for smaller instance sizes: 128 MB instances are empirically known to be less consistent.</p>
<p>In terms of pricing, the executions are independent. The bill consists of two parts: a fixed fee per execution and a variable charge for execution duration, measured in GB-seconds:</p>
<pre tabindex="0"><code>&lt;allocated instance size in GB&gt; * &lt;duration of execution rounded up to 100ms&gt;
</code></pre><p>In the example above, if each execution has 1 GB of allocated RAM and runs for 1 second, the total bill will be 3 GB-seconds.</p>
<p>This model works but is definitely better for some workloads than others.</p>
<h2 id="io-bound-workloads">I/O Bound Workloads</h2>
<p>Typically, enterprise workloads are not very demanding to CPU (not much computation other than serialization and conversions) and RAM (mostly consumed by the runtime and libraries since functions are stateless). Many functions end up calling other resources synchronously over the network: managed cloud services, databases, and web APIs. This means the execution duration is mainly determined by the response time of those resources.</p>
<p>AWS would charge for the full execution time, even if 90% of the time were spent waiting for an external response. In this case, the resources dedicated to the execution are basically wasted.</p>
<p>Ben Kehoe argued for <a href="https://read.acloud.guru/the-need-for-asynchronous-rpc-architecture-in-serverless-systems-ff168f1c8785">the need for asynchronous FaaS call chains in serverless systems</a> almost 2 years ago, but, unfortunately, the situation is still the same.</p>
<h2 id="resource-pooling">Resource Pooling</h2>
<p>In the ideal world of greenfield projects, serverless functions would only be purely transforming inputs to outputs or would make external calls that respond fast and don’t require keeping any state between the requests.</p>
<p>However, in the real world, you’re often required to access traditional SQL databases, such as Postgres or SQL Server. Database connection protocols were not designed for today’s serverless services with hundreds of stateless short-lived single-execution hosts. Connections are expensive to establish. There is a limit of how many of them the database can handle. Therefore, the client should strive to reuse the connections as much as possible.</p>
<p>Good examples of these issues and possible workarounds are shown in these great posts by Jeremy Daly:</p>
<ul>
<li><a href="https://www.jeremydaly.com/reuse-database-connections-aws-lambda/">How To: Reuse Database Connections in AWS Lambda</a></li>
<li><a href="https://www.jeremydaly.com/manage-rds-connections-aws-lambda/">How To: Manage RDS Connections from AWS Lambda Serverless Functions</a></li>
<li><a href="https://github.com/jeremydaly/serverless-mysql">Serverless MySQL</a>.</li>
</ul>
<p>A smaller scale version of the same problem applies to HTTP-based communication: reuse of DNS lookups, TCP connections, etc.</p>
<p>Obviously, it would help if multiple executions of a function shared the pool of database and TCP connections.</p>
<h2 id="azure-functions">Azure Functions</h2>
<p>Azure Functions tried to address this issue by separating the notion of executions and instances. In the Azure world, an instance is a host with dedicated resources (both CPU and RAM allocations are fixed and not currently configurable). Each instance is then capable of running multiple executions at the same time and reusing the resources for all of them.</p>
<p>If we apply the Azure model to the example of the 3 overlapping executions we discussed in the AWS section, we can quickly see how they differ:</p>
<p><img src="concurrent-executions.png" alt="Concurrent Executions in Azure Functions"></p>
<figcaption><h4>3 overlapping executions running on a single host</h4></figcaption>
<p>In this case, the executions share the common pool of resources by running at the same host.</p>
<p>The potential efficiency is also reflected in the reduced bill: You&rsquo;re charged for a merged window of the parallel invocations. If 1 GB of memory is consumed (regardless if 1, 2, or 3 executions are active), then the total bill for 3 executions is 1.5 GB-seconds, where 1.5s is the time between the start of the first execution and the end of the last execution. That&rsquo;s 2x cheaper compared to AWS Lambda.</p>
<p>This calculation might not be accurate for very short executions or many concurrent executions. The minimum time charge is always 100 ms, and 0.125 GB is the minimum memory charge. For that reason, a single execution can’t be less than 0.0125 GB-seconds.</p>
<p>In the example above, sharing resources is lean and beneficial to the end customer. However, it might be problematic in the case of CPU-bound workloads; see <a href="https://blog.binaris.com/from-0-to-1000-instances/#azure-2">Bcrypt/Azure example in How Serverless Providers Scale Queue Processing</a>.</p>
<p>Another benefit of concurrent executions is the potential for reuse of SQL connection pools and HTTP clients. For instance, an Azure Function implemented in C# shares the same .NET process for concurrent executions. Therefore, any static objects are reused automatically.</p>
<h2 id="configuring-concurrency-in-azure-functions">Configuring Concurrency in Azure Functions</h2>
<p>We have now established a clear trade-off between resource allocation efficiency and performance guarantees. Now that we understand the basics of concurrent executions, you might be wondering how Azure Functions decide how many executions to put into a single instance.</p>
<p>The truth is, there isn’t one clear answer. It’s a combination of decisions made by the <a href="https://blog.binaris.com/from-0-to-1000-instances/#azure">Scale Controller</a> and configuration knobs. It also depends on the type of the event triggering the execution.</p>
<p>For Azure Functions that are triggered by queue messages, there are settings <code>batchSize</code> and <code>newBatchThreshold</code>. The maximum number of concurrent executions is then derived from the simple equation <code>batchSize + newBatchThreshold</code>.</p>
<p>Cosmos DB and Event Hubs triggers invoke one function execution per batch of items. Each batch is tied to one partition in the event source. The concurrency is then determined at runtime by the Scale Controller based on factors, such as the number of partitions and the metrics reported from existing instances.</p>
<p>HTTP Functions are the marriage of these two approaches. There is a setting, <code>maxConcurrentRequests</code>, that can be used to limit the concurrency explicitly. It defaults to <code>200</code> concurrent requests, which is quite generous. In practice, it’s not likely to reach that level of execution concurrency unless they are idle for minutes. Most commonly, the Scale Controller creates new instances of the function before the maximum limit is exhausted, which may improve the response time but will incur a higher bill.</p>
<h2 id="binaris">Binaris</h2>
<p>Binaris has support for both modes: AWS Lambda-esque exclusive invocations or concurrent invocations similar to Azure Functions. The user decides which one to use by changing a simple setting in the <code>binaris.yml</code> file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#0550ae">functions</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">  </span><span style="color:#0550ae">NewCreate</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">file</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>function.js<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">entrypoint</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>handler<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">executionModel</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>concurrent<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">runtime</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>node8<span style="color:#fff">
</span></span></span></code></pre></div><p>When <code>executionModel</code> is set to <code>exclusive</code>, the Binaris runtime limits the concurrency of each &ldquo;function unit&rdquo; (container) to one.</p>
<p>Alternatively, when <code>executionModel</code> is set to <code>concurrent</code>, Binaris enables the shared execution model, which allows for re-entrant invocations on the same function unit. The current model allows for up to 10 concurrent invocations within a single unit (the number will be configurable in the future). All re-entrant invocations share the same memory, disk space, and available vCPU.</p>
<p>Binaris charges a flat rate for each millisecond of running time. The rate does not depend on whether a single execution is running or if there are multiple executions running on the same instance.</p>
<p>There is no configuration for instance CPU or RAM allocation. The price of 1 second of execution is equivalent to what AWS charges for 1 GB instances. In the example above, the customer is charged for 1.5 seconds of execution time.</p>
<p>The granularity is always 1 ms, and there is no minimum charge, which may make a big difference for short executions compared to the minimum of 100 ms for AWS and Azure. This makes Binaris very competitive for quick functions: If 10 parallel executions complete within 10 ms, the total charge is 100 times less compared to AWS Lambda.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Sharing compute resources between concurrent executions of serverless functions can be beneficial for I/O-bound workloads. During the periods when some executions are idle waiting for a response from the network, other executions may continue to use the allocated resources. This shareability also applies to assets, such as database connections and libraries loaded into the instance’s memory.</p>
<p>The shared execution model allows for more efficient use of hardware resources, which, in turn, leads to a lower bill.</p>
<p>However, concurrency can also lead to resource contention for CPU-intensive workloads, which might negatively affect the performance of serverless functions. Thus, until cloud providers come up with a perfect method of optimizing concurrency at runtime, it&rsquo;s essential to give the function owner control over the concurrency mode. Given a simple knob, they can make a judgment call between concurrency and isolation.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/aws" term="aws" label="AWS" />
                             
                                <category scheme="https://mikhail.io/tags/binaris" term="binaris" label="Binaris" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                             
                                <category scheme="https://mikhail.io/tags/aws-lambda" term="aws-lambda" label="AWS Lambda" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Visualizing Cold Starts]]></title>
            <link href="https://mikhail.io/2019/03/visualizing-cold-starts/"/>
            <id>https://mikhail.io/2019/03/visualizing-cold-starts/</id>
            
            <published>2019-03-14T00:00:00+00:00</published>
            <updated>2019-03-14T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Serverless cold starts illustrated with animated GIFs.</blockquote><p>I <a href="/serverless/coldstarts/">wrote a lot</a> about cold starts of serverless functions. The articles are full of charts and numbers which are hopefully useful but might be hard to internalize. I decided to come up with a way to represent colds starts visually.</p>
<p>I created HTTP functions that serve geographic maps (map credit <a href="https://www.openstreetmap.org">Open Street Map</a>). The map is a combination of small square tiles; each tile is 256 by 256 pixels. My selected map view consists of 12 tiles, so 12 requests are made to the serverless function to load a single view.</p>
<p>During each experiment, I load the map and then zoom-in three times. The very first view hits  the function in a cold state. Subsequently, the zoomed views are loaded from the warm function. There is a timer next to the map which shows the total time elapsed since the beginning until the last tile arrives.</p>
<h2 id="cold-starts-visualized">Cold Starts Visualized</h2>
<p>All functions are implemented in Node.js and run in the geographically closest region to me (West Europe).</p>
<p>The functions load map tiles from the cloud storage (AWS S3, Google Cloud Storage, and Azure Blob Storage). So the duration is increased by loading SDK at startup and the storage read latency.</p>
<h3 id="aws-lambda">AWS Lambda</h3>
<p>The following GIF movie is a recording of the experiment against an AWS Lambda:</p>




<figure style="cursor: pointer">
    <img src="aws.png" alt="Map loads from AWS Lambda backend" data-alt="aws.gif">
    <figcaption><h4>Map loads from AWS Lambda backend (click to play or replay)</h4></figcaption>
</figure>
<p>The cold view took 1.9 seconds to load, while the warm views were between 200 and 600 milliseconds. The distinction is fairly visible but not extremely annoying: the first load feels like a small network glitch.</p>
<h3 id="google-cloud-functions">Google Cloud Functions</h3>
<p>This GIF shows the experiment against a Google Cloud Function:</p>




<figure style="cursor: pointer">
    <img src="gcp.png" alt="Map loads from Google Cloud Functions backend" data-alt="gcp.gif">
    <figcaption><h4>Map loads from Google Cloud Functions backend (click to play or replay)</h4></figcaption>
</figure>
<p>Loading the initial view took an extra second compared to AWS. It&rsquo;s not a dealbreaker, but the delay of 3 seconds is often quoted as psychologically important.</p>
<p>The tiles seem to appear more gradually; read more on that below.</p>
<h3 id="azure-functions">Azure Functions</h3>
<p>Here is another movie, this time for an Azure Function:</p>




<figure style="cursor: pointer">
    <img src="azure.png" alt="Map loads from Azure Functions backend" data-alt="azure.gif">
    <figcaption><h4>Map loads from Azure Functions backend (click to play or replay)</h4></figcaption>
</figure>
<p>As expected from my previously published measurements, Azure Functions take significantly longer to start. A user has enough time to start wondering whether the map is broken.</p>
<p>I expect better results from Functions implemented in C#, but that would not be an apples-to-apples comparison.</p>
<h2 id="how-do-providers-handle-parallel-requests">How do providers handle parallel requests?</h2>
<p>The map control fires 12 requests in parallel. All functions talk HTTP/2, so the old limit on the number of connections does not apply. Let&rsquo;s compare how those parallel requests are processed.</p>
<h3 id="aws-lambda-1">AWS Lambda</h3>
<p>Each instance of AWS Lambda can handle a single request at a time. So, instead of hitting just one cold start, we hit 12 cold starts in parallel. To illustrate this, I’ve modified the function to color-code each tile based on the Lambda instance ID and to print that ID on the image:</p>
<p><img src="aws-colored.png" alt="AWS Map Colored"></p>
<figcaption><h4>AWS Lambda provisioned 12 instances to serve 12 requests in parallel</h4></figcaption>
<p>Effectively, the measured durations represent the roundtrip time for <em>the slowest out of 12 parallel requests</em>. It&rsquo;s not the average or median duration of the cold start.</p>
<h3 id="google-cloud-functions-1">Google Cloud Functions</h3>
<p>Google uses the same one-execution-per-instance model, so I expected GCP Cloud Functions to behave precisely the same as AWS Lambda. However, I was wrong:</p>
<p><img src="gcp-colored.png" alt="GCP Map Colored"></p>
<figcaption><h4>Google Cloud Function provisioned 3 instances to serve 12 parallel requests</h4></figcaption>
<p>Only three instances were created, and two of them handled multiple requests. It looks like GCP serializes the incoming requests and spreads them through a limited set of instances.</p>
<h3 id="azure-functions-1">Azure Functions</h3>
<p>Azure Functions have a different design: each instance of a function can handle multiple parallel requests at the same time. Thus, in theory, all 12 tiles could be served by the first instance created after the cold start.</p>
<p>In practice, multiple instances are created. The picture looks very similar to GCP:</p>
<p><img src="azure-colored.png" alt="Azure Map Colored"></p>
<figcaption><h4>Azure Function provisioned 4 instances to serve 12 parallel requests</h4></figcaption>
<p>There were four active instances, but the same one handled 9 out of 12 requests. This behavior seems to be quite consistent between multiple runs.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I hope that these visualizations are useful to get a better feel of the cold starts in serverless functions.</p>
<p>However, they are just examples. Don&rsquo;t treat the numbers as exact statistics for a given cloud provider. If you are curious, you can learn more in <a href="/serverless/coldstarts/">Serverless Cold Starts</a> series of articles.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/cold-starts" term="cold-starts" label="Cold Starts" />
                             
                                <category scheme="https://mikhail.io/tags/aws-lambda" term="aws-lambda" label="AWS Lambda" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                             
                                <category scheme="https://mikhail.io/tags/google-cloud-functions" term="google-cloud-functions" label="Google Cloud Functions" />
                             
                                <category scheme="https://mikhail.io/tags/aws" term="aws" label="AWS" />
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/gcp" term="gcp" label="GCP" />
                             
                                <category scheme="https://mikhail.io/tags/maps" term="maps" label="Maps" />
                             
                                <category scheme="https://mikhail.io/tags/dataviz" term="dataviz" label="Dataviz" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Evergreen Serverless Performance Reviews]]></title>
            <link href="https://mikhail.io/2019/02/evergreen-serverless-performance-reviews/"/>
            <id>https://mikhail.io/2019/02/evergreen-serverless-performance-reviews/</id>
            
            <published>2019-02-24T00:00:00+00:00</published>
            <updated>2019-02-24T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Automating cloud infrastructure to delivery always-up-to-date performance metrics.</blockquote><p>In the past 6 months I published several blog posts under the same theme of comparing the serverless services of the top cloud providers in terms of their performance and scalability properties:</p>
<ul>
<li><a href="https://mikhail.io/2018/08/serverless-cold-start-war/">Serverless: Cold Start War</a></li>
<li><a href="https://mikhail.io/2018/11/from-0-to-1000-instances-how-serverless-providers-scale-queue-processing/">From 0 to 1000 Instances: How Serverless Providers Scale Queue Processing</a></li>
<li><a href="https://mikhail.io/2019/serverless-at-scale-serving-stackoverflow-like-traffic/">Serverless at Scale: Serving StackOverflow-like Traffic</a></li>
</ul>
<p>I received some very positive feedback on those posts (readers are fantastic!). On top of that, people always had great suggestions about improving and extending the contents of those articles. I&rsquo;ve been thinking a lot about them.</p>
<h2 id="limitations-of-a-blog-post">Limitations of a blog post</h2>
<p>However, the format of a blog post is limited in several important ways:</p>
<ul>
<li>
<p><strong>Read-only</strong>. Once it&rsquo;s published, it&rsquo;s published. Except for some typos, I never make changes or extend the past articles. Despite the lack of technical limitations, re-writing blog posts doesn&rsquo;t seem to be a part of the genre. If I want to add another cloud, do I change the old post or create an entirely new one?</p>
</li>
<li>
<p><strong>Point-in-time</strong>. All those articles are heavily data-driven: numbers, charts, comparisons. The value of the data decreases as time goes. Readers can&rsquo;t trust the 2-years-old numbers in the ever-changing world of the cloud.</p>
</li>
<li>
<p><strong>Wall of text</strong>. A blog post is just a chunk of text interrupted by images and charts. People read it from top to bottom. There&rsquo;s no other structure to it. So, I have to come up with the best sequence of material to present. I have to balance the length of the post to give enough insight but not to be too long and tedious. There&rsquo;s no good way to summarize something and then send curious readers to the details.</p>
</li>
<li>
<p><strong>Same for everyone</strong>. Readers might have different backgrounds. Somebody only cares about one cloud provider or one language because that&rsquo;s what they work with. Others look for brief comparison and industry-wide trends. Nonetheless, they all have to read the same text.</p>
</li>
<li>
<p><strong>Not reproducible</strong>. Because of the previous points, there is no real incentive to make the experiments reproducible. It&rsquo;s enough to run it once, publish the results, and forget. However, this means that the effort is lost, and there is no open code that others could use.</p>
</li>
</ul>
<p>I decided to have a shot at addressing these issues.</p>
<h2 id="how-to-solve-these-problems">How to solve these problems</h2>
<p>I believe I can solve the issues inherent to the format of the blog by doing the following steps:</p>
<ul>
<li>
<p><strong>Automate the experiments</strong>. Provision the required infrastructure, run the workload, collect metrics, record the data, aggregate them, and publish the charts. All programmatically, without significant human intervention.</p>
</li>
<li>
<p><strong>Run more often</strong>. Re-do the experiments every month or so. Compare the results over time, detect any trends.</p>
</li>
<li>
<p><strong>Website not blog</strong>. Publish the results as a set of pages with different perspectives on the related data. Make it compelling for multiple types of audience.</p>
</li>
<li>
<p><strong>Keep it up-to-date</strong>. Make sure that people can trust the data as being actual, not old or obsolete.</p>
</li>
<li>
<p><strong>Open everything</strong>. Publish the code behind the experiments, the raw data, the aggregated data. Enable people to find bugs, flaws, and suggest improvements—if they want to.</p>
</li>
</ul>
<h2 id="whats-available-today">What&rsquo;s available today</h2>
<p>I&rsquo;ve completed all the suggestions for the topic of <a href="/serverless/coldstarts/">Cold Starts</a>:</p>
<a href="/serverless/coldstarts">
<figure >
    
        <img src="coldstarts-screen.jpg"
            alt="Cold Starts landing page screenshot"
             />
        
    
    <figcaption>
        <h4>Cold Starts landing page screenshot</h4>
    </figcaption>
    
</figure>
</a>
<p>Two dozens of cloud functions span across three providers. The experiments run for a week and then the results are saved as JSON files. A script aggregates the data and produces charts in several seconds.</p>
<p>I commit to running this experiment and updating the data at least once in 2 months for as long as it would make sense, to my judgment.</p>
<p>The corresponding section of the website consists of 16 pages with different focus and level of details.</p>
<p>All the code and data are <a href="/serverless/open/">open</a>.</p>
<p>I invite you to give it a try, <a href="https://www.twitter.com/MikhailShilkov">follow me on Twitter</a> and <a href="https://github.com/mikhailshilkov/mikhailio-hugo/issues/5">leave the feedback on GitHub</a>.</p>
<p><a href="/serverless/coldstarts/">Cold Starts in Serverless Functions</a>.</p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/cold-starts" term="cold-starts" label="Cold Starts" />
                            
                        
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[From YAML to TypeScript: Developer's View on Cloud Automation]]></title>
            <link href="https://mikhail.io/2019/02/from-yaml-to-typescript-developers-view-on-cloud-automation/"/>
            <id>https://mikhail.io/2019/02/from-yaml-to-typescript-developers-view-on-cloud-automation/</id>
            
            <published>2019-02-14T00:00:00+00:00</published>
            <updated>2019-02-14T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>An expressive and powerful way to design cloud-native and serverless infrastructure</blockquote><p>The rise of managed cloud services, cloud-native and serverless applications brings both new possibilities and challenges. More and more practices from software development process like version control, code review, continuous integration, and automated testing are applied to the cloud infrastructure automation.</p>
<p>Most existing tools suggest defining infrastructure in text-based markup formats, YAML being the favorite. In this article, I&rsquo;m making a case for using real programming languages like TypeScript instead. Such a change makes even more software development practices applicable to the infrastructure realm.</p>
<h2 id="sample-application">Sample Application</h2>
<p>It&rsquo;s easier to make a case given a specific example. For this essay, I define a URL Shortener application, a basic clone of tinyurl.com or bit.ly. There is an administrative page where one can define short aliases for long URLs:</p>
<p><img src="url-shortener.png" alt="URL Shortener sample app"></p>
<figcaption><h4><h4>URL Shortener sample app</h4></figcaption>
<p>Now, whenever a visitor goes to the base URL of the application + an existing alias, they get redirected to the full URL.</p>
<p>This app is simple to describe but involves enough moving parts to be representative of some real-world issues. As a bonus, there are many existing implementations on the web to compare with.</p>
<h2 id="serverless-url-shortener">Serverless URL Shortener</h2>
<p>I&rsquo;m a big proponent of the serverless architecture: the style of cloud applications being a combination of serverless functions and managed cloud services. They are fast to develop, effortless to run and cost pennies unless the application gets lots of users. However, even serverless applications have to deal with infrastructure, like databases, queues, and other sources of events and destinations of data.</p>
<p>My examples are going to use Amazon AWS, but this could be Microsoft Azure or Google Cloud Platform too.</p>
<p>So, the gist is to store URLs with short names as key-value pairs in Amazon DynamoDB and use AWS Lambdas to run the application code. Here is the initial sketch:</p>
<p><img src="lambda-dynamodb.png" alt="URL Shortener with AWS Lambda and DynamoDB"></p>
<figcaption><h4>URL Shortener with AWS Lambda and DynamoDB</h4></h4></figcaption>
<p>The Lambda at the top receives an event when somebody decides to add a new URL. It extracts the name and the URL from the request and saves them as an item in the DynamoDB table.</p>
<p>The Lambda at the bottom is called whenever a user navigates to a short URL. The code reads the full URL based on the requested path and returns a 301 response with the corresponding location.</p>
<p>Here is the implementation of the <code>Open URL</code> Lambda in JavaScript:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">aws</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">require</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#39;aws-sdk&#39;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">table</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">DynamoDB</span><span style="color:#1f2328">.</span><span style="color:#1f2328">DocumentClient</span><span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">exports</span><span style="color:#1f2328">.</span><span style="color:#1f2328">handler</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">event</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">const</span> <span style="color:#1f2328">name</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">event</span><span style="color:#1f2328">.</span><span style="color:#1f2328">path</span><span style="color:#1f2328">.</span><span style="color:#1f2328">substring</span><span style="color:#1f2328">(</span><span style="color:#0550ae">1</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">const</span> <span style="color:#1f2328">params</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">TableName</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;urls&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">Key</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#0a3069">&#34;name&#34;</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">name</span> <span style="color:#1f2328">}</span> <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">const</span> <span style="color:#1f2328">value</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">await</span> <span style="color:#1f2328">table</span><span style="color:#1f2328">.</span><span style="color:#1f2328">get</span><span style="color:#1f2328">(</span><span style="color:#1f2328">params</span><span style="color:#1f2328">).</span><span style="color:#1f2328">promise</span><span style="color:#1f2328">();</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">const</span> <span style="color:#1f2328">url</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">value</span> <span style="color:#0550ae">&amp;&amp;</span> <span style="color:#1f2328">value</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Item</span> <span style="color:#0550ae">&amp;&amp;</span> <span style="color:#1f2328">value</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Item</span><span style="color:#1f2328">.</span><span style="color:#1f2328">url</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cf222e">return</span> <span style="color:#1f2328">url</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">?</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">statusCode</span><span style="color:#0550ae">:</span> <span style="color:#0550ae">301</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">body</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">headers</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#0a3069">&#34;Location&#34;</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">url</span> <span style="color:#1f2328">}</span> <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">statusCode</span><span style="color:#0550ae">:</span> <span style="color:#0550ae">404</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">body</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">name</span> <span style="color:#0550ae">+</span> <span style="color:#0a3069">&#34; not found&#34;</span> <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">};</span>
</span></span></code></pre></div><p>That&rsquo;s 11 lines of code. I&rsquo;ll skip the implementation of <code>Add URL</code> function because it&rsquo;s very similar. Considering a third function to list the existing URLs for UI, we might end up with 30-40 lines of JavaScript in total.</p>
<p>So, how do we deploy the application?</p>
<p>Well, before we do that, we should realize that the above picture was an over-simplification:</p>
<ul>
<li>AWS Lambda can&rsquo;t handle HTTP requests directly, so we need to add AWS API Gateway in front of it.</li>
<li>We also need to serve some static files for the UI, which we&rsquo;ll put into AWS S3 and proxy it with the same API Gateway.</li>
</ul>
<p>Here is the updated diagram:</p>
<p><img src="apigateway-lambda-dynamodb-s3.png" alt="API Gateway, Lambda, DynamoDB, and S3"></p>
<figcaption><h4>API Gateway, Lambda, DynamoDB, and S3</h4></figcaption>
<p>This is a viable design, but the details are even more complicated:</p>
<ul>
<li>API Gateway is a complex beast which needs Stages, Deployments, and REST Endpoints to be appropriately configured.</li>
<li>Permissions and Policies need to be defined so that API Gateway could call Lambda and Lambda could access DynamoDB.</li>
<li>Static Files should go to S3 Bucket Objects.</li>
</ul>
<p>So, the actual setup involves a couple of dozen objects to be configured in AWS:</p>
<p><img src="apigateway-lambda-dynamodb-s3-details.png" alt="All cloud resources to be provisioned"></p>
<figcaption><h4>All cloud resources to be provisioned</h4></figcaption>
<p>How do we approach this task?</p>
<h2 id="options-to-provision-the-infrastructure">Options to Provision the Infrastructure</h2>
<p>There are many options to provision a cloud application, each one has its trade-offs. Let&rsquo;s quickly go through the list of possibilities to understand the landscape.</p>
<h2 id="aws-web-console">AWS Web Console</h2>
<p>AWS, like any other cloud, has a <a href="https://console.aws.amazon.com">web user interface</a> to configure its resources:</p>
<p><img src="aws-web-console.png" alt="AWS Web Console"></p>
<figcaption><h4>AWS Web Console</h4></figcaption>
<p>That&rsquo;s a decent place to start—good for experimenting, figuring out the available options, following the tutorials, i.e., for exploration.</p>
<p>However, it doesn&rsquo;t suit particularly well for long-lived ever-changing applications developed in teams. A manually clicked deployment is pretty hard to reproduce in the exact manner, which becomes a maintainability issue pretty fast.</p>
<h2 id="aws-command-line-interface">AWS Command Line Interface</h2>
<p>The <a href="https://aws.amazon.com/cli/">AWS Command Line Interface</a> (CLI) is a unified tool to manage all AWS services from a command prompt. You write the calls like</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-console" data-lang="console"><span style="display:flex;"><span><span style="color:#1f2328">aws apigateway create-rest-api --name &#39;My First API&#39; --description &#39;This is my first API&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"></span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#1f2328">aws apigateway create-stage --rest-api-id 1234123412 --stage-name &#39;dev&#39; --description &#39;Development stage&#39; --deployment-id a1b2c3
</span></span></span></code></pre></div><p>The initial experience might not be as smooth as clicking buttons in the browser, but the huge benefit is that you can <em>reuse</em> commands that you once wrote. You can build scripts by combining many commands into cohesive scenarios. So, your colleague can benefit from the same script that you created. You can provision multiple environments by parameterizing the scripts.</p>
<p>Frankly speaking, I&rsquo;ve never done that for several reasons:</p>
<ul>
<li>CLI scripts feel too imperative to me. I have to describe &ldquo;how&rdquo; to do things, not &ldquo;what&rdquo; I want to get in the end.</li>
<li>There seems to be no good story for updating existing resources. Do I write small delta scripts for each change? Do I have to keep them forever and run the full suite every time I need a new environment?</li>
<li>If a failure occurs mid-way through the script, I need to manually repair everything to a consistent state. This gets messy real quick, and I have no desire to exercise this process, especially in production.</li>
</ul>
<p>To overcome such limitations, the notion of the <strong>Desired State Configuration</strong> (DSC) was invented. Under this paradigm, one describes the desired layout of the infrastructure, and then the tooling takes care of either provisioning it from scratch or applying the required changes to an existing environment.</p>
<p>Which tool provides DSC model for AWS? There are legions.</p>
<h2 id="aws-cloudformation">AWS CloudFormation</h2>
<p><a href="https://aws.amazon.com/cloudformation/">AWS CloudFormation</a> is the first-party tool for Desired State Configuration management from Amazon. CloudFormation templates use YAML to describe all the infrastructure resources of AWS.</p>
<p>Here is a snippet from <a href="https://aws.amazon.com/blogs/compute/build-a-serverless-private-url-shortener/">a private URL shortener example</a> kindly provided at AWS blog:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#0550ae">Resources</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">  </span><span style="color:#0550ae">S3BucketForURLs</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">Type</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#34;AWS::S3::Bucket&#34;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">DeletionPolicy</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>Delete<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">Properties</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">      </span><span style="color:#0550ae">BucketName</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>!If [ &#34;CreateNewBucket&#34;, !Ref &#34;AWS::NoValue&#34;, !Ref S3BucketName ]<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">      </span><span style="color:#0550ae">WebsiteConfiguration</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">        </span><span style="color:#0550ae">IndexDocument</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#34;index.html&#34;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">      </span><span style="color:#0550ae">LifecycleConfiguration</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">        </span><span style="color:#0550ae">Rules</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">          </span>-<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">            </span><span style="color:#0550ae">Id</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>DisposeShortUrls<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">            </span><span style="color:#0550ae">ExpirationInDays</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>!Ref URLExpiration<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">            </span><span style="color:#0550ae">Prefix</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#0a3069">&#34;u&#34;</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">            </span><span style="color:#0550ae">Status</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>Enabled<span style="color:#fff">
</span></span></span></code></pre></div><p>This is just a very short fragment: the complete example consists of 317 lines YAML. That&rsquo;s an order of magnitude more than the actual JavaScript code that we have in the application!</p>
<p>CloudFormation is a powerful tool, but it demands quite some learning to be done to master it. Moreover, it&rsquo;s specific to AWS: you won&rsquo;t be able to transfer the skill to other cloud providers.</p>
<p>Wouldn&rsquo;t it be great if there was a universal DSC format? Meet Terraform.</p>
<h2 id="terraform">Terraform</h2>
<p><a href="https://www.terraform.io/">HashiCorp Terraform</a> is an open source tool to define infrastructure in declarative configuration files. It has a pluggable architecture, so the tool supports all major clouds and even hybrid scenarios.</p>
<p>The custom text-based Terraform <code>.tf</code> format is used to define the configurations. The templating language is quite powerful, and once you learn it, you can use it for different cloud providers.</p>
<p>Here is a snippet from <a href="https://github.com/jamesridgway/aws-lambda-short-url">AWS Lambda Short URL Generator</a> example:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-tf" data-lang="tf"><span style="display:flex;"><span><span style="color:#cf222e">resource</span> <span style="color:#0a3069">&#34;aws_api_gateway_rest_api&#34;</span> <span style="color:#0a3069">&#34;short_urls_api_gateway&#34;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">name</span>        <span style="color:#0550ae">=</span> <span style="color:#0a3069">&#34;Short URLs API&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">description</span> <span style="color:#0550ae">=</span> <span style="color:#0a3069">&#34;API for managing short URLs.&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">resource</span> <span style="color:#0a3069">&#34;aws_api_gateway_usage_plan&#34;</span> <span style="color:#0a3069">&#34;short_urls_admin_api_key_usage_plan&#34;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">name</span>         <span style="color:#0550ae">=</span> <span style="color:#0a3069">&#34;Short URLs admin API key usage plan&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">description</span>  <span style="color:#0550ae">=</span> <span style="color:#0a3069">&#34;Usage plan for the admin API key for Short URLS.&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">api_stages</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">api_id</span> <span style="color:#0550ae">=</span> <span style="color:#0a3069">&#34;</span><span style="color:#0a3069">${</span><span style="color:#1f2328">aws_api_gateway_rest_api</span><span style="color:#1f2328">.</span><span style="color:#1f2328">short_urls_api_gateway</span><span style="color:#1f2328">.</span><span style="color:#1f2328">id</span><span style="color:#0a3069">}</span><span style="color:#0a3069">&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">stage</span>  <span style="color:#0550ae">=</span> <span style="color:#0a3069">&#34;</span><span style="color:#0a3069">${</span><span style="color:#1f2328">aws_api_gateway_deployment</span><span style="color:#1f2328">.</span><span style="color:#1f2328">short_url_api_deployment</span><span style="color:#1f2328">.</span><span style="color:#1f2328">stage_name</span><span style="color:#0a3069">}</span><span style="color:#0a3069">&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>This time, the complete example is around 450 lines of textual templates. Are there ways to reduce the size of the infrastructure definition?</p>
<p>Yes, by raising the level of abstraction. It&rsquo;s possible with Terraform&rsquo;s modules, or by using other, more specialized tools.</p>
<h2 id="serverless-framework-and-sam">Serverless Framework and SAM</h2>
<p><a href="https://serverless.com/">The Serverless Framework</a> is an infrastructure management tool focused on serverless applications. It works across cloud providers (AWS support is the strongest though) and only exposes features related to building applications with cloud functions.</p>
<p>The benefit is that it&rsquo;s much more concise. Once again, the tool is using YAML to define the templates, here is the snippet from <a href="https://github.com/danielireson/serverless-url-shortener">Serverless URL Shortener</a> example:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#0550ae">functions</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">  </span><span style="color:#0550ae">store</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">handler</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>api/store.handle<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">    </span><span style="color:#0550ae">events</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">      </span>- <span style="color:#0550ae">http</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">          </span><span style="color:#0550ae">path</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>/<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">          </span><span style="color:#0550ae">method</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>post<span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">          </span><span style="color:#0550ae">cors</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span><span style="color:#cf222e">true</span><span style="color:#fff">
</span></span></span></code></pre></div><p>The domain-specific language yields a shorter definition: this example has 45 lines of YAML + 123 lines of JavaScript functions.</p>
<p>However, the conciseness has a flip side: as soon as you veer outside of the fairly &ldquo;thin&rdquo; golden path—the cloud functions and an incomplete list of event sources—you have to fall back to more generic tools like CloudFormation. As soon as your landscape includes lower-level infrastructure work or some container-based components, you&rsquo;re stuck using multiple config languages and tools again.</p>
<p>Amazon&rsquo;s <a href="https://docs.aws.amazon.com/serverless-application-model/index.html">AWS Serverless Application Model</a> (SAM) looks very similar to the Serverless Framework but tailored to be AWS-specific.</p>
<p>Is that the end game? I don&rsquo;t think so.</p>
<h2 id="desired-properties-of-infrastructure-definition-tool">Desired Properties of Infrastructure Definition Tool</h2>
<p>So what have we learned while going through the existing landscape? The perfect infrastructure tools should:</p>
<ul>
<li>Provide <strong>reproducible</strong> results of deployments</li>
<li>Be <strong>scriptable</strong>, i.e., require no human intervention after the definition is complete</li>
<li>Define the <strong>desired state</strong> rather than exact steps to achieve it</li>
<li>Support <strong>multiple cloud providers</strong> and hybrid scenarios</li>
<li>Be <strong>universal</strong> in the sense of using the same tool to define any type of resource</li>
<li>Be <strong>succinct</strong> and <strong>concise</strong> to stay readable and manageable</li>
<li><del>Use YAML-based format</del></li>
</ul>
<p>Nah, I crossed out the last item. YAML seems to be the most popular language among this class of tools (and I haven&rsquo;t even touched Kubernetes yet!), but I&rsquo;m not convinced it works well for me. <a href="https://noyaml.com/">YAML has many flaws, and I just don&rsquo;t want to use it</a>.</p>
<p>Have you noticed that I haven&rsquo;t mentioned <strong>Infrastructure as code</strong> a single time yet? Well, here we go (from <a href="https://en.wikipedia.org/wiki/Infrastructure_as_code">Wikipedia</a>):</p>
<blockquote>
<p>Infrastructure as code (IaC) is the process of managing and provisioning computer data centers through machine-readable definition files, rather than physical hardware configuration or interactive configuration tools.</p></blockquote>
<p>Shouldn&rsquo;t it be called &ldquo;Infrastructure as definition files&rdquo;, or &ldquo;Infrastructure as YAML&rdquo;?</p>
<p>As a software developer, what I really want is &ldquo;Infrastructure as actual code, you know, the program thing&rdquo;. I want to use <strong>the same language</strong> that I already know. I want to stay in the same editor. I want to get IntelliSense <strong>auto-completion</strong> when I type. I want to see the <strong>compilation errors</strong> when what I typed is not syntactically correct. I want to reuse the <strong>developer skills</strong> that I already have. I want to come up with <strong>abstractions</strong> to generalize my code and create <strong>reusable components</strong>. I want to <strong>leverage the open-source community</strong> who would create much better components than I ever could. I want to <strong>combine the code and infrastructure</strong> in one code project.</p>
<p>If you are with me on that, keep reading. You get all of that with Pulumi.</p>
<h2 id="pulumi">Pulumi</h2>
<p><a href="https://pulumi.io/">Pulumi</a> is a tool to build cloud-based software using real programming languages. They support all major cloud providers, plus Kubernetes.</p>
<p>Pulumi programming model supports Go and Python too, but I&rsquo;m going to use TypeScript for the rest of the article.</p>
<p>While prototyping a URL shortener, I explain the fundamental way of working and illustrate the benefits and some trade-offs. If you want to follow along, <a href="https://pulumi.io/quickstart/install.html">install Pulumi</a>.</p>
<h2 id="how-pulumi-works">How Pulumi Works</h2>
<p>Let&rsquo;s start defining our URL shortener application in TypeScript. I installed <code>@pulumi/pulumi</code> and <code>@pulumi/aws</code> NPM modules so that I can start the program. The first resource to create is a DynamoDB table:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span>    <span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">aws</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/aws&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">// A DynamoDB table with a single primary key
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>    <span style="color:#cf222e">let</span> <span style="color:#1f2328">counterTable</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">dynamodb</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Table</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;urls&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;urls&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">attributes</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">{</span> <span style="color:#1f2328">name</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;name&#34;</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">type</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;S&#34;</span> <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">],</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">hashKey</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;name&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">readCapacity</span>: <span style="color:#cf222e">1</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">writeCapacity</span>: <span style="color:#cf222e">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>I use <code>pulumi</code> CLI to run this program to provision the actual resource in AWS:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-console" data-lang="console"><span style="display:flex;"><span>&gt; pulumi up
</span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#1f2328">Previewing update (urlshortener):
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"></span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#1f2328">     Type                   Name             Plan
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"> +   pulumi:pulumi:Stack    urlshortener     create
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"> +    aws:dynamodb:Table    urls             create
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"></span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#1f2328">Resources:
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328">    + 2 to create
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"></span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#1f2328">Do you want to perform this update? yes
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328">Updating (urlshortener):
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"></span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#1f2328">     Type                   Name             Status
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"> +   pulumi:pulumi:Stack    urlshortener     created
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"> +    aws:dynamodb:Table    urls             created
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"></span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#1f2328">Resources:
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328">    + 2 created
</span></span></span></code></pre></div><p>The CLI first shows the preview of the changes to be made, and when I confirm, it creates the resource. It also creates a <strong>stack</strong>—a container for all the resources of the application.</p>
<p>This code might look like an imperative command to create a DynamoDB table, but it actually isn&rsquo;t. If I go ahead and change <code>readCapacity</code> to <code>2</code> and then re-run <code>pulumi up</code>, it produces a different outcome:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-console" data-lang="console"><span style="display:flex;"><span>&gt; pulumi up
</span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#1f2328">Previewing update (urlshortener):
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"></span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#1f2328">     Type                   Name             Plan
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328">     pulumi:pulumi:Stack    urlshortener
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"> ~   aws:dynamodb:Table     urls             update  [diff: ~readCapacity]
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"></span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#1f2328">Resources:
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328">    ~ 1 to update
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328">    1 unchanged
</span></span></span></code></pre></div><p>It detects the exact change that I made and suggests an update. The following picture illustrates how Pulumi works:</p>
<p><img src="how-pulumi-works.png" alt="How Pulumi works"></p>
<figcaption><h4>How Pulumi works</h4></figcaption>
<p><code>index.ts</code> in the red square is my program. Pulumi&rsquo;s language host understands TypeScript and translates the code to commands to the internal engine. As a result, the engine builds a tree of resources-to-be-provisioned, the desired state of the infrastructure.</p>
<p>The end state of the last deployment is persisted in the storage (can be in pulumi.com backend or a file on disk). The engine then compares the current state of the system with the desired state of the program and calculates the delta in terms of create-update-delete commands to the cloud provider.</p>
<h2 id="help-of-types">Help Of Types</h2>
<p>Now I can proceed to the code that defines a Lambda function:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#57606a">// Create a Role giving our Lambda access.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">let</span> <span style="color:#1f2328">policy</span>: <span style="color:#cf222e">aws.iam.PolicyDocument</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">{</span> <span style="color:#57606a">/* Redacted for brevity */</span> <span style="color:#1f2328">};</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#1f2328">role</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">iam</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Role</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;lambda-role&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">assumeRolePolicy</span>: <span style="color:#cf222e">JSON.stringify</span><span style="color:#1f2328">(</span><span style="color:#1f2328">policy</span><span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">let</span> <span style="color:#1f2328">fullAccess</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">iam</span><span style="color:#1f2328">.</span><span style="color:#1f2328">RolePolicyAttachment</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;lambda-access&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">role</span>: <span style="color:#cf222e">role</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">policyArn</span>: <span style="color:#cf222e">aws.iam.AWSLambdaFullAccess</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Create a Lambda function, using code from the `./app` folder.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">let</span> <span style="color:#1f2328">lambda</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">lambda</span><span style="color:#1f2328">.</span><span style="color:#6639ba">Function</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;lambda-get&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">runtime</span>: <span style="color:#cf222e">aws.lambda.NodeJS8d10Runtime</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">code</span>: <span style="color:#cf222e">new</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">asset</span><span style="color:#1f2328">.</span><span style="color:#1f2328">AssetArchive</span><span style="color:#1f2328">({</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0a3069">&#34;.&#34;</span><span style="color:#0550ae">:</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">asset</span><span style="color:#1f2328">.</span><span style="color:#1f2328">FileArchive</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;./app&#34;</span><span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}),</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">timeout</span>: <span style="color:#cf222e">300</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">handler</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;read.handler&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">role</span>: <span style="color:#cf222e">role.arn</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">environment</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">variables</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#0a3069">&#34;COUNTER_TABLE&#34;</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">counterTable</span><span style="color:#1f2328">.</span><span style="color:#1f2328">name</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">},</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">dependsOn</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span><span style="color:#1f2328">fullAccess</span><span style="color:#1f2328">]</span> <span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>You can see that the complexity kicked in and the code size is growing. However, now I start to gain real benefits from using a typed programming language:</p>
<ul>
<li>I&rsquo;m using objects in the definitions of other object&rsquo;s parameters. If I misspell their name, I don&rsquo;t get a runtime failure but an immediate error message from the editor.</li>
<li>If I don&rsquo;t know which options I need to provide, I can go to the type definition and look it up (or use IntelliSense).</li>
<li>If I forget to specify a mandatory option, I get a clear error.</li>
<li>If the type of the input parameter doesn&rsquo;t match the type of the object I&rsquo;m passing, I get an error again.</li>
<li>I can use language features like <code>JSON.stringify</code> right inside my program. In fact, I can reference and use any NPM module.</li>
</ul>
<p>You can see the code for API Gateway <a href="https://github.com/mikhailshilkov/fosdem2019/blob/master/samples/1-raw/index.ts#L60-L118">here</a>. It looks too verbose, doesn&rsquo;t it? Moreover, I&rsquo;m only half-way through with only one Lambda function defined.</p>
<h2 id="reusable-components">Reusable Components</h2>
<p>We can do better than that. Here is the improved definition of the same Lambda function:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">Lambda</span> <span style="color:#1f2328">}</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;./lambda&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">func</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">Lambda</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;lambda-get&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">path</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;./app&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">file</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;read&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">environment</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>       <span style="color:#0a3069">&#34;COUNTER_TABLE&#34;</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">counterTable</span><span style="color:#1f2328">.</span><span style="color:#1f2328">name</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">},</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>Now, isn&rsquo;t that beautiful? Only the essential options remained, while all the machinery is gone. Well, it&rsquo;s not completely gone, it&rsquo;s been hidden behind an <em>abstraction</em>.</p>
<p>I defined a <strong>custom component</strong> called <code>Lambda</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">interface</span> <span style="color:#1f2328">LambdaOptions</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">readonly</span> <span style="color:#1f2328">path</span>: <span style="color:#cf222e">string</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">readonly</span> <span style="color:#1f2328">file</span>: <span style="color:#cf222e">string</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">readonly</span> <span style="color:#1f2328">environment?</span>:  <span style="color:#cf222e">pulumi.Input</span><span style="color:#0550ae">&lt;</span><span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">[</span><span style="color:#1f2328">key</span>: <span style="color:#cf222e">string</span><span style="color:#1f2328">]</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Input</span><span style="color:#1f2328">&lt;</span><span style="color:#0550ae">string</span><span style="color:#1f2328">&gt;;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span><span style="color:#0550ae">&gt;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">class</span> <span style="color:#1f2328">Lambda</span> <span style="color:#cf222e">extends</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">ComponentResource</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">public</span> <span style="color:#cf222e">readonly</span> <span style="color:#1f2328">lambda</span>: <span style="color:#cf222e">aws.lambda.Function</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">constructor</span><span style="color:#1f2328">(</span><span style="color:#1f2328">name</span>: <span style="color:#cf222e">string</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">options</span>: <span style="color:#cf222e">LambdaOptions</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">opts?</span>: <span style="color:#cf222e">pulumi.ResourceOptions</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">super</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;my:Lambda&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">name</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">opts</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">const</span> <span style="color:#1f2328">role</span> <span style="color:#0550ae">=</span> <span style="color:#57606a">//... Role as defined in the last snippet
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>        <span style="color:#cf222e">const</span> <span style="color:#1f2328">fullAccess</span> <span style="color:#0550ae">=</span> <span style="color:#57606a">//... RolePolicyAttachment as defined in the last snippet
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>
</span></span><span style="display:flex;"><span>        <span style="color:#cf222e">this</span><span style="color:#1f2328">.</span><span style="color:#1f2328">lambda</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">lambda</span><span style="color:#1f2328">.</span><span style="color:#6639ba">Function</span><span style="color:#1f2328">(</span><span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">name</span><span style="color:#0a3069">}</span><span style="color:#0a3069">-func`</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">runtime</span>: <span style="color:#cf222e">aws.lambda.NodeJS8d10Runtime</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">code</span>: <span style="color:#cf222e">new</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">asset</span><span style="color:#1f2328">.</span><span style="color:#1f2328">AssetArchive</span><span style="color:#1f2328">({</span>
</span></span><span style="display:flex;"><span>                <span style="color:#0a3069">&#34;.&#34;</span><span style="color:#0550ae">:</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">pulumi</span><span style="color:#1f2328">.</span><span style="color:#1f2328">asset</span><span style="color:#1f2328">.</span><span style="color:#1f2328">FileArchive</span><span style="color:#1f2328">(</span><span style="color:#1f2328">options</span><span style="color:#1f2328">.</span><span style="color:#1f2328">path</span><span style="color:#1f2328">),</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">}),</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">timeout</span>: <span style="color:#cf222e">300</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">handler</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">`</span><span style="color:#0a3069">${</span><span style="color:#1f2328">options</span><span style="color:#1f2328">.</span><span style="color:#1f2328">file</span><span style="color:#0a3069">}</span><span style="color:#0a3069">.handler`</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">role</span>: <span style="color:#cf222e">role.arn</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">environment</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>                <span style="color:#1f2328">variables</span>: <span style="color:#cf222e">options.environment</span>
</span></span><span style="display:flex;"><span>            <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">},</span> <span style="color:#1f2328">{</span> <span style="color:#1f2328">dependsOn</span><span style="color:#0550ae">:</span> <span style="color:#1f2328">[</span><span style="color:#1f2328">fullAccess</span><span style="color:#1f2328">],</span> <span style="color:#1f2328">parent</span>: <span style="color:#cf222e">this</span> <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">}</span>
</span></span></code></pre></div><p>The interface <code>LambdaOptions</code> defines options that are important for my abstraction. The class <code>Lambda</code> derives from <code>pulumi.ComponentResource</code> and creates all the child resources in its constructor.</p>
<p>A nice effect is that one can see the structure in <code>pulumi</code> preview:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-console" data-lang="console"><span style="display:flex;"><span><span style="color:#1f2328">Previewing update (urlshortener):
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"></span><span style="color:#f6f8fa;background-color:#82071e">
</span></span></span><span style="display:flex;"><span><span style="color:#f6f8fa;background-color:#82071e"></span><span style="color:#1f2328">     Type                                Name                  Plan
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"> +   pulumi:pulumi:Stack                 urlshortener          create
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"> +     my:Lambda                         lambda-get            create
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"> +       aws:iam:Role                    lambda-get-role       create
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"> +       aws:iam:RolePolicyAttachment    lambda-get-access     create
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"> +       aws:lambda:Function             lambda-get-func       create
</span></span></span><span style="display:flex;"><span><span style="color:#1f2328"> +     aws:dynamodb:Table                urls                  create
</span></span></span></code></pre></div><p>The <code>Endpoint</code> component simplifies the definition of API Gateway (see <a href="https://github.com/mikhailshilkov/fosdem2019/blob/master/samples/2-components/endpoint.ts">the source</a>):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#cf222e">const</span> <span style="color:#1f2328">api</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">Endpoint</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;urlapi&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">path</span><span style="color:#0550ae">:</span> <span style="color:#0a3069">&#34;/{proxy+}&#34;</span><span style="color:#1f2328">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">lambda</span>: <span style="color:#cf222e">func.lambda</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span></code></pre></div><p>The component hides the complexity from the clients—if the abstraction was selected correctly, that is. The component class can be reused in multiple places, in several projects, across teams, etc.</p>
<h2 id="standard-component-library">Standard Component Library</h2>
<p>In fact, Pulumi team came up with lots of high-level components that build abstractions on top of raw resources. The components from the <code>@pulumi/cloud-aws</code> package are particularly useful for serverless applications.</p>
<p>Here is the full URL shortener application with DynamoDB table, Lambdas, API Gateway, and S3-based static files:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#cf222e">import</span> <span style="color:#0550ae">*</span> <span style="color:#cf222e">as</span> <span style="color:#1f2328">aws</span> <span style="color:#cf222e">from</span> <span style="color:#0a3069">&#34;@pulumi/cloud-aws&#34;</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Create a table `urls`, with `name` as primary key.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">let</span> <span style="color:#1f2328">urlTable</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">Table</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;urls&#34;</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;name&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Create a web server.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#cf222e">let</span> <span style="color:#1f2328">endpoint</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">new</span> <span style="color:#1f2328">aws</span><span style="color:#1f2328">.</span><span style="color:#1f2328">API</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;urlshortener&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// Serve all files in the www directory to the root.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#1f2328">endpoint</span><span style="color:#1f2328">.</span><span style="color:#cf222e">static</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;/&#34;</span><span style="color:#1f2328">,</span> <span style="color:#0a3069">&#34;www&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// GET /url/{name} redirects to the target URL based on a short-name.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#1f2328">endpoint</span><span style="color:#1f2328">.</span><span style="color:#cf222e">get</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;/url/{name}&#34;</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">req</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">res</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#1f2328">name</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">req</span><span style="color:#1f2328">.</span><span style="color:#1f2328">params</span><span style="color:#1f2328">[</span><span style="color:#0a3069">&#34;name&#34;</span><span style="color:#1f2328">];</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#1f2328">value</span> <span style="color:#0550ae">=</span> <span style="color:#cf222e">await</span> <span style="color:#1f2328">urlTable</span><span style="color:#1f2328">.</span><span style="color:#cf222e">get</span><span style="color:#1f2328">({</span><span style="color:#1f2328">name</span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#1f2328">url</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">value</span> <span style="color:#0550ae">&amp;&amp;</span> <span style="color:#1f2328">value</span><span style="color:#1f2328">.</span><span style="color:#1f2328">url</span><span style="color:#1f2328">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#57606a">// If we found an entry, 301 redirect to it; else, 404.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span>    <span style="color:#cf222e">if</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">url</span><span style="color:#1f2328">)</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">res</span><span style="color:#1f2328">.</span><span style="color:#1f2328">setHeader</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;Location&#34;</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">url</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">res</span><span style="color:#1f2328">.</span><span style="color:#1f2328">status</span><span style="color:#1f2328">(</span><span style="color:#0550ae">301</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">res</span><span style="color:#1f2328">.</span><span style="color:#1f2328">end</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">else</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">res</span><span style="color:#1f2328">.</span><span style="color:#1f2328">status</span><span style="color:#1f2328">(</span><span style="color:#0550ae">404</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#1f2328">res</span><span style="color:#1f2328">.</span><span style="color:#1f2328">end</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;&#34;</span><span style="color:#1f2328">);</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">}</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#57606a">// POST /url registers a new URL with a given short-name.
</span></span></span><span style="display:flex;"><span><span style="color:#57606a"></span><span style="color:#1f2328">endpoint</span><span style="color:#1f2328">.</span><span style="color:#1f2328">post</span><span style="color:#1f2328">(</span><span style="color:#0a3069">&#34;/url&#34;</span><span style="color:#1f2328">,</span> <span style="color:#cf222e">async</span> <span style="color:#1f2328">(</span><span style="color:#1f2328">req</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">res</span><span style="color:#1f2328">)</span> <span style="color:#0550ae">=&gt;</span> <span style="color:#1f2328">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#1f2328">url</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">req</span><span style="color:#1f2328">.</span><span style="color:#1f2328">query</span><span style="color:#1f2328">[</span><span style="color:#0a3069">&#34;url&#34;</span><span style="color:#1f2328">];</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">let</span> <span style="color:#1f2328">name</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">req</span><span style="color:#1f2328">.</span><span style="color:#1f2328">query</span><span style="color:#1f2328">[</span><span style="color:#0a3069">&#34;name&#34;</span><span style="color:#1f2328">];</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cf222e">await</span> <span style="color:#1f2328">urlTable</span><span style="color:#1f2328">.</span><span style="color:#1f2328">insert</span><span style="color:#1f2328">({</span> <span style="color:#1f2328">name</span><span style="color:#1f2328">,</span> <span style="color:#1f2328">url</span> <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>    <span style="color:#1f2328">res</span><span style="color:#1f2328">.</span><span style="color:#1f2328">json</span><span style="color:#1f2328">({</span> <span style="color:#1f2328">shortenedURLName</span>: <span style="color:#cf222e">name</span> <span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span><span style="color:#1f2328">});</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cf222e">export</span> <span style="color:#cf222e">let</span> <span style="color:#1f2328">endpointUrl</span> <span style="color:#0550ae">=</span> <span style="color:#1f2328">endpoint</span><span style="color:#1f2328">.</span><span style="color:#1f2328">publish</span><span style="color:#1f2328">().</span><span style="color:#1f2328">url</span><span style="color:#1f2328">;</span>
</span></span></code></pre></div><p>The coolest thing here is that the actual <em>implementation code</em> of AWS Lambdas is <a href="https://blog.pulumi.com/lambdas-as-lambdas-the-magic-of-simple-serverless-functions">intertwined</a> with the <em>definition of resources</em>. The code looks very similar to an Express application. AWS Lambdas are defined as TypeScript lambdas. All strongly typed and compile-time checked.</p>
<p>It&rsquo;s worth noting that at the moment such high-level components only exist in TypeScript. One could create their custom components in Python or Go, but there is no standard library available. Pulumi folks <a href="https://github.com/pulumi/pulumi/issues/2430">are actively trying to figure out a way to bridge this gap</a>.</p>
<h2 id="avoiding-vendor-lock-in">Avoiding Vendor Lock-in?</h2>
<p>If you look closely at the previous code block, you notice that only one line is AWS-specific: the <code>import</code> statement. The rest is just naming.</p>
<p>We can get rid of that one too: just change the import to <code>import * as cloud from &quot;@pulumi/cloud&quot;;</code> and replace <code>aws.</code> with <code>cloud.</code> everywhere. Now, we&rsquo;d have to go to the stack configuration file and specify the cloud provider there:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#0550ae">config</span><span style="color:#1f2328">:</span><span style="color:#fff">
</span></span></span><span style="display:flex;"><span><span style="color:#fff">  </span><span style="color:#0550ae">cloud:provider</span><span style="color:#1f2328">:</span><span style="color:#fff"> </span>aws<span style="color:#fff">
</span></span></span></code></pre></div><p>Which is enough to make the application work again!</p>
<p>Vendor lock-in seems to be a big concern among many people when it comes to cloud architectures heavily relying on managed cloud services, including serverless applications. While I don&rsquo;t necessarily share those concerns and am not sure if generic abstractions are the right way to go, Pulumi Cloud library can be one direction for the exploration.</p>
<p>The following picture illustrates the choice of the level of abstraction that Pulumi provides:</p>
<p><img src="pulumi-layers.png" alt="Pulumi abstraction layers"></p>
<figcaption><h4>Pulumi abstraction layers</h4></figcaption>
<p>Working on top of the cloud provider&rsquo;s API and internal resource provider, you can choose to work with raw components with maximum flexibility, or opt-in for higher-level abstractions. Mix-and-match in the same program is possible too.</p>
<h2 id="infrastructure-as-real-code">Infrastructure as Real Code</h2>
<p>Designing applications for the modern cloud means utilizing multiple cloud services which have to be configured to play nicely together. The Infrastructure as Code approach is almost a requirement to keep the management of such applications reliable in a team setting and over the extended period.</p>
<p>Application code and supporting infrastructure become more and more blended, so it&rsquo;s natural that software developers take the responsibility to define both. The next logical step is to use the same set of languages, tooling, and practices for both software and infrastructure.</p>
<p>Pulumi exposes cloud resources as APIs in several popular general-purpose programming languages. Developers can directly transfer their skills and experience to define, build, compose, and deploy modern cloud-native and serverless applications more efficiently than ever.</p>
]]></content>
            
                 
                    
                 
                    
                
            
        </entry>
    
    
        
        <entry>
            <title type="html"><![CDATA[Serverless at Scale: Serving StackOverflow-like Traffic]]></title>
            <link href="https://mikhail.io/2019/serverless-at-scale-serving-stackoverflow-like-traffic/"/>
            <id>https://mikhail.io/2019/serverless-at-scale-serving-stackoverflow-like-traffic/</id>
            
            <published>2019-01-21T00:00:00+00:00</published>
            <updated>2019-01-21T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Scalability test for HTTP-triggered serverless functions across AWS, Azure and GCP</blockquote><p>Serverless compute is a very productive and quick way to get an application up and running. A developer writes a piece of code that solves a particular task and uploads it to the cloud. The provider handles code deployment and the ops burden of managing all the required infrastructure, so that the Function is always available, secure and performant.</p>
<p>Performance is a feature, and the ability to run the same application for 10 users or 10 million users is very appealing. Unfortunately, FaaS is not magical, so scalability limits do exist. That&rsquo;s why I spend time testing the existing FaaS services to highlight the cases where performance might not be perfect. For some background, you can read my previous articles:  <a href="https://mikhail.io/2018/11/from-0-to-1000-instances-how-serverless-providers-scale-queue-processing/">From 0 to 1000 Instances: How Serverless Providers Scale Queue Processing</a> for queue-based workloads and <a href="https://mikhail.io/2018/08/serverless-cold-start-war/">Serverless: Cold Start War</a> for exploring cold start latencies.</p>
<p>Today, I want to dig into the scalability of serverless HTTP-based functions. HTTP-based functions are a popular use case that most developers can relate to, and they are also heavily impacted by the ability to scale. When your app goes viral on social networks, scores the Hacker News front page, or gets featured on TV, the last thing you want is slow responses and timeouts.</p>
<p>I implemented a simple HTTP-triggered function and deployed it across the Big-3 cloud providers—Amazon, Microsoft, and Google. Next, I ran a load test issuing hundreds of requests per second to each function. In this article, I present the design and the results of these experiments.</p>
<p><em>DISCLAIMER: Performance testing is hard. I might be missing some crucial factors and parameters that influence the outcome. My interpretation might be wrong. The results might change over time. If you happen to know a way to improve my tests, please let me know, and I will re-run them and re-publish the results.</em></p>
<h2 id="stackoverflow-on-faas">StackOverflow on FaaS</h2>
<p>Every developer knows <a href="https://stackoverflow.com/">StackOverflow</a> and uses it pretty much every day. I&rsquo;ve made the goal to serve traffic comparable to what StackOverflow sees, solely from a serverless function.</p>
<p>StackOverflow is an excellent target for many reasons:</p>
<ul>
<li>Publish the actual request statistics from the site (see <a href="https://nickcraver.com/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/">the data from 2016</a>)</li>
<li>Very transparent about their tech stack (same link above)</li>
<li>Publish  <a href="https://www.brentozar.com/archive/2015/10/how-to-download-the-stack-overflow-database-via-bittorrent/">the full database</a> and provide <a href="https://data.stackexchange.com/stackoverflow/queries">a tool to query the data online</a></li>
</ul>
<p>StackOverflow runs on .NET Core, SQL Server, Redis, Elastic, etc. Obviously, my goal is not to replicate the whole site. I just want to serve the comparable traffic to the outside world.</p>
<p>Here are some important metrics for my experiment:</p>
<ul>
<li>StackOverflow served 66 million pages per day, which is 760 pages/sec on average.</li>
<li>We’ll make the assumption that the vast majority of those pageviews are question pages, so I will ignore everything else.</li>
<li>We know they serve the whole page as one server-rendered HTML, so we’ll do something comparable.</li>
<li>Each page should be ~ 100kb size before compression.</li>
</ul>
<p>With this in mind, I came up with the following experiment design:</p>
<ul>
<li>Create a HTML template for the whole question page with question and answer markup replaced by placeholders.</li>
<li>Download the data of about 1000 questions and their respective answers from the <a href="https://data.stackexchange.com/stackoverflow/query/new">the data explorer</a>.</li>
<li>Save the HTML templates and JSON data in blob storage of each cloud provider.</li>
<li>Implement a serverless function that retrieves the question data, populates the template, and returns the HTML in response.</li>
</ul>
<p><img src="stackoverflow-test-setup.png" alt="Serving StackOverflow Traffic from a Serverless Function"></p>
<figcaption><h4>Serving StackOverflow Traffic from a Serverless Function</h4></figcaption>
<p>The HTML template is loaded at the first request and then cached in memory. The question/answers data file is loaded from the blob storage for every request. Template population is accomplished with string concatenation.</p>
<p>In my view, this setup is a simple but fair approximation of StackOverflow’s front-end. In addition, it is somewhat representative of many real-world web applications.</p>
<h2 id="metrics-setup">Metrics Setup</h2>
<p>I analyzed the scalability of the following cloud services:</p>
<ul>
<li>AWS Lambda triggered via Amazon API Gateway (<a href="https://docs.aws.amazon.com/en_us/lambda/latest/dg/with-on-demand-https.html">docs</a>)</li>
<li>Azure Function with an HTTP trigger (<a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook">docs</a>)</li>
<li>Google Cloud HTTP Function (<a href="https://cloud.google.com/functions/docs/writing/http">docs</a>)</li>
</ul>
<p>All functions were implemented in JavaScript (Node.js) and were running on the latest GA runtime.</p>
<p>Since built-in monitoring tools, such as CloudWatch, would only report the function execution duration, which does not include other potential delays in the HTTP pipeline, I instead measured end-to-end latency from the client perspective. This means that the latency of the network and HTTP gateway (e.g., API Gateway in the case of AWS) were included in the total duration.</p>
<p>Requests were sent from multiple VMs outside the target cloud provider&rsquo;s region but in geographical proximity. Network latency was present in the metrics, but I estimated it to be 20-30 milliseconds at most.</p>
<p><img src="measuring-response-time.png" alt="Measuring Response Time of a Serverless Function"></p>
<figcaption><h4>Measuring Response Time of a Serverless Function</h4></figcaption>
<p>Blob storage services of all cloud providers have enough throughput to serve one blob per HTTP request. However, the latencies differ among the clouds, so I included blob fetch duration measurements in the performance baseline.</p>
<p>Each measurement was then saved to persistent storage and analyzed afterward.</p>
<p>The charts below show <a href="https://en.wikipedia.org/wiki/Percentile">percentile</a> values. For instance, the 95th percentile (written as P95) value of 100ms means that 95% of the requests were faster than 100ms while 5% were slower than that. P50 is the median.</p>
<h2 id="load-pattern">Load Pattern</h2>
<p>I wanted to test the ability of serverless functions to scale up rapidly in response to the growth in request rate, so I came up with a dynamic load scenario.</p>
<p>The experiments started with a baseline 10% of the target load. The goal of the baseline was to make sure that the app was overall healthy, to evaluate the basic latency and the impact of blob storage on it.</p>
<p>At some point (around minute 0 of the charts), the load began to grow and reached 1000 RPS within 8 minutes. After the peak, the cooldown period started, and the load steadily decreased to zero in 8 more minutes.</p>
<p><img src="request-distribution.png" alt="Request Distribution during the Load Test"></p>
<figcaption><h4>Request Distribution during the Load Test</h4></figcaption>
<p>Even though the growth period on the left and the decline period on the right represented the same number of requests, the hypothesis was that the first half might be more challenging because of the need to provision new resources rapidly.</p>
<p>In total, 600,000 requests were served within 17 minutes with the total outbound traffic of 70 GB.</p>
<p>Finally, we’ve made it through all the mechanics, and now it&rsquo;s time to present the actual results.</p>
<h2 id="aws-lambda">AWS Lambda</h2>
<p>AWS Lambda was our first target for the experiment. I provisioned 512 MB size for Lambda instances, which is a medium-range value. I expected larger instances to be slightly faster, and smaller instances to be a bit slower, but the load is not very demanding to CPU, so the overall results should be comparable across the spectrum.</p>
<p>During the low-load baseline, the median response time was about 70 ms with a minimum of 50 ms. The median response time from the S3 bucket was 50 ms.</p>
<p>Here is the P50-P95 latency chart during the load test:</p>
<p><img src="aws-lambda-p50-p95.png" alt="AWS Lambda Response Time Distribution (P50-P95)"></p>
<figcaption><h4>AWS Lambda Response Time Distribution (P50-P95)</h4></figcaption>
<p>The percentiles were very consistent and flat. The median response time was still around 70 ms with no variance observed. P90 and P95 were quite stable too.</p>
<p>Only the 99th percentile displayed the difference between the ramp-up period on the left and the cooldown period on the right:</p>
<p><img src="aws-lambda-p99.png" alt="AWS Lambda Response Time Distribution (P99)"></p>
<figcaption><h4>AWS Lambda Response Time Distribution (P99)</h4></figcaption>
<p>AWS Lambda scales by creating multiple instances of the same function that handle the requests in parallel. Each Lambda instance is handling a single request at any given time, which is why the scale is measured in &ldquo;concurrent executions.&rdquo; When the current request is done being processed, the same instance can be reused for a subsequent request.</p>
<p>Instance identifier can be retrieved from <code>/proc/self/cgroup</code> of a lambda, so I recorded this value for each execution. The following chart shows the number of instances throughout the experiment:</p>
<p><img src="aws-lambda-concurrent-executions.png" alt="AWS Lambda Concurrent Executions"></p>
<figcaption><h4>AWS Lambda Concurrent Executions</h4></figcaption>
<p>There were about 80 concurrent executions at peak. That&rsquo;s quite a few, but still, almost an order of magnitude fewer instances compared to <a href="https://mikhail.io/2018/11/from-0-to-1000-instances-how-serverless-providers-scale-queue-processing/#crunching-numbers">my queue processing experiment</a>. It felt that AWS was capable of scaling even further.</p>
<p>P99.9 showed slowness of the least lucky 0.1% requests. Most probably, it had lots of cold starts in it:</p>
<p><img src="aws-lambda-p999.png" alt="AWS Lambda Response Time Distribution (P99.9)"></p>
<figcaption><h4>AWS Lambda Response Time Distribution (P99.9)</h4></figcaption>
<p>Still, even those requests were mostly served within 2-3 seconds.</p>
<h2 id="google-cloud-functions">Google Cloud Functions</h2>
<p>Now, let&rsquo;s look at the results of Google Cloud Functions. Once again I provisioned 512 MB instance size (same as for Lambda).</p>
<p>During the low-load baseline, the median response time was about 150 ms with a minimum of 100 ms. Almost all of that time was spent fetching blobs from Cloud Storage: Its median latency was over 130 ms! I haven&rsquo;t spent too much time investigating the reason, but I assume that Google Cloud Storage has higher latency for small files than S3. Zach Bjornson published  <a href="http://blog.zachbjornson.com/2015/12/29/cloud-storage-performance.html">the comparison of storage latencies</a>. Although it&rsquo;s 3 years old, the conclusion was that &ldquo;GCS averaged more than three times higher latency&rdquo; when compared to Azure and AWS.</p>
<p>That&rsquo;s an important observation because the Function execution times were twice as big as those recorded on AWS. Keeping this difference in mind, here is the P50-P95 latency chart during the GCP load test:</p>
<p><img src="google-cloud-function-p50-p95.png" alt="Google Cloud Function Response Time Distribution (P50-P95)"></p>
<figcaption><h4>Google Cloud Function Response Time Distribution (P50-P95)</h4></figcaption>
<p>The median value was stable and flat at 150-180 ms. P90 and P95 had some spikes during the first 3 minutes. Google passed the test, but the lower percentiles were not perfect.</p>
<p>The 99th percentile was relatively solid though. It was higher on the left, but it stayed within 1 second most of the time:</p>
<p><img src="google-cloud-function-p99.png" alt="Google Cloud Function Response Time Distribution (P99)"></p>
<figcaption><h4>Google Cloud Function Response Time Distribution (P99)</h4></figcaption>
<p>The scaling model of Google Functions appeared to be very similar to the one of AWS Lambda. This means that 2x duration of the average execution required 2x more concurrent executions to run and 2x more instances to be provisioned:</p>
<p><img src="google-cloud-function-instances.png" alt="Google Cloud Function Concurrent Executions"></p>
<figcaption><h4>Google Cloud Function Concurrent Executions</h4></figcaption>
<p>Indeed, there were about 160 concurrent executions at peak. GCP had to work twice as hard because of the storage latency, which might explain some of the additional variations of response time.</p>
<p>Besides, Google seems to manage instance lifecycle differently. It provisioned a larger batch of instances during the first two minutes, which was in line with <a href="https://mikhail.io/2018/11/from-0-to-1000-instances-how-serverless-providers-scale-queue-processing/#pause-the-world-workload">my previous findings</a>. It also kept instances for longer when the traffic went down (or at least, reused the existing instances more evenly).</p>
<p>For completeness, here are the P99.9 values:</p>
<p><img src="google-cloud-function-p999.png" alt="Google Cloud Function Response Time Distribution (P99.9)"></p>
<figcaption><h4>Google Cloud Function Response Time Distribution (P99.9)</h4></figcaption>
<p>They fluctuated between 1 and 5 seconds on the left and were incredibly stable on the right.</p>
<h2 id="azure">Azure</h2>
<p>Experiments with Azure Functions were run on <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-scale#consumption-plan">Consumption Plan</a>
—the dynamically scaled and billed-per-execution runtime. Consumption Plan doesn&rsquo;t have a configuration for allocated memory or any other instance size parameters.</p>
<p>During the low-load baseline, the median response time was about 95 ms with a minimum of 45 ms, which is close to AWS and considerably faster than GCP. This time, JSON file retrieval was not the main contributor to the end-to-end latency: The median response time of Azure Blob Storage was an amazing 8 ms.</p>
<p>However, it turns out that the scaling model of Azure Functions doesn&rsquo;t work well for my experiment. Very high latencies were observed during the load test on Azure:</p>
<p><img src="azure-function-js-p50-p95.png" alt="Azure Function (Node.js) Response Time Distribution (P50-P95)"></p>
<figcaption><h4>Azure Function (Node.js) Response Time Distribution (P50-P95)</h4></figcaption>
<p>The concurrency model of Azure Functions is different from the counterparts of AWS/GCP. Function App instance is closer to a VM than a single-task container. It runs multiple concurrent executions in parallel. A central coordinator called Scale Controller monitors the metrics from existing instances and determines how many instances to provision on top. Instance identifier can be retrieved from environment variables of a function, so I recorded this value for each execution.</p>
<p>The multiple-requests-at-one-instance model didn&rsquo;t help in terms of the total instances required to process the traffic:</p>
<p><img src="azure-function-js-instances.png" alt="Azure Function (Node.js) Instances"></p>
<figcaption><h4>Azure Function (Node.js) Instances</h4></figcaption>
<p>At peak, 90 instances were required, which is almost the same as the number of current executions of AWS Lambda. Given the I/O bound nature of my function, this was surprising to me.</p>
<p>Puzzled by the moderate results, I decided to run the same application as a .NET Azure Function and compare the performance. The same function ported to C# got much faster:</p>
<p><img src="azure-function-dotnet-p50-p95.png" alt="Azure Function (.NET) Response Time Distribution (P50-P95)"></p>
<figcaption><h4>Azure Function (.NET) Response Time Distribution (P50-P95)</h4></figcaption>
<p>P50 was extremely good: It stayed below 50 ms (leveraging the blazingly fast Blob Storage) for the whole period except for one point when it was 140 ms. P90 and P95 were stable except for three data points.</p>
<p>The chart of instance growth was very different from the JavaScript one too:</p>
<p><img src="azure-function-dotnet-instances.png" alt="Azure Function (.NET) Instances"></p>
<figcaption><h4>Azure Function (.NET) Instances</h4></figcaption>
<p>Basically, it spiked to 20 instances at the third minute, and that was enough for the rest of the test. I concluded that the .NET worker was more efficient compared to Node.js worker, at least for my scenario.</p>
<p>If I compare the percentile charts with the instance charts, it looks as if the latency spikes happen at the time when new instances get provisioned. For some reason, the performance suffers during the scale out. It&rsquo;s not just cold starts at the new instances: P90 and even P50 are affected. It might be a good topic for a separate investigation.</p>
<h2 id="conclusion">Conclusion</h2>
<p>During the experiment, sample StackOverflow pages were built and served from AWS Lambda, Google Cloud Functions, and Azure Functions at the rate of up to 1000 pageviews per second. Each Function call served a single pageview and was a combination of I/O workload (reading blob storage) and CPU usage (for parsing JSON and rendering HTML).</p>
<p>All cloud providers were able to scale up and serve the traffic. However, the latency distributions were quite different.</p>
<p>AWS Lambda was solid: Median response time was always below 100 ms, 95th percentile was below 200 ms, and 99th percentile exceeded 500 ms just once.</p>
<p>Google Cloud Storage seemed to have the highest latency out of the three cloud providers. Google Cloud Functions had a bit of a slowdown during the first two minutes of the scale-out but otherwise were quite stable and responsive.</p>
<p>Azure Functions had difficulties during the scale-out period and the response time went up to several seconds. .NET worker appeared to be more performant compared to Node.js one, but both of them show undesirable spikes when new instances are provisioned.</p>
<p>Here is my practical advice to take home:</p>
<ul>
<li>Function-as-a-Service is a great model to build applications that can work for low-usage scenarios, high-load applications, and even spiky workloads.</li>
<li>Scalability limits do exist, so if you anticipate high growth in the application&rsquo;s usage, run a simple load test to see how it behaves.</li>
<li>Always test in combination with your non-serverless dependencies. I&rsquo;ve selected scalable-by-definition cloud blob storage, and yet I got some influence of its behavior on the results. If you use a database or a third-party service, it&rsquo;s quite likely they will hit the scalability limit much earlier than the serverless compute.</li>
</ul>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://mikhail.io/tags/azure" term="azure" label="Azure" />
                             
                                <category scheme="https://mikhail.io/tags/azure-functions" term="azure-functions" label="Azure Functions" />
                             
                                <category scheme="https://mikhail.io/tags/serverless" term="serverless" label="Serverless" />
                             
                                <category scheme="https://mikhail.io/tags/performance" term="performance" label="Performance" />
                             
                                <category scheme="https://mikhail.io/tags/scalability" term="scalability" label="Scalability" />
                             
                                <category scheme="https://mikhail.io/tags/aws" term="aws" label="AWS" />
                             
                                <category scheme="https://mikhail.io/tags/aws-lambda" term="aws-lambda" label="AWS Lambda" />
                             
                                <category scheme="https://mikhail.io/tags/gcp" term="gcp" label="GCP" />
                             
                                <category scheme="https://mikhail.io/tags/google-cloud-functions" term="google-cloud-functions" label="Google Cloud Functions" />
                            
                        
                    
                
            
        </entry>
    
    
</feed>