<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by George Mao on Medium]]></title>
        <description><![CDATA[Stories by George Mao on Medium]]></description>
        <link>https://medium.com/@georgemao?source=rss-6f6d7bda3a17------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/2*nUj9MlzQ6RtO2X0M0Y1nAQ.png</url>
            <title>Stories by George Mao on Medium</title>
            <link>https://medium.com/@georgemao?source=rss-6f6d7bda3a17------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 10:44:49 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@georgemao/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Intro: Antigravity Skills for Developer automation]]></title>
            <link>https://medium.com/google-cloud/intro-antigravity-skills-for-developer-automation-9c658fecc71a?source=rss-6f6d7bda3a17------2</link>
            <guid isPermaLink="false">https://medium.com/p/9c658fecc71a</guid>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[developer]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[antigravity]]></category>
            <dc:creator><![CDATA[George Mao]]></dc:creator>
            <pubDate>Mon, 13 Jul 2026 14:43:16 GMT</pubDate>
            <atom:updated>2026-07-14T17:48:35.038Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*nd3zJjYQLeUGe6w3.png" /></figure><h3>Intro</h3><p>In my previous post I introduced <a href="https://medium.com/google-cloud/deep-dive-antigravity-agent-skills-b303bf05085b">Antigravity Skills</a>. This post will show how you can create customized Skills to power your personal workload. For this post, I’ll focus on Skills a software developer might use to automate time consuming CI/CD tasks.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LHztRxV7Zzu0w7IwEvqUiA.png" /></figure><p>There are a lot of manual steps (and mistakes) a developer has to execute in the CI/CD lifecycle … so we’re going to write an Antigravity Skill that an Agent can use to automate all of these steps!</p><p>If you’re fairly comfortable with Skills, just skip to the <a href="https://github.com/georgemao/antigravity-skills/blob/main/ai-developer/SKILL.md">AI Developer </a>skill in my Antigravity Skills repo for the full skill definition. Otherwise, keep reading for a walkthrough.</p><p><a href="https://github.com/georgemao/antigravity-skills">GitHub - georgemao/antigravity-skills: Sample agents for use by Antigravity</a></p><h3>Skill Requirements</h3><p>My goal is to automate all of the tasks that are performed by a human. The tasks fall into two categories:</p><h4>All of the actions to manage my Git repo</h4><p>These are branching, pushing code, writing, documenting, &amp; merging Pull Requests.</p><ul><li>These actions manage an external 3rd party tool so it’s appropriate to call it’s MCP server and execute Tools to perform these tasks.</li><li>We’ll enable the Github MCP server in the Skill.</li></ul><h4>All of the human actions to approve code</h4><p>This are peer reviews for bugs, performance, security issues, and ensuring proper documentation.</p><ul><li>These require context and developer knowledge that only a LLM can perform</li><li>We’ll write detailed instructions in the Skill for the Agent to follow</li></ul><h3>Skill Overview</h3><p>Here’s the <a href="https://github.com/georgemao/antigravity-skills/blob/main/ai-developer/SKILL.md?plain=1">AI Developer skill</a> for reference.</p><h4>Define the Skill via Front matter</h4><p>The first section defines the skill’s name , description , tools, and model.</p><ul><li>The <strong>name</strong> attribute is not important — you can explicitly execute a skill by using the <em>@[skill name]</em> syntax.</li><li>The <strong>description</strong> attribute is what Antigravity will use to match prompts to Skills and it describes when the Skill should be activated.</li><li>The <strong>tools</strong> attribute enables the Skill to use 3rd party MCP servers. In our case we want to enable the GitHub MCP server. Note the syntax: <strong>mcp_[name]_*. </strong>The * indicates that all Tools available on the MCP server are allowed to be used.</li><li>The <strong>model</strong> attribute tells the Agent to use a specific model. Choose one that matches your workload needs.</li></ul><pre>---<br>name: ai-developer<br>description:<br>  Use this skill to help a developer automate code check-ins to Github. You are an expert developer who can review code, suggest code improvements, produce documentation, create Pull Requests and merge them. You have access to the codebase and can make changes to the code.<br>tools:<br>  - mcp_github_*<br>model: gemini-3.5-flash<br>---</pre><h4>Define the Workflow — how you want the Skill to proceed</h4><p>This is important because we want to be prescriptive and make sure the Agent follows a very specific workflow. This is the main reason to use a Skill.</p><p>At a high level, here&#39;s the workflow — it’s 5 steps but each step has detailed instructions on how to complete the step. I also specify the Scope — which tells the Agent exactly what it should review — in this case only code and only the main branch.</p><p>Let’s take a look at each step.</p><pre>## Workflow<br>First, ask the user what branch to review. If they don&#39;t specify, assume main branch only and do not look at other branches.<br>Then perform the following steps:<br><br>### 1. Review code<br>### 2. Check if the changes address any open Issues<br>### 3. Create Pull Request<br>### 4. Merge Pull Request<br>### 5. Cleanup<br><br>#### Scope<br>Only review source code. Skip the docs, config files, and package files.<br>Always use the Github MCP server.<br>Only review code against the main branch. Do not look at other branches.</pre><h4>#1 Review Code</h4><p>I want the Agent to perform a comprehensive code review that follows 6 distinct phases. Each phase should pause and check point with the user to summarize the results.</p><pre>### 1. Review code<br>Review the local code provided by the user. Execute a 6 step review as defined below. Checkpoint with the user after each step. <br><br>1. Identify bugs in the code.<br>2. Identify security issues in the code.<br>3. Identify performance issues in the code.<br>4. Identify missing features in the code.<br>5. Identify missing documentation in the code.<br>6. Update the Readme if needed.<br><br>For each checkpoint:<br>- Make sure to show the plan for changes<br>- Ask the user if they agree to the changes.<br>**Never make changes to the code until the user agrees.**<br>- Summarize your review using the following format:</pre><p>I design the actual check point to summarize and use a very specific format as follows:</p><pre>#### 1.1. Step X - [Description of step]<br>Summarize the improvements you would like to see. Use the following format:<br><br>  • Summary: A high-level overview of the review.<br>  • Findings:<br>      • Critical: State any breaking changes or issues that must be addressed.<br>      • Improvements: Suggestions for better code quality.<br>      • Nitpicks: Formatting or minor style issues (optional).<br>  • Conclusion: Clear recommendation.</pre><h4>#2 Check if the changes address any open Issues</h4><p>At this point the 6 phase review is complete. I want to check if we’ve addressed any open issues in the Git repo so we can close them.</p><pre>## 2. Check if the changes address any open Issues<br>Check the Git repo and see if the changes address any open issues. <br>- If so, suggest closing them. Add a note to the PR that these issues have been addressed.</pre><h4>#3 Create Pull Request</h4><p>Now we are ready to Create the Pull Request. Here I instruct the Agent to create a new branch and a PR in that branch. I also tell it to reference any issues that were addressed as part of our research in step #2.</p><pre>### 3. Create Pull Request<br>Once the user agrees to the changes, create a *NEW* branch and then a Pull Request for the code. <br>Use the existing configured Git repository.<br>Provide a short title and description of the changes, with reference to any issues that were addressed</pre><p>I also provide a very specific format for the Pull Request to be written in:</p><pre>Use the following format:<br>#### Summary *(What does this PR do?)*<br>- [ ] Bug fix<br>- [ ] New feature<br>- [ ] Refactoring<br>- [ ] Documentation update<br><br>#### Description<br>*(Describe this PR)*<br>- [ ] Summarize the changes<br>- [ ] Explain the impact<br><br>#### Checklist:<br>*(Please delete options that are not relevant.)*<br><br>- [ ] My code follows the style guidelines of this project<br>- [ ] I have performed a self-review of my code<br>- [ ] I have commented my code, particularly in hard-to-understand areas<br>- [ ] I have made corresponding changes to the documentation<br>- [ ] My changes generate no new warnings<br>- [ ] I have added tests that prove my fix is effective or that my feature works<br>- [ ] New and existing unit tests pass locally with my changes<br>- [ ] Any dependent changes have been merged and published in downstream modules</pre><h4>#4 Merge Pull Request</h4><p>This is the easiest part. I instruct the Agent to merge the PR, however I make sure to ask if the user agrees first.</p><pre>### 4. Merge Pull Request<br>Ask if we should save the code permantly. <br>If the user agrees, merge the Pull Request. If not, just move to clean up.</pre><h4>#5 Cleanup</h4><p>If the merge is successful, I ask the Agent to switch my local branch back to main. Finally, summarize the productivity gains from all of the automation.</p><pre>### 5. Cleanup<br>After the review, ask the user if they want to switch back to the default branch (e.g.,  main  or  master ).<br>Summarize and give the user an estimate on amount of time you saved them.</pre><h3>A test run</h3><p>First make sure you <a href="https://antigravity.google/docs/mcp#antigravity-cli">configure a MCP server </a>that has permissions to interact with your Git repo. You can do this via a <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens">Git PAT</a> with permissions to Write Branches, Pull Requests, and Merge.</p><p>For the example below I’m using my mcp-on-cloudrun repo:</p><p><a href="https://github.com/georgemao/mcp-on-cloudrun">GitHub - georgemao/mcp-on-cloudrun</a></p><p>Start the Antigravity CLI and Invoke the skill by entering: <em>Help me check in this code. </em>You should see the Agent begin walking through the 6 steps. As instructed, each step includes a checkpoint that summarizes the code review, a plan for changes, and a final question to proceed. Here’s a sample of the first 3 steps:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*flxGwvg2oTgsFdf9Vid2uA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FvQ25FPB2qB9UuWyCaplWg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZP1zq3p7legc0DhCsCxMkQ.png" /></figure><p>Next, the Agent creates the <a href="https://github.com/georgemao/mcp-on-cloudrun/pull/15">Pull Request</a> and writes the details per the template provided. The PR links any Issues that were addressed and prepares for the final merge to main.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AkTohxoErmG8J9LWuYzOHg.png" /></figure><p>As the last step, the Agent summarizes all actions taken and provides an estimated time saved.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IwZCmTNUyEZN0s0FrsC7Qg.png" /></figure><h3>Tips &amp; Best Practices</h3><p>Use a Skill to create a precriptive workflow for an Agent to follow.</p><ul><li>Be clear and define exactly what you expect the Agent to do</li><li>Tightly scope the Skill so it only uses resources you expect, such as MCP servers, Git Repos</li><li>Provide templates so generated artifacts follow a standard</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9c658fecc71a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/intro-antigravity-skills-for-developer-automation-9c658fecc71a">Intro: Antigravity Skills for Developer automation</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Deep Dive: Antigravity Agent Skills]]></title>
            <link>https://medium.com/google-cloud/deep-dive-antigravity-agent-skills-b303bf05085b?source=rss-6f6d7bda3a17------2</link>
            <guid isPermaLink="false">https://medium.com/p/b303bf05085b</guid>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[agent-skills]]></category>
            <category><![CDATA[google-antigravity]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <dc:creator><![CDATA[George Mao]]></dc:creator>
            <pubDate>Fri, 05 Jun 2026 22:41:38 GMT</pubDate>
            <atom:updated>2026-06-06T06:06:19.655Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LU4tZV9ggYOirs7OUIr2Dg.png" /></figure><p>An <a href="https://agentskills.io/home">Agent Skill </a>is a set of instructions that provide prescriptive guidance for how an AI tool should use a LLM to complete a task. The only required file isSKILL.md — this file defines the skill, its name, description, and execution instructions.</p><pre>my-skill/<br>├── SKILL.md          # Required: metadata + instructions<br>├── scripts/          # Optional: executable code<br>├── references/       # Optional: documentation<br>├── assets/           # Optional: templates, resources<br>└── ...               # Any additional files or directories</pre><p>Skills are <strong>not</strong> APIs and do not need to be hosted on a server. A Skill has two major components:</p><ul><li>Frontmatter based Metadata section — this describes the Skill and helps your AI tool select the right Skill to use based on a Prompt</li><li>Body — this includes the instructions and procedure the AI tool should follow</li></ul><p>Skills are loaded progressively. This means metadata is indexed to allow Skill selection but the body (and all optional resources) is not loaded until the specific skill is choosen. This helps reduce context bloat, improve performance, and reduce token usage.</p><h3>Execute a Skill</h3><p>This article will cover the new <a href="https://antigravity.google/">Google Antigravity Developer AI IDE/CLI.</a> Check out some sample skills in my <a href="https://github.com/georgemao/antigravity-skills">Antigravity Skills github repo</a>:</p><p><a href="https://github.com/georgemao/antigravity-skills">GitHub - georgemao/antigravity-skills: Sample agents for use by Antigravity</a></p><p>Install these in your ~/.gemini/skills directory to make them available globally in any workspace. Let’s start with the kid-code-reviewer skill to see how skills work. It’s a single agent skill that operates like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/804/1*XMqCuos79bEI71aNWMPCDg.png" /></figure><p>Read the <a href="https://github.com/georgemao/antigravity-skills/blob/main/kid-code-reviewer/SKILL.md">SKILL.md</a> file to understand the Skill’s workflow:</p><blockquote>Act as the child and argue about the code change</blockquote><blockquote>Give up</blockquote><blockquote>Pretend to commit to Github</blockquote><p>Start Antigravity CLI from any sample code base and enter the prompt below. I’ll use sample code from this code base: <a href="https://github.com/georgemao/mcp-on-cloudrun">MCP on Cloud Run</a>.</p><blockquote>Use my kid code reviewer to provide a code review.</blockquote><p>You should see Antigravity activate the kid-code-reviewer skill and read the source code for review. A few seconds later, the review is done and the response simulates a child.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8ddRaswzuhI476mOP-jk1A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ps7k79BsI6u6Dq-Y7sPkwQ.png" /></figure><p>Interact with the Skill a few times and eventually it’ll give up and try to commit to Git.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SGScmbyZzVa6ji8WXt2mDg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9blNoQMbnW_WvQCZ52pckw.png" /></figure><p>This interaction followed the prescriptive workflow specified in <a href="http://medium.com/r?url=https%3A%2F%2Fgithub.com%2Fgeorgemao%2Fantigravity-skills%2Fblob%2Fmain%2Fkid-code-reviewer%2FSKILL.md">SKILL.md. </a>Type /context to review the overall token usage for this interaction.</p><h3>Execute multiple Skills</h3><p>Now lets work on a use case where we need to call multiple skills. We’ll invoke two Skills sequentially like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MT5INwWyplY3xF4s2De7dw.png" /></figure><p>Check out the solutions-architect-agent <a href="https://github.com/georgemao/antigravity-skills/blob/main/solutions-architect-agent/SKILL.md">SKILL.md </a>file. Pay attention to the Frontmatter (metadata) section at the top. In particular, the <strong>tools</strong> attribute specifies that this skill can use a single MCP server: <strong>mcp_google-developer-knowledge_*.</strong> The * means the skill can use all tools hosted by that MCP server.</p><p>In the body, we provide instructions to act as a Solutions Architect &amp; use the Developer Knowledge MCP server to recommend solutions according to Google best practices. It also instructs the agent to respond using a very specific format. Finally, it provides a summary of the analysis and how much human time was saved. This is a long running operation and needs to reach out to an external service.</p><p>In the Antigravity CLI, enter this prompt:</p><blockquote>Investigate this error with the solutions-architect-agent skill and help suggest a fix: HTTP 429<br>The request was aborted because there was no available instance. At the same time, use my kid code reviewer to provide a code review.</blockquote><p>Antigravity loads the loads the solutions-architect-agent and the kid code reviewer skill just like before. It immediately begins execution of both Skills. A code review of the application code (<a href="https://github.com/georgemao/mcp-on-cloudrun/blob/main/server.py">server.py</a>) begins and a call to the Developer Knowledge MCP Server is launched.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*N-iTNW6aHjFTsD2mc6PZUw.png" /></figure><p>After a short period, Antigravity tells us it investigated the errors and suggests the solution, using the format specified. It also summarizes the work and says this could have saved 15–20 minutes of human time.</p><p>Next, Antigravity returns the code review results for server.py.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NaYRDDF73_1dGxoGLsghOQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jzaOt1gGnkk9MFnIdKSJLA.png" /></figure><p>The Solutions Architect skill is complete at this time, however the kid code reviewer is not complete because it is instructed to argue with you a few times.</p><p>Note how you cannot interact with the kid code reviewer until <strong>both</strong> of the skills execute to their first/final checkpoint. This is because work executes sequentially by the main Agent. Your main conversation context also increases across the entire scope of work. Try interacting with the code reviewer:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6Qfu8ozPnT3mp4dMaUgblg.png" /></figure><p>Now type /context — pay attention to System Tools (16k tokens) where the bulk of the tokens are used.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dCReoaGdOXL8G4a6__ENTA.png" /></figure><h3>Use Sub agents to Execute Skills</h3><p>Antigravity allows you to execute Skills in parallel by using sub agents. A Sub agent also ensures the context is contained to the sub agent and keeps the main conversation clean.</p><p>Try the Solutions Architect skill as a sub agent:</p><blockquote>In a subagent: Investigate this error with the solutions-architect-agent skill and help suggest a fix: HTTP 429<br>The request was aborted because there was no available instance.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*y7acmD9ZCAluFCaOnFrXDA.png" /></figure><p>Note the <strong>DefineSubagent</strong> process kicks off an Agent (solutions_architect) to perform the work, leaving the main conversation ready for more work. While the first Subagent is working, create another Subagent to perform the code review:</p><blockquote>In a different subagent: use my kid code reviewer to provide a code review.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G-VOUGU7mbXUJ0uTU9CwKQ.png" /></figure><p>Now both agents are working through their tasks. The main agent will monitor the subagents for status. Type /agents to check how they are doing:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sC8mGPKGrNt8rI-yoXXn1w.png" /></figure><p>After some time both sub agents will reported back with their status. The Solutions architect subagent is complete and wrote results to an artifact. Type /artifact to review it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dHEko0PArbl3614vKTv6oA.png" /></figure><p>The Code review subagent is ready to continue its conversation with you. Once you respond, the main conversation invokes a SendMessage call to the subagent.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZTsJr0UyHxruLKQsNGjN_g.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U4lTMXNtai5Vh7mHgBmdrQ.png" /></figure><p>You can also combine to a single prompt to execute both skills immediately in parallel like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/994/1*t0cq9nmtvAR2LilTDEOuQQ.png" /></figure><blockquote>In a subagent: Investigate this error with the solutions-architect-agent skill and help suggest a fix: HTTP 429<br>The request was aborted because there was no available instance.</blockquote><blockquote>In a different subagent: use my kid code reviewer to provide a code review.</blockquote><h3>Whats next?</h3><p>This post covered Antigravity Skills and how to use subagents. In my next post I’ll cover how to coordinate multiple agents and pass output from one agent to another. In the mean time, read all about <a href="https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system">Agentic AI design patterns</a> in the Google Cloud Architecture Center.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b303bf05085b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/deep-dive-antigravity-agent-skills-b303bf05085b">Deep Dive: Antigravity Agent Skills</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tips for Google Cloud Architects]]></title>
            <link>https://medium.com/google-cloud/tips-for-google-cloud-architects-31615d71c2c2?source=rss-6f6d7bda3a17------2</link>
            <guid isPermaLink="false">https://medium.com/p/31615d71c2c2</guid>
            <category><![CDATA[gcp]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[cloud-computing]]></category>
            <dc:creator><![CDATA[George Mao]]></dc:creator>
            <pubDate>Wed, 29 Apr 2026 17:16:25 GMT</pubDate>
            <atom:updated>2026-04-30T02:23:18.415Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zqXlobkktIFi_0v9xydQVA.jpeg" /></figure><p>It took over a year of GCP experience to learn these important GCP tricks. Here’s a list that all Google Cloud Developers &amp; Architects should be aware of!</p><h3>VPC Service Controls don’t protect VPC resources</h3><p>I assumed that the <a href="https://docs.cloud.google.com/vpc-service-controls/docs/overview">VPC Service Controls</a> (SC) feature protects resources in a VPC from unauthorized network access. Here’s a snippet from the docs:</p><blockquote>While IAM enables granular <em>identity-based access control</em>, VPC Service Controls enables broader <em>context-based perimeter security</em>, including controlling data egress across the perimeter.</blockquote><blockquote>VPC Service Controls lets you define security policies that prevent access to Google-managed services outside of a trusted perimeter, block access to data from untrusted locations, and mitigate data exfiltration risks.</blockquote><p>For example, I thought I could protect services hosted on Google Compute Engine or Cloud SQL. Turns out that is not correct.</p><blockquote>VPC SC only protects Google managed APIs by limiting network access to VPCs that are authorized.</blockquote><p>VPC SC only protects “Google Managed APIs” or more accurately, the <strong>Control plane </strong>from being accessed by unauthorized VPC resources.<strong> </strong>This includes Google APIs used to <em>manage</em> your resources or fully managed services behind an API (ie. GCS, BQ, PubSub). It does not protect access to the <strong>Data plane </strong>— anything <em>running on </em>those resources.</p><p>For example, if you have a web server running on Google Cloud Engine — this is <strong>not</strong> protected by VPC SC. However, the Google APIs that manage the GCE VM can be protected.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*oZgehT0tIey4yp3q.png" /><figcaption>From the <a href="https://docs.cloud.google.com/vpc-service-controls/docs/overview">docs</a>.</figcaption></figure><p>Lastly, it gets a little confusing when we apply VPC SCs to fully managed Google Cloud services that expose <em>ingress</em> points. Cloud Run is a good example. Cloud Run Services expose a fully managed <a href="https://docs.cloud.google.com/run/docs/triggering/https-request">HTTP Endpoint —</a> this is technically the data plane, however, because Cloud Run is fully managed in a GCP owned project, it is in scope for VPC SC protection.</p><p>To protect assets not in scope for VPC SC configure Firewall Rules with Network Tags and/or use <a href="https://docs.cloud.google.com/firewall/docs/about-firewalls">Cloud NGFW</a>.</p><h3>Identity Aware Proxy (IAP) cannot be used with Cloud CDN</h3><p><a href="https://cloud.google.com/security/products/iap?e=48754805">IAP</a> is one of the coolest services I’ve used at GCP — it uses Google’s Zero-trust technology to control access to cloud-based resources without requiring a VPN. You can use IAP for SSH/RDP access or for HTTP/Web access to controlled resources. Many services support IAP, such as Google Compute Engine, Load Balancers, and Cloud Run.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PNq_3tCpbIMxPA1bxFpMBw.png" /></figure><p>One of the most common use cases is to use IAP to easily add AuthN/Z to a web app without managing another Identitfy Provider.</p><p>Web Apps are often deployed as a Single Page App (SPA) built with Angular or React and then served by a CDN (ie Cloud CDN). This accelerates content delivery and improves cost for delivery. You <a href="https://docs.cloud.google.com/cdn/docs/setting-up-cdn-with-bucket">deploy</a> a Load Balancer and target a Backend Cloud Storage Bucket with Cloud CDN enabled.</p><p>Cloud CDN provides highly performant edge caching of assets, which makes it incompatible with IAP, because IAP evaluates every request made.</p><blockquote>You cannot enable Cloud CDN on any backend that has IAP enabled.</blockquote><p>If you need edge caching, you should use Firewall rules or NGFW to control ingress (instead of IAP). Then deploy OAuth based access control on your application.</p><h3>Cloud CDN cannot be used to secure access to a public Google Cloud Storage Bucket</h3><p>Deploy your SPA assets to a Google Cloud Storage (GCS) bucket then serve them up via Cloud CDN. The client runs on web browsers and interacts with APIs. You get all the benefits of global distribution at highly cost effective price — with no servers to scale or manage.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*8YiEprKw5isanmCx.png" /></figure><blockquote>Cloud CDN cannot access a private GCS bucket and enabling CDN on a Load Balancer does not secure the Backend bucket — the bucket must be public.</blockquote><p>Instead, create two seperate domains. One for public assets and another for the secured portion, for example: assets.iamgeorge.com and app.iamgeorge.com</p><h3>Beware of Pub Sub filtering details!</h3><p>I wrote about <a href="https://medium.com/google-cloud/top-google-cloud-pub-sub-tips-tricks-30cb0074c603">Pub/Sub Tips &amp; Tricks</a> in a previous post — but there are two major things to watchout for:</p><ul><li>You cannot configure <a href="https://docs.cloud.google.com/pubsub/docs/subscription-message-filter">filters</a> to inspect actual message <strong><em>data</em></strong>. Instead, PubSub inspects incoming message <strong><em>attributes.</em></strong> For example, you might publish a message that includes the attribute <strong><em>color</em></strong>: <strong><em>red</em></strong> to allow a subscriber to only recieve messages tagged with <strong><em>red.</em></strong></li></ul><blockquote>You should almost always include attributes to allow for useful filtering.</blockquote><pre>{<br>  &quot;data&quot;: &quot;your payload here&quot;,<br>  &quot;attributes&quot;: {<br>    &quot;color&quot;: &quot;red&quot;,<br>    ...<br>  }<br>}</pre><ul><li>Filtering will reduce your costs. But not as much as you think. In most cases you will not see a direct percent reduction in cost because filtering is applied at the <strong>Subscription</strong>, not at the Topic. You pay Throughput charges for <strong><em>every message delivered</em></strong> to the Topic regardless of filtering configuration, but filtered messages do not incur any data transfer charges from Subscriptions to the subscriber.</li><li>Lets say a Topic has 2 Subscriptions. If you don’t apply a filter on either Subscription, all messages published to the Topic will flow to both Subscriptions. You incur Throughput costs 3x (for the Topic + 2 Subs) plus 2x data transfer costs to the 2 Subscribers.</li><li>If you apply a filter <strong><em>color:red</em></strong> on the first Sub and another filter <strong><em>color:blue</em></strong> on the second Sub you still incur 3x Throughput charges. However, data transfer charges are only incurred for messages matching the filter.</li></ul><blockquote>If you have Subscriptions with filters that have substantially disproprtional filtering percentages it’s usually better to create seperate Topics dedicated to each Subscription.</blockquote><h3>There is more than one way to obtain GCP Application Default Credentials (ADC)</h3><p><a href="https://docs.cloud.google.com/docs/authentication/application-default-credentials">Application Default Credentials</a> is the process by which Google clients (SDKs, CLI, etc) search your system for local credentials used to access Google Cloud APIs.</p><p>For my first year+ of working with Google Cloud, I only knew one way to obtain credentials for authenticating to Google APIs.</p><pre>gcloud auth application-default login</pre><p>There’s actually a slightly shorter way to do the same thing. Now I re-auth twice as fast. Courtesy of <a href="https://medium.com/u/9fb50b410609">Matthew Ashton</a> :)</p><pre>gcloud auth login --update-adc</pre><h3>Budget Alerts are not on by default</h3><p>Enable Alerts as part of the account provisioning process. Open the Budgets console and you will see a monthly spend report. In this example I set a $3000 Budget alert and various thresholds for notifications. You can choose between <strong><em>Actual</em></strong> cost or <strong><em>Forcasted</em></strong> and who you want to notify. A Pub/Sub topic is my preferred option as it can notify a group or trigger a webhook to something like a Slack channel.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NZkSbOL4x-KJFJ69j0RyVA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JdTZGHsVh25329sx9TUe5A.png" /><figcaption>Auto generated billing cost summary</figcaption></figure><p>I also recommend enabling Anomly detection. The easiest way is to use the Auto detection feature, but you can customize as needed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RaBknjCLtgpsNSan3JIESw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2MCVzxdc2Dlo9Rm47UTMug.png" /></figure><h3>Cloud Monitoring sets metrics to a Rate view</h3><p>If you view any metric, such as the Cloud Run Request count you’ll notice it gives you a Requests Per second aggregation. Generally, I’m more interested the actual number of requests than Requests Per second.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SfxbFYjdkYOr0vEQrIalXQ.png" /></figure><p>I always end up swapping the view so I can see the actual data without a Rate aggregation. Find the Aggregation field, open the drop-down, and select Configure Aligner . Then choose the actual value you want to see such as Sum, Max, Min, p95 or p99.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-t2AVKGw9CCRAAqnWlhEig.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cP-wN_q0pPSOPCFuGBOTRA.png" /></figure><p>Now you can see the actual Request count rather than the Requests Per second.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=31615d71c2c2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/tips-for-google-cloud-architects-31615d71c2c2">Tips for Google Cloud Architects</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Top Google Cloud Pub/Sub Tips & Tricks]]></title>
            <link>https://medium.com/google-cloud/top-google-cloud-pub-sub-tips-tricks-30cb0074c603?source=rss-6f6d7bda3a17------2</link>
            <guid isPermaLink="false">https://medium.com/p/30cb0074c603</guid>
            <category><![CDATA[messaging]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[pub-sub]]></category>
            <category><![CDATA[infrastructure]]></category>
            <category><![CDATA[google-cloud-pubsub]]></category>
            <dc:creator><![CDATA[George Mao]]></dc:creator>
            <pubDate>Fri, 05 Sep 2025 20:08:38 GMT</pubDate>
            <atom:updated>2025-09-10T21:08:29.645Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*fkXYpzHMEQD90fTP.png" /></figure><p>Google Cloud <a href="https://cloud.google.com/pubsub/">Pub/Sub </a>is best way to build decoupled, distributed, high scale systems that require message delivery or queuing features. This article will help you understand the best practices for efficient and cost effective use of Pub/Sub.</p><h3>Pricing</h3><p>Pricing is composed of 3 main components (monthly):</p><ol><li><a href="https://cloud.google.com/pubsub/pricing#throughput_costs">Throughput</a> ($40/TiB. $50/TiB for BQ or GCS targets) — all data published to a Topic or read from a Subscription. Measured in 1 KB increments.</li><li><a href="https://cloud.google.com/pubsub/pricing#egress_costs">Data </a>egress— all data into Pub/Sub is free. All data out to internet or between regions cost you <a href="https://cloud.google.com/vpc/network-pricing?e=48754805&amp;hl=en#vpc-pricing">money</a>.</li><li><a href="https://cloud.google.com/pubsub/pricing#storage_costs">Storage</a> ($0.27/GiB) — for using message retention on your Topic and for any message in a Subscription that is older than 1 day</li></ol><h3>A quick recap</h3><p>Pub/Sub is a global service -- this means you don’t need to deploy a Topic in every region where you run workloads. Pub/Sub <a href="https://cloud.google.com/pubsub/docs/subscription-overview">subscriptions</a> attach to a Topic so you can listen to incoming messages. Subscriptions can either be Push or Pull based. Push is the simplest since Pub/Sub will push messages to subscribers and handle authentication, scaling, and backoff/retry. Pub/Sub uses <a href="https://cloud.google.com/pubsub/docs/push#push_backoff">exponential back off </a>to optimize the push rate to your consumers.</p><blockquote>Pro tip: You should use Push unless there is a good reason to Pull</blockquote><blockquote><strong>Hint</strong>: There are very few reasons to Pull. You have to write &amp; manage code + you are wasting compute $$$ if you do this in serverless compute like Cloud Run.</blockquote><h3>Understand how Subscriptions work</h3><p>Here are the key things to remember about a subscription:</p><ul><li>Messages are duplicated from the Topic to all Subscriptions. This means Throughput charges grow in a linearly in proportion to the number of active Subscriptions</li><li>Subscriptions maintain independent control of their copy of each message. Acknowledged messages in a Subscription don’t affect any other subscriptions</li><li>Subscriptions only contain messages that arrive to the Topic <strong>after</strong> the subscription was created</li></ul><h3>Understand Message Retention</h3><p>By default <a href="https://cloud.google.com/pubsub/docs/topic-properties#message_retention_duration">Topics</a> do not retain messages, however by default <a href="https://cloud.google.com/pubsub/docs/subscription-properties#message-retention-duration">Subscriptions</a> retain messages for 7 days. You can change both of these settings at cost.</p><blockquote>Pro Tip: If you need to Seek back in time and replay previously acknowledged messages, then add Topic retention.</blockquote><h3>Delivery Format matters: Wrapped vs Unwrapped</h3><p>By default, Pub/Sub will <strong>wrap</strong> published messages in a Pub/Sub specific JSON schema in the message.datafield.</p><pre> {<br>      &quot;message&quot;: {<br>          &quot;data&quot;: &quot;[Published message as a Base64 encoded string]&quot;,<br>          &quot;messageId&quot;: &quot;2070443601311540&quot;,<br>          &quot;message_id&quot;: &quot;2070443601311540&quot;,<br>          &quot;publishTime&quot;: &quot;2021-02-26T19:13:55.749Z&quot;,<br>          &quot;publish_time&quot;: &quot;2021-02-26T19:13:55.749Z&quot;<br>      },<br>    &quot;subscription&quot;: &quot;projects/myproject/subscriptions/mysubscription&quot;<br>  }</pre><p>Pub/Sub pushes messages using a standard HTTP POST that includes a Content-Type header of application/json. This helps downstream consumers process the data appropriately.</p><p>Your code must base64 decode the message.data field as the first step. Here’s sample code for a Cloud Run Function:</p><pre>functions.http(&#39;helloHttp&#39;, async(req, res) =&gt; {<br>  try {<br>    if (req.method !== &#39;POST&#39;) {<br>      res.status(405).send(&#39;Method Not Allowed&#39;);<br>      return;<br>    }<br><br>    // Get the payload from the request body<br>    const payload = req.body;<br>    //console.log(&quot;Body:&quot;+ JSON.stringify(payload));<br><br>    if (!payload.message || !payload.message.data) {<br>      const errorMessage = &#39;Bad Request: Invalid Pub/Sub message format&#39;;<br>      console.error(errorMessage);<br>      res.status(400).send(errorMessage);<br>      return;<br>    }<br><br>    const b64Message= payload.message.data<br>    <br>    const message = Buffer.from(b64Message, &#39;base64&#39;).toString().trim()<br>    console.log(&quot;Decoded msg: &quot; + message)<br><br>    const jsonObject = JSON.parse(message);<br>    // TODO: Do something with the message<br>  <br>    // A 200 Ack&#39;s the message<br>    res.status(200).send(&#39;Success&#39;);<br>  } catch (error) {<br>    console.error(&#39;Error:&#39;, JSON.stringify(error));<br>    res.status(500).send(&#39;Error processing request&#39;);<br>  }<br>});</pre><p>Alternatively, you can <a href="https://cloud.google.com/pubsub/docs/payload-unwrapping">enable</a> Message <strong>Unwrapping </strong>(for push subs only) — which tells Pub/Sub to deliver the message published to the topic as-is without any additional information. When using Unwrapped messages, all headers will be removed from the HTTP POST request, including the <em>Content-Type</em> header.</p><p>Cloud Run will attempt to parse the incoming message from a Pub/Sub subscription according to the <em>Content-Type</em> header, however the lack of headers causes delivery issues for certain HTTP frameworks.</p><blockquote>Pro tip: When using Unwrapped, I suggest adding back the metadata (which includes headers) so you don’t run into delivery and parsing issues downstream</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GETBtrcoYYL0KWgXaY_p8g.png" /></figure><h3>Best Practices</h3><h4>Message Sizes</h4><p>Throughput costs are measured in 1 KB increments, which means a 1 byte message costs the same as a 1KB message. If all of your messages are small, you should use <a href="https://cloud.google.com/pubsub/docs/batch-messaging">Batch settings </a>to maximize the 1KB metering window and deliver multiple messages in a single publish.</p><p>For example, lets assume each message averages 300 bytes. That means you can fit 3 messages per batch to maximize the 1KB window.</p><p>If you’re using the <a href="https://googleapis.dev/nodejs/pubsub/latest/global.html#BatchPublishOptions">Node APIs</a>, set the batching configuration for maxMessages = 3.Always set the maxMilliseconds option to prevent delays if there is a low throughput of messages being published.</p><p>Then call the publishMessage method multiple times. The client will automatically buffer your messages and deliver them together.</p><pre>  const publishOptions = {<br>    batching: {<br>      maxMessages: 3, // Choose a value that gets you close to 1KB<br>      maxMilliseconds: 3000, // Don&#39;t delay too long. Publish at 3 seconds<br>    },<br>  };<br>  const batchPublisher = pubSubClient.topic(topicNameOrId, publishOptions);<br><br>  const promises = [];<br>  for (let i = 0; i &lt; 3; i++) {<br>    promises.push(<br>      (async () =&gt; {<br>        const messageId = await batchPublisher.publishMessage({<br>          data: dataBuffer,<br>        });<br>        console.log(`Message ${messageId} published.`);<br>      })(),<br>    );<br>  }<br>  await Promise.all(promises);</pre><h4>Process messages within 1 day</h4><p>Messages sitting in a Subscription for less than 1 day do not incur Storage charges. Try to process and Acknowledge messages within 1 day to minimize costs. <a href="https://cloud.google.com/pubsub/docs/monitoring#monitoring_the_backlog">Monitor</a> the Unacknowledged Message metric — if this metric increases over time, that means your consumer is not keeping up.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DkweWfYV7jqfmt-AhUVYWQ.png" /><figcaption>This shows a large number of messages that are not being processed by the consumer</figcaption></figure><p>Confirm the Oldest unacknowledged message age metric to understand how far behind your consumer is.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Js9OouWMZIrOUJag4LvcwA.png" /><figcaption>An increasing age means your consumer is unable to keep up with incoming messages and falling behind</figcaption></figure><h4>Filter your Subscriptions</h4><p>Always include <a href="https://cloud.google.com/pubsub/docs/publisher#using-attributes">attributes</a> when you publish messages. Attributes allow you to enable a <a href="https://cloud.google.com/pubsub/docs/subscription-message-filter">filter</a> and ensure only valid messages make it to a Subscription.</p><p>This will prevent your consumer from attempting to process invalid messages, ultimately improving processing throughput and improving overall cost efficiency.</p><blockquote>Filtered messages cut out the entire data transfer component of the pricing but you still pay the Throughput component.</blockquote><p>The cost savings can be very significant, especially if you have large message sizes or high scale volume.</p><h4>Use a DLQ</h4><p>If you don’t use a DLQ, messages that exceed the Retain duration or Retry limit are deleted forever. This usually happens for a few reasons:</p><ol><li>Your consumer attempts to process a message but encounters an error or does not Acknowledge the message</li><li>Your consumer is unable to keep up with incoming publish throughput and messages start exceeding the Retain setting</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/751/1*XEV6874VpLh7N4JbPYQyhw.png" /></figure><p>Setup a different process to inspect the DLQ and redrive the messages back into the main Topic.</p><h4>Test throughput end to end</h4><p>It’s critical to understand how your consumers respond to different scale of incoming messages. You should understand CPU Utilization and Memory consumption so you can tune your consumer appropriately.</p><p>The only way to do this is to load test the flow of messages from end to end. You can write your own load generator that publishes messages to Pub/Sub — but you have to own code and deploy this to compute.</p><p>I recommend using Google Cloud’s <a href="https://cloud.google.com/products/dataflow">Dataflow</a> service to generate artificial load. There’s a built in Template that you can use called the <a href="https://cloud.google.com/dataflow/docs/guides/templates/provided/streaming-data-generator">Streaming Data Generator.</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/766/1*Xq5gls1XVXmSN9WT9uA2xQ.png" /></figure><p>Set the Output Rate to something you want to simulate:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/766/1*ZoPAnzGpkvStNZ-odSK1Iw.png" /></figure><p>Enable the Streaming Engine so you offload the publisher compute to Google owned infrastructure.</p><p>Next, you will need to provide Dataflow with a JSON template that will be used to generate random messages to push to PubSub. All possible configs and functions are available <a href="https://github.com/vincentrussell/json-data-generator">here</a>. Here’s a sample template that pushes a fictitious order in JSON. Upload it to GCS for reference in the next step.</p><pre>{<br>  &quot;id&quot;: {{integer(0,1000)}},<br>  &quot;orderId&quot;: &quot;{{uuid()}}&quot;,<br>  &quot;firstName&quot;:&quot;{{firstName()}}&quot;,<br>  &quot;email&quot;:&quot;{{email()}}&quot;,<br>  &quot;state&quot;:&quot;{{state()}}&quot;,<br>  &quot;product&quot;: &quot;{{random(&quot;labubu&quot;,&quot;molly&quot;,&quot;snoopy&quot;)}}&quot;,<br>  &quot;isInStock&quot;: {{bool()}},<br>  &quot;orderDate&quot;: &quot;{{date(&#39;YYYY-MM-dd&#39;)}}&quot;<br>}</pre><p>Select the JSON template from above and make sure you specify the Output Sink as <strong>PUBSUB.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/771/1*Rfo7WhnNKCOotE76qIP7Ug.png" /></figure><p>Finally, if you removed your <strong>default</strong> network and made a custom one, you will need to specify the network and subnetwork (subnet) you want to deploy to. <strong>Pay attention to the subnetwork format.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/731/1*AgI2kdHdBoVF8sNCUtCNlw.png" /></figure><p>Dataflow will provision all resources and launch the job. This usually takes a few minutes and then slowly ramp up the RPS to the max you configured. You can watch the progress:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/379/1*j9q_Wyk1XlrIlMEPBA0DrQ.png" /></figure><h3>Summary</h3><p>This post doesn’t cover a bunch of features you should be aware of, such as:</p><ul><li><a href="https://cloud.google.com/pubsub/docs/create-bigquery-subscription">BigQuery</a> Subscriptions</li><li><a href="https://cloud.google.com/pubsub/docs/create-cloudstorage-subscription">Cloud Storage </a>Subscriptions</li><li><a href="https://cloud.google.com/pubsub/docs/ordering">Ordering</a> control</li></ul><p>Reach out to me if you need any help.</p><p>Happy Coding!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=30cb0074c603" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/top-google-cloud-pub-sub-tips-tricks-30cb0074c603">Top Google Cloud Pub/Sub Tips &amp; Tricks</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Power your MCP servers with Google Cloud Run]]></title>
            <link>https://medium.com/google-cloud/power-your-mcp-servers-with-google-cloud-run-978df61615f4?source=rss-6f6d7bda3a17------2</link>
            <guid isPermaLink="false">https://medium.com/p/978df61615f4</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[cloudrun]]></category>
            <category><![CDATA[mcps]]></category>
            <dc:creator><![CDATA[George Mao]]></dc:creator>
            <pubDate>Thu, 10 Jul 2025 02:44:55 GMT</pubDate>
            <atom:updated>2025-07-14T13:43:34.388Z</atom:updated>
            <content:encoded><![CDATA[<h3>What is the Model Context Protocol (MCP)?</h3><p>MCP was developed because we realized LLMs need a standard way to access downstream resources that provide <strong><em>deterministic</em></strong> operations. The downstream resources typically use a wide ranges of protocols such as REST, GraphQL, gRPC/ProtoBuf, or SOAP/XML.</p><p>MCP is an attempt to standardize how AI clients interface with these downstream resources. Those resources are exposed to the AI clients as <strong>Tools</strong>. To make your resource consumable by AI clients all you have to do is implement the <a href="https://modelcontextprotocol.io/specification/2025-06-18">MCP specification</a> which requires the message format to be <a href="https://www.jsonrpc.org/specification">JSON-RPC 2.0</a>.</p><p>My friend <a href="https://medium.com/u/368c7eb10ed">Heeki Park</a> describes it best in his <a href="https://heeki.medium.com/understanding-the-evolution-of-mcp-through-the-lens-of-apis-c651086ecc99">article</a>:</p><blockquote>MCP is an abstraction layer for the downstream resource, using the MCP client with a standard JSON-RPC 2.0 wire format to the MCP server.</blockquote><h4>So .. what does the MCP spec look like?</h4><p>The spec is complex but the best place to start by getting familar with the two types of messages defined by the spec. The first is the tools/list message. This allows you to list all available <strong>Tools </strong>and the input schema required for each tool. A request to list tools looks like this:</p><pre>{<br>  &quot;jsonrpc&quot;: &quot;2.0&quot;,<br>  &quot;id&quot;: 1,<br>  &quot;method&quot;: &quot;tools/list&quot;,<br>  &quot;params&quot;: {}<br>}</pre><p>The second message is tools/call. This allows you to invoke one of the tools discovered in the tools/list message.</p><pre>{<br>    &quot;jsonrpc&quot;: &quot;2.0&quot;,<br>    &quot;method&quot;: &quot;tools/call&quot;,<br>    &quot;params&quot;: {<br>        &quot;name&quot;: &quot;add&quot;,<br>        &quot;arguments&quot;: {<br>            &quot;a&quot;: 1,<br>            &quot;b&quot;: 2<br>        }<br>    }<br>}</pre><p>All operations occur over HTTP POST requests.</p><h4>Why do you need a MCP Server?</h4><p>You <em>could</em> implement the MCP interface at every resource — but this approach is not scalable and tough to manage. This is where a MCP Server comes in — it’s better to abstract the MCP interface via a <strong><em>centralized</em></strong> service called the MCP Server. The MCP Server has two main responsibilities:</p><ul><li>Implement the integration into the downstream Resource. For example, this might be a REST or GraphQL call to a Weather API</li><li>Implement the MCP spec to expose the Resource to AI applications as a tool— think of it as a proxy layer.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/0*g3tqAolo7ZzVVnOh.png" /><figcaption>Courtesy of <a href="https://medium.com/u/368c7eb10ed">Heeki Park</a>.</figcaption></figure><h3>Build your own Serverless MCP Server</h3><p>Google <a href="https://cloud.google.com/run">Cloud Run</a> is the perfect service to operate a MCP Server. Run is fully Serverless and will scale to meet the demands of AI Apps that consume your Tools. Run will handle all of the arbitrary tasks like OS management, patching, and autoscaling.</p><p>For this walk through, we will create a MCP Server using Python, deploy it to Cloud Run, and test it.</p><p>Create a directory to work in and then git clone this project:</p><pre>git clone https://github.com/georgemao/mcp-on-cloudrun.git</pre><h4>Install UV</h4><p>UV is a Python package manager similar to Pip. Install it using <a href="https://docs.astral.sh/uv/getting-started/installation/">the method</a> that’s most appropriate for your development environment.</p><h4>Init the env</h4><p>This will install all dependencies defined in the <a href="https://github.com/georgemao/mcp-on-cloudrun/blob/main/pyproject.toml">pyproject.toml</a> and create a virtual Python env for the project.</p><pre>uv sync</pre><h4>Deploy from Source to Cloud Run</h4><p>Take a look at the source code (<a href="https://github.com/georgemao/mcp-on-cloudrun/blob/main/server.py">server.py)</a>. It’s a simple calculator resource with two tools: <strong><em>add</em></strong> and <strong><em>subtract</em></strong>. It uses <a href="https://github.com/jlowin/fastmcp">FastMCP</a> to implement the MCP spec. Adding the @mcp.tool annotation exposes a method as a tool.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0JSqSfzlkSHBbgfeLR5_oQ.png" /></figure><p>Next, lets deploy the service to Run. This will use Cloud Build to create a Docker image, push it to Artifact Registry, and deploy as a service to Cloud Run.</p><p>Note: we are not allowing unauthenticated calls to the MCP Server by specifying the no-allow-unauthenticated flag.</p><pre>gcloud run deploy mcp-server --no-allow-unauthenticated --region=us-central1 --source .</pre><p>You goto the <a href="https://console.cloud.google.com/cloud-build/builds">Cloud Build console</a> and verify that the build was successful.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TSYk32V-vgJt_0o5h91OVQ.png" /></figure><h4>Test your server</h4><p>We will proxy localhost calls to Cloud Run and provide the correct authentication. This will prompt you to enter your IAM account password. The account you’re using must have permissions to <strong><em>invoke</em></strong> Run services. If necessary, add this permission to your IAM role: roles/run.invoker</p><pre>gcloud run services proxy mcp-server --region=us-central1</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*L1LflOBk1J3wW8iAUv8cgA.png" /></figure><p>Now all calls to localhost are proxied out to your MCP server on Run and include the proper Authorization header.</p><p>If you execute a tools/list call to the server, you will get a detailed JSON response that describes all of the supported tools (add &amp; subtract).</p><pre>{<br>  &quot;tools&quot;: [<br>    {<br>      &quot;name&quot;: &quot;add&quot;,<br>      &quot;description&quot;: &quot;Use this to add two numbers together.\n\nArgs:\n    a: The first number.\n    b: The second number.\n\nReturns:\n    The sum of the two numbers.\n&quot;,<br>      &quot;inputSchema&quot;: {<br>        &quot;type&quot;: &quot;object&quot;,<br>        &quot;properties&quot;: {<br>          &quot;a&quot;: {<br>            &quot;title&quot;: &quot;A&quot;,<br>            &quot;type&quot;: &quot;integer&quot;<br>          },<br>          &quot;b&quot;: {<br>            &quot;title&quot;: &quot;B&quot;,<br>            &quot;type&quot;: &quot;integer&quot;<br>          }<br>        },<br>        &quot;required&quot;: [<br>          &quot;a&quot;,<br>          &quot;b&quot;<br>        ]<br>      }<br>    },<br>    {<br>      &quot;name&quot;: &quot;subtract&quot;,<br>      &quot;description&quot;: &quot;Use this to subtract two numbers.\n\nArgs:\n    a: The first number.\n    b: The second number.\n\nReturns:\n    The difference of the two numbers.\n&quot;,<br>      &quot;inputSchema&quot;: {<br>        &quot;type&quot;: &quot;object&quot;,<br>        &quot;properties&quot;: {<br>          &quot;a&quot;: {<br>            &quot;title&quot;: &quot;A&quot;,<br>            &quot;type&quot;: &quot;integer&quot;<br>          },<br>          &quot;b&quot;: {<br>            &quot;title&quot;: &quot;B&quot;,<br>            &quot;type&quot;: &quot;integer&quot;<br>          }<br>        },<br>        &quot;required&quot;: [<br>          &quot;a&quot;,<br>          &quot;b&quot;<br>        ]<br>      }<br>    }<br>  ]<br>}</pre><h4>Programmatic testing</h4><p>This Python code connects to localhost and lists the available Tools. Then it calls the <strong>add</strong> and <strong>subtract</strong> tool. Notice the method for calling both tools is the same: client.call_tool(). The only difference is the parameters being passed in.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*a5ic0XtI4WGW-5MSUkYiSw.png" /><figcaption>Full source code is available here: <a href="https://github.com/georgemao/mcp-on-cloudrun/blob/main/test_server.py">test_server.py</a></figcaption></figure><p>Execute the test:</p><pre>uv run test_server.py</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5jM1COU7TqEVzSMYuO0UOQ.png" /></figure><h4>UI testing with MCP Inspector</h4><p>If you prefer to use a GUI based tool to test your MCP Server, <a href="https://modelcontextprotocol.io/docs/tools/inspector">MCP Inspector</a> is built for this purpose. Think of it as Postman for MCP.</p><p>Launch it with this command:</p><pre>npx @modelcontextprotocol/inspector</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9A4h-TEXiSAt_EIvKEpJ0g.png" /></figure><p>Copy the <strong><em>Session Token,</em></strong> enter it into the Inspector Configuration, and click <strong><em>Connect</em></strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eMPzvBMmc6yUJIMNxOQVwQ.png" /></figure><p>You should be able to list tools and call them through the UI:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uY9EJlmBWR5kBS4IHPPLdg.png" /></figure><p>Let’s see what the requests actually look like. Open your <strong><em>Developers Tools </em></strong>and select the <strong><em>Network</em></strong> tab.</p><p>Run the <strong><em>add</em></strong> or <strong><em>subtract</em></strong> tool from MCP Inspector and you should see two requests:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9ptt5uBgFQgDvuqbxcxerw.png" /></figure><p>The first is the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS">Cross Origin Resource Sharing</a> (CORS) preflight request. Open the second one and you will see the tools/call message along with the parameters you specified in the UI.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yUmHolJiWpIscPLXRR9cfw.png" /></figure><p>Go to the Cloud Run console and open the <strong><em>mcp-server</em></strong> service. Observe a few of the metrics. In particular, take a look at the <strong><em>Container Instance Count </em></strong>metric — it scales up and down in response to the tests we performed. Also in relation, the B<strong><em>illable Container Instance time</em></strong> metric confirms the nature of serverless compute — there is no cost until actual requests arrive.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GpkPKdjwiWncC7Kra8j01A.png" /></figure><h3>Summary</h3><p>You should now understand the purpose of MCP and why a MCP Server can scale your AI workloads efficiently.</p><p>MCP is a lot more complex than what we covered here — for example, we haven’t talked about <a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization">Authorization</a>/security, monetization, version control, or advanced features like argument <a href="https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion">auto completion</a>.</p><p>Some resources that can help you learn:</p><ul><li><a href="https://cloud.google.com/run/docs/host-mcp-servers">Host MCP Servers on Cloud Run</a></li><li><a href="https://medium.com/u/368c7eb10ed">Heeki Park</a>’s Oauth + MCP <a href="https://heeki.medium.com/understanding-oauth2-and-implementing-identity-aware-mcp-servers-221a06b1a6cf">security post</a></li><li>MCP on <a href="https://en.wikipedia.org/wiki/Model_Context_Protocol">Wikipedia</a></li><li><a href="https://cloud.google.com/blog/topics/developers-practitioners/build-and-deploy-a-remote-mcp-server-to-google-cloud-run-in-under-10-minutes?e=48754805">Build and Deploy</a> a Remote MCP Server to Google Cloud Run in Under 10 Minutes</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=978df61615f4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/power-your-mcp-servers-with-google-cloud-run-978df61615f4">Power your MCP servers with Google Cloud Run</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cloud Run + GPU = Serverless LLMs!]]></title>
            <link>https://medium.com/google-cloud/cloud-run-gpu-serverless-llms-d569ffc4af50?source=rss-6f6d7bda3a17------2</link>
            <guid isPermaLink="false">https://medium.com/p/d569ffc4af50</guid>
            <category><![CDATA[cloud-run-gpu]]></category>
            <category><![CDATA[runcloud]]></category>
            <category><![CDATA[machine-learning-ai]]></category>
            <category><![CDATA[gcp-app-dev]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <dc:creator><![CDATA[George Mao]]></dc:creator>
            <pubDate>Fri, 30 May 2025 18:46:48 GMT</pubDate>
            <atom:updated>2025-05-31T07:55:38.883Z</atom:updated>
            <content:encoded><![CDATA[<p>Did you know you can run incredibly powerful LLMs on <a href="https://medium.com/u/4f3f4ee0f977">Google Cloud</a>’s Serverless compute platform?</p><p>I’ll show you how you can use <a href="https://cloud.google.com/run">Google Cloud Run</a> to serve your own LLM for about <strong>$1/hour.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/576/1*i9vYcIgU7CVkpiuLMgxayw.png" /></figure><h3>Google Cloud Run + GPU + Gemma3</h3><p>My favorite way to build applications is on top of <a href="https://cloud.google.com/run">Google Cloud Run</a> — it lets you build serverless applications at any scale and it’s the <strong>only</strong> serverless compute technology from any cloud provider that supports GPUs. If you’re not familiar with Gemma — its a family of open source LLMs that are based off the Google Gemini models. Gemma is designed to be lightweight and requires just a <em>single</em> GPU/TPU. It supports up to 128K tokens and is multi-modal. You can read in detail about <a href="https://ai.google.dev/gemma/docs/core">Gemma3 here</a>.</p><p>Before we begin, here are a few things that Cloud Run will require when you enable <a href="https://cloud.google.com/blog/products/application-development/run-your-ai-inference-applications-on-cloud-run-with-nvidia-gpus?e=48754805">Nvidia L4 GPUs </a>(24GB):</p><ul><li>Minimum configuration is 4 vCPU and 16G RAM</li><li>Instance billing is required (instead of request billing)</li></ul><h3>Build your Serverless LLM</h3><p>Gemma3 has 4 different <a href="https://ai.google.dev/gemma/docs/core#sizes">model sizes</a> (1B, 4B, 12B, 27B) — we will be using the 4B model size but you should be able to deploy the 12B model as well.</p><p>Here’s what we’ll do at a high level:</p><ul><li>Use <a href="https://ollama.com/">Ollama</a> to serve the Gemma3:4B model on Cloud Run</li><li>Store the model on a GCS Bucket and <a href="https://cloud.google.com/run/docs/configuring/services/cloud-storage-volume-mounts">mount it as a Volume to</a> Cloud Run</li><li>Use <a href="https://www.openwebui.com/">OpenWebUI</a> to interact with the model and provide a UI for users</li><li>Deploy and host as a 100% serverless capablity on Cloud Run</li></ul><h4>Enable the required APIs:</h4><pre>gcloud services enable run.googleapis.com \<br>    cloudbuild.googleapis.com \<br>    storage.googleapis.com \<br>    artifactregistry.googleapis.com</pre><h4>Download the Gemma3 4B model</h4><p>Create a directory to work in:</p><pre>mkdir serverless-gemma-demo<br>cd serverless-gemma-demo</pre><p>Download Ollama via their <a href="https://ollama.com/download">website</a> or run command line:</p><pre>curl -fsSL https://ollama.com/install.sh | sh</pre><p>Pull the Gemma3 4B Model &amp; test it:</p><pre>ollama serve<br>ollama pull gemma3:4b<br>ollama run gemma3</pre><h4>Push the model to a GCS Bucket</h4><p>You can do this via the console or CLI:</p><pre>gcloud storage buckets create gs://[your gcs bucket]<br>gsutil cp -r ~/.ollama/models gs://[your gcs bucket]</pre><h4>Create the LLM Image for Cloud Run (Ollama + Gemma3)</h4><p>Create a file called: Dockerfile. It’s contents should be:</p><pre>FROM ollama/ollama<br><br># Listen on all interfaces, port 11434<br>ENV OLLAMA_HOST 0.0.0.0:11434<br><br># Store model weight files in /models<br>ENV OLLAMA_MODELS /models<br><br># Reduce logging verbosity<br>ENV OLLAMA_DEBUG false<br><br># Never unload model weights from the GPU<br>ENV OLLAMA_KEEP_ALIVE -1 <br><br># Store the model weights in the container image<br>ENV MODEL gemma3:4b<br>RUN ollama serve &amp; sleep 5 &amp;&amp; ollama pull $MODEL <br><br># Start Ollama<br>ENTRYPOINT [&quot;ollama&quot;, &quot;serve&quot;]</pre><p>Build &amp; Push the Image to Artifact registry:</p><pre>gcloud artifacts repositories create ollama-sidecar-gemma-repo --repository-format=docker \<br>    --location=us-central1 --description=&quot;Serverless Ollama + OpenWebUI Gemma demo&quot; \<br>    --project=[your gcp project id]</pre><pre>gcloud builds submit \<br>   --tag us-central1-docker.pkg.dev/[your gcp project id]/ollama-sidecar-gemma-repo/ollama-gemma-4b \<br>   --machine-type e2-highcpu-32</pre><h4>Create the OpenWebUI Image for Cloud Run</h4><p>Pull the image from Dockerhub. Make sure you specify the platform as linux/amd64. Cloud Run requires an x86 based image.</p><pre>docker pull --platform linux/amd64 ghcr.io/open-webui/open-webui:main</pre><p>Configure Docker to use your GCP creds so you can push the image to AR:</p><pre>gcloud auth configure-docker us-central1-docker.pkg.dev</pre><p>Tag and push the image to AR:</p><pre>docker tag ghcr.io/open-webui/open-webui:main us-central1-docker.pkg.dev/[your gcp project id]]/ollama-sidecar-gemma-repo/openwebui<br>docker push us-central1-docker.pkg.dev/[your gcp project id]/ollama-sidecar-gemma-repo/openwebui</pre><h4>Deploy the Cloud Run Service</h4><p>Create a service.yaml file to define the service. We’ll be deploying two containers on the same Cloud Run Service. One for the OpenWebUI and another for the Ollama / Gemma3 LLM. Review the service.yaml file <a href="https://github.com/georgemao/serverless-llm-gcp">here</a>. It’s currently configured to disable GPU <a href="https://cloud.google.com/run/docs/configuring/services/gpu#zonal-redundancy">Zonal Redundancy</a> and allow a max of 5 concurrent request per Run instance.</p><pre>gcloud  run services replace service.yaml</pre><p>Deployment will take 5–8 minutes and result in a service url you can use to test it out.</p><pre>https://ollama-sidecar-gemma-demo-[project id].us-central1.run.app</pre><p>Here’s what you should see:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-YAP3F0X-llLHeyFlEMApA.png" /></figure><h3>Test out your LLM</h3><p>Try asking it a few different prompts. Start with a text based prompt like:</p><blockquote>Explain a SQL Database. Respond as if you were talking to a child. Keep it concise.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xn5f2gV2Rw4Exfj-NQ1zJQ.png" /></figure><p>Then try the multi-model features. Paste in an architecture diagram, like this one and ask it:</p><blockquote>Explain this architecture diagram</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0ULWu1nn2ADzBJWulvGxRg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9C8IlD6JulzHOavB-S3xoA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6gUeq8WIpxKTLlBkRjYWHQ.png" /></figure><h3>Review</h3><p>Check your metrics to understand how Run scales your containers. For example, the Instance Count metric shows the service scaling up (and down to zero).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GxmylRK6T3uolH4tU27cmg.png" /></figure><p>The GPU memory util metric shows you how much of the 24GB your service is using. Here I can tell Gemma3:4B is only uses about 20% of the 24GB available GPU memory.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hbPXTGOPyx3I09ugaVj05A.png" /></figure><p>See all avaible metrics <a href="https://cloud.google.com/run/docs/monitoring">here</a>.</p><h3>Summary</h3><p>For simplicity we deployed a single Cloud Run Service to host both the <strong>OpenWebUI</strong> front end and the <strong>Ollama/Gemma3 LLM</strong> backend.</p><ul><li>The service configuration allows the service to scale down to 0 for maximum cost savings.</li><li>A cold start from zero will take ~15–20 seconds. During this time, users will experience a delay in responsiveness.</li><li>The entire architecture is <em>100% serverless </em>and costs about <strong>$1/hour!</strong></li></ul><p>In production, you should split the service up and allow them to scale independently. You should also consider setting a <a href="https://cloud.google.com/run/docs/configuring/min-instances">min instances </a>value so your first set of users do <em>not</em> experience a cold start.</p><p>Source and config are available on <a href="https://github.com/georgemao/serverless-llm-gcp">Github here</a>.</p><p>You can experiment and deploy any downloadable model. Let me know how it goes!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d569ffc4af50" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/cloud-run-gpu-serverless-llms-d569ffc4af50">Cloud Run + GPU = Serverless LLMs!</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[4 steps to supercharge Planet Scale, Global architectures on Google Cloud]]></title>
            <link>https://medium.com/google-cloud/4-steps-to-supercharge-planet-scale-global-architectures-on-google-cloud-2aec08d150ca?source=rss-6f6d7bda3a17------2</link>
            <guid isPermaLink="false">https://medium.com/p/2aec08d150ca</guid>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[google-cloud-run]]></category>
            <category><![CDATA[cloud-architecture]]></category>
            <dc:creator><![CDATA[George Mao]]></dc:creator>
            <pubDate>Wed, 26 Mar 2025 18:36:02 GMT</pubDate>
            <atom:updated>2025-03-27T19:57:56.602Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XHg217ET9evr3TK6uXPeFw.png" /><figcaption>Reference architecture for a globally load balanced &amp; highly available, serverless application</figcaption></figure><p>In <a href="https://medium.com/google-cloud/build-a-planet-scale-global-architecture-for-modern-apps-on-google-cloud-96561750cba4">part 1 of this post</a>, I showed you how to create a globally load balanced, self-healing, auto-scaled, and secure architecture on <a href="https://medium.com/u/4f3f4ee0f977">Google Cloud</a> (Serverless of course!). In this post, we will add 3 improvements to this architecture and automate it’s deployment. Here’s what I’ll cover in this post:</p><ol><li>Optimize content delivery by adding <a href="https://cloud.google.com/cdn/docs/overview">Cloud CDN</a></li><li>Enhance security posture by adding <a href="https://cloud.google.com/security/products/iap?e=48754805&amp;hl=en">Identity Aware Proxy</a> (IAP)</li><li>Add <a href="https://cloud.google.com/products/firestore?e=48754805&amp;hl=en">Google Firestore</a> for global data persistence</li><li>Automate deployment via <a href="https://www.pulumi.com/registry/packages/gcp/">Pulumi IaC</a></li></ol><h3>Step 1: Optimize content delivery (static assets)</h3><p>The previous architecture relies on Cloud Run to respond to all incoming traffic. This is technically feasible, however it’s expensive and inefficient.</p><blockquote>The goal is to offload all static assets that don’t require any active compute to be delivered by Cloud CDN instead.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*--pjLsEAEmGnb2ZXwPMX5g.png" /><figcaption>Add Cloud CDN to serve assets</figcaption></figure><p>Cloud CDN will serve assets from the Google edge network closest to the user. The Global Load Balancer will integrate with a Backend bucket to store the actual assets. For maximum efficiency, Cloud CDN maintains a cache and attempts to serve directly from that cache instead of reaching to the <a href="https://cloud.google.com/storage/docs/buckets">Google Cloud Storage</a> bucket (GCS).</p><p>We will configure all requests to the root of the domain / to be served by CDN. You can enable this at the Load Balancer console.</p><p>First, goto the <strong>Backend Configuration</strong> and create a <strong>Backend Bucket. </strong>Make sure to enable <strong>Cache static content</strong>. My bucket name is gmao-web-app. Make sure the Bucket has an index.html file that will be served by default.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*njkkbsDNyPNsji3cYQMUAw.png" /><figcaption>Load Balancer config for adding a GCS Bucket backend</figcaption></figure><p>Next, goto <strong>Routing Rules</strong> and add a path that will be served by Cloud CDN. I want the root / of the domain to serve a static web application and the /api path to be served by the global Cloud Run service that we deployed earlier. Here’s how to set that up:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kU5wn0JK9w1es0sIxEDsXA.png" /></figure><p>Finally, make sure the bucket has <a href="https://cloud.google.com/cdn/docs/setting-up-cdn-with-bucket#make_your_bucket_public">public access enabled </a>and <a href="https://cloud.google.com/storage/docs/static-website#index-page">set the default file</a> to be index.html. To set the default file, use <strong>gsutil</strong> to set the <strong>MainPageSuffix</strong> to index.html:</p><pre>gsutil web set -m index.html -e 404.html gs://gmao-web-app</pre><p>Detailed docs are available <a href="https://cloud.google.com/cdn/docs/setting-up-cdn-with-bucket">here</a> and a full walk through <a href="https://cloud.google.com/cdn/docs/quickstart-backend-bucket-console">here</a>.</p><h4>Deploy a Static Website</h4><p>One of the best ways to create a web application is to build it in <a href="https://react.dev/">React</a> and compile it as a static Single Page Application (SPA) — then deploy to your GCS bucket. Cloud CDN will serve the assets and the entire application will run client side with no web servers necessary.</p><p>Here is my <a href="https://github.com/georgemao/sample-next-react-webapp">sample Github repo</a> that show you how to build a basic React app that can be deployed to any service capable of serving HTTP traffic.</p><h3>Step 2: Enhance security with Identity Aware Proxy</h3><p><a href="https://cloud.google.com/iap/docs/concepts-overview">Identity Aware Proxy</a> (IAP) is a global Google security service that authorizes user requests to applications based on properties in the user’s request context. IAP supports integration with various 3rd party Identity providers such as Google, Facebook, etc as well as GCP project specific IAM users. Plus, it’s #serverless — there are no servers to manage or scale!</p><blockquote>The goal is to add context aware security to our application. We will enable IAP on the GALB to protect the application on Cloud Run.</blockquote><p>For this reference architecture, we will enable IAP for IAM users, not external Identities. We will protect the application on path /api. Goto the <a href="https://console.cloud.google.com/security/iap">IAP console</a>, locate the Backend for your service, and enable IAP. This will take short duration to provision — just wait until the status is <strong>OK</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gdpAI_lzEr5W95bHwtmfPw.png" /></figure><p>Then add <strong>Principals</strong> and assign them the <strong>IAP-Secured Web App User</strong> role.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wBE90rkg8lKoaaoF2EalUQ.png" /></figure><p>Any principal that has this role will be prompted to authenticate when they request path /api</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*F7OdEGZMQlOlbcG4Q8mWvg.png" /></figure><h3>Step 3: Add Global Persistence</h3><p>Google Cloud provides a wide range of database services that serve different purposes. I choose Google Cloud <a href="https://cloud.google.com/products/firestore">Firestore</a> to add global persistence to this reference architecture because it is designed to offer global scalability, multi-region replication with 99.999% SLA, strong consistency by default, and <strong>no server management</strong>. It’s a NoSQL Document database that organizes documents in groups called Collections.</p><p>Firestore’s strong consistency allows this architecture to read and write from any region with consistent results. For each user that will use the system, I preload a document with its ID set to the user’s email. The document itself stores user information as follows:</p><pre>{<br>    &quot;firstName&quot;: &quot;George&quot;,<br>    &quot;lastName&quot;: &quot;Mao&quot;,<br>    &quot;email&quot;: &quot;user@iamgeorge.altostrat.com&quot;<br>}</pre><p>After successful authentication, IAP will inject 3 headers into the downstream request as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y1QRrwcdzLoLcYa1Kq13Gw.png" /><figcaption>Auth context injection from IAP to downstream services</figcaption></figure><blockquote>The goal is to extract the authorized user context proved from IAP and use it to pull the associated document from the Firestore backend.</blockquote><p>First, we need create a new Service Account that has permissions to interact with Firestore and assign it to the Cloud Run Function. The default service account attached to Cloud Run resources does not provide any permissions. The role we need to add is roles/datastore.user.</p><p>If you are using the the console, <a href="https://cloud.google.com/iam/docs/service-accounts-create">Create a new Service Account</a> then assign the <strong>Cloud Datastore User</strong> role.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/828/1*TxjmqIJ87Scf4pf58HQziA.png" /></figure><p>If you are using the <strong>glcoud</strong> <strong>cli</strong>, just execute the following commands:</p><pre>gcloud iam service-accounts create SERVICE_ACCOUNT_NAME \<br>  --description=&quot;DESCRIPTION&quot; \<br>  --display-name=&quot;DISPLAY_NAME&quot;<br><br>gcloud projects add-iam-policy-binding PROJECT_ID \<br>  --member=&quot;serviceAccount:SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com&quot; \<br>  --role=&quot;roles/datastore.user&quot;</pre><p>Second, we need to update the Cloud Run Function code to:</p><ol><li>Extract the x-goog-authenticated-user-email header from the request</li><li>Lookup the Document in Firestore with the matching ID</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8a0UrKETj2zK_cdc98rWLA.png" /></figure><pre>let userEmail = req.headers[&#39;x-goog-authenticated-user-email&#39;];<br>console.log(userEmail);<br>// Remove the accounts.gooogle.com string<br>userEmail=userEmail.replace(&quot;accounts.google.com:&quot;, &#39;&#39;);</pre><blockquote>Note: In a real production environment, you should also validate the JWT token in the x-goog-iap-jwt-assertion header</blockquote><p>Get the document in Firestore that has the same ID:</p><pre>let doc = await readDoc(userEmail);<br><br>async function readDoc(userEmail) {<br>    try {<br>        console.log(`Reading document: ${userEmail}`)<br>        const document = firestore.collection(&#39;person&#39;).doc(userEmail);<br>        <br>        // Read the document.<br>        const doc = await document.get();<br>        if (!doc.exists) {<br>            console.log(&#39;No such document!&#39;);<br>            return;<br>        }<br>        console.log(&#39;Read the document&#39;);<br>        console.log(doc.data());<br><br>        return doc.data();<br>    } <br>    catch (error) {<br>        console.error(&#39;Error retrieving Firestore document:&#39;, error);<br>        return null;<br>  }<br>}</pre><p>Full updated code is available on in my <a href="https://github.com/georgemao/multi-region-demo">Github repo here.</a></p><h3>Step 4: Automate deployment</h3><p><a href="https://www.pulumi.com/registry/packages/gcp/">Pulumi IaC</a> is a great way to describe your Cloud architecture using common programming languages you already know. It has significant support for GCP and automates all deployment tasks.</p><blockquote>The goal is to describe all major components of this architecture using Pulumi NodeJs libraries and fully automated the deployment</blockquote><p>I’ll cover some of the key configurations below. To create the Cloud Run resources: Stage the code as a zip file in a GCS bucket, then deploy the Cloud Run Function.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RQCIsRHVLqRzl0bqRjPRBw.png" /><figcaption>Create a GCS Bucket and push the function code as a Zip file</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ApgAvpWJWu1bLRZ3R09WeQ.png" /><figcaption>Deploy a new Cloud Run Function</figcaption></figure><p>To create the Firestore Database and add a document:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NfnKzTiDNdVVoxydoPNUJQ.png" /><figcaption>Create the Firestore DB and add a sample document</figcaption></figure><p>The source code that handles the deployment via <a href="https://github.com/georgemao/pulumi-gcf-multi-region-proj">Pulumi is available here</a> — including configuration of all other resources in this architecture, such as the Global ALB, Cloud CDN, and Firestore.</p><p>Run pulumi up to execute the deployment. You should see a diff that shows the resources that will be deployed. Confirm these are accurate and select yes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JHRAeJhvTJseq0mBPc04Fg.png" /></figure><h3>Summary</h3><p>In <a href="https://medium.com/google-cloud/build-a-planet-scale-global-architecture-for-modern-apps-on-google-cloud-96561750cba4">part 1 of this post</a>, I showed you how to create a globally load balanced architecture.</p><p>In this post, we enhanced the architecture by adding global content delivery, security at the API layer, and global persistence capabilities.</p><p>Review the source code for:</p><ul><li><a href="https://github.com/georgemao/sample-next-react-webapp">Static web app deployed to Cloud CDN</a></li><li>Global, Serverless Cloud Run <a href="https://github.com/georgemao/multi-region-demo">Functions</a> backend</li><li>Deployment automation via <a href="https://github.com/georgemao/pulumi-gcf-multi-region-proj">Pulumi</a></li></ul><p>Reach out to me if you have any questions &amp; Happy Coding :)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2aec08d150ca" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/4-steps-to-supercharge-planet-scale-global-architectures-on-google-cloud-2aec08d150ca">4 steps to supercharge Planet Scale, Global architectures on Google Cloud</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Build a planet scale, global architecture for modern apps on Google Cloud]]></title>
            <link>https://medium.com/google-cloud/build-a-planet-scale-global-architecture-for-modern-apps-on-google-cloud-96561750cba4?source=rss-6f6d7bda3a17------2</link>
            <guid isPermaLink="false">https://medium.com/p/96561750cba4</guid>
            <category><![CDATA[google-cloud-run]]></category>
            <category><![CDATA[gcp]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <dc:creator><![CDATA[George Mao]]></dc:creator>
            <pubDate>Mon, 03 Mar 2025 13:02:38 GMT</pubDate>
            <atom:updated>2025-03-26T18:36:50.350Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qeoqPwQZsDC5Scg_lFd70Q.png" /></figure><p>Serverless is the best way to build modern, cloud applications — I let <a href="https://cloud.google.com/">Google Cloud</a> handle all of the heavy lifting of server management, operating systems, patching, and scaling so I can focus on building my application.</p><p>In my previous post, I <a href="https://medium.com/google-cloud/go-serverless-with-google-cloud-run-functions-376b065c6c48">showed why Google Cloud Run</a> is the best serverless, compute runtime. In this post, I’ll show you a reference architecture that demonstrates a globally load balanced, self-healing, auto-scaled, and secure environment— all without managing a single server! We will enhance this architecture in <a href="https://georgemao.medium.com/4-steps-to-supercharge-planet-scale-global-architectures-on-google-cloud-2aec08d150ca">part two</a>.</p><h3>High Level Architecture</h3><p>There are four major components in this architecture that make this possible. We will deep dive into each component and cover the integration details.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-TOtooYYRSabwUq1ZaWikA.png" /><figcaption>Cloud DNS → Cloud Armor → Cloud Load Balancing → Cloud Run</figcaption></figure><ol><li><a href="https://cloud.google.com/dns">Cloud DNS</a> — allows you to configure a globally resolvable domain name that points to your Global Load Balancer</li><li><a href="https://cloud.google.com/security/products/armor">Cloud Armor</a> — provides layer 7 protection against DDoS attacks, OWASP vulnerabilities, and a fully configurable Web Application Firewall</li><li><a href="https://cloud.google.com/load-balancing">Cloud Load Balancing</a> — provides auto-scaled, traffic balancing across regions and native integration with the other components</li><li><a href="https://cloud.google.com/run">Cloud Run</a> Functions— provides a fully managed, pay-per-use serverless compute environment to run your application logic</li></ol><h4>Cloud Run (Functions)</h4><p>Cloud Run is a regional service and is supported in every region GCP operates in. It’s entirely serverless and requires zero management. I’ll be using a Cloud Run Function (CRF) to act as the compute tier for this application. It will expose a <a href="https://cloud.google.com/functions/docs/calling/http">HTTPS endpoint</a> that allows a consumer to invoke my code. The build process to deploy a CRF looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uw1aZNn9L7n632t9g98z7w.png" /><figcaption>Source code is built, stored, and deployed — all without any servers you manage</figcaption></figure><p>To get started, let’s deploy a Cloud Run Function. You can use <a href="https://gist.github.com/georgemao/74c23f2af8a3b5697e3c4d5b8ef9daea">this sample application</a>. It simulates a 3 second workload and uses the <a href="https://cloud.google.com/compute/docs/metadata/predefined-metadata-keys">Metadata</a> service to return info about where it’s hosted from. The code will retrieve the instance id it’s hosted on, the number of times the specific Run instance has been invoked, and the region it’s running as JSON:</p><pre>{<br>  &quot;instanceID&quot;: &quot;...&quot;,<br>  &quot;counter&quot;: ###,<br>  &quot;region&quot;: &quot;projects/######/regions/region-name&quot;<br>}</pre><p>The deployment process submits my source code to <a href="https://cloud.google.com/build">Cloud Build</a> (CB) which will build a docker image, store it in <a href="https://cloud.google.com/artifact-registry">Artifact Registry</a>, and deploy to the Cloud Run service. Once deployed, the Run Function is assigned a HTTPs endpoint in the format:</p><pre>https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME</pre><p>You can do this through the console, gcloud CLI, or Terraform. Here’s the command to deploy:</p><pre>gcloud functions deploy multi-region-demo <br>     --gen2 --region=us-central1 <br>     --runtime=nodejs22 --source=. --entry-point=helloHttp <br>     --trigger-http <br>     --ingress-settings=internal-and-gclb --allow-unauthenticated</pre><p>This creates a Run Function called multi-region-demo in the us-central1 region using Node22 based source code.</p><blockquote>By default, the CRF endpoint is hosted out of a single region, allows traffic from anywhere, and requires Authentication.</blockquote><p>Our goal is to serve all traffic through a Global External Application Load Balancer (GALB) <em>instead</em> of<strong> </strong>directly from the Cloud Run Function.<strong> This will allow the GALB to decide which region to best serve incoming traffic.</strong></p><ul><li>Use the allow-unauthenticated flag because GALB does not perform any form of auth to a Cloud Run service</li><li>Use the ingress-settings flag to <a href="https://cloud.google.com/run/docs/securing/ingress">restrict</a> traffic to the function so that only internal sources and your GALB are allowed</li></ul><p>You can monitor the deployment progress at Cloud Build and view the actual image that was built at Artifact Registry.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EIYJkh_-D97-nf1EOyJXQw.png" /><figcaption>In Cloud Build, I can see two successful builds for this function in us-central1</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uUgBRokfiiCEC_SriQZzYQ.png" /><figcaption>Here are the build details and a success confirmation</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*k2jmCLsiBJ8cIXhK5jNKYw.png" /><figcaption>The builds are deployed in a “gcf-artifacts” folder in Artifact Registry</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9EE9wsVj0LRfrEr59KXk_Q.png" /><figcaption>Here are the actual images in Artifact Registry, which are ultimately stored in a Storage Bucket</figcaption></figure><p>Next, deploy the Run Function to <em>every</em> region you want your application to be hosted from. Just run the same gcloud CLI command with a different region specified. Every Run Function will exist independently in its own region and have its own HTTPs endpoint. We will attach all deployed regions to a Global ALB in the next step.</p><blockquote>Remember these are regional services so Cloud Build will execute independently in each region and store Artifacts in each region</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5YBx4pXqBJrLAFDV_qx_dg.png" /><figcaption>My function deployed across 3 regions</figcaption></figure><h4>Cloud Load Balancing</h4><p>A <a href="https://cloud.google.com/load-balancing/docs/https">Global External Application Load Balancer</a> (GALB) will act as a single point of traffic ingress and distribute traffic across all regions based on the user’s location. This is made possible through a single anycast IP address that is presented across all regions. We will use <a href="https://cloud.google.com/dns">Cloud DNS</a> to create a DNS entry (A record) that resolves to this IP address, then use <a href="https://cloud.google.com/certificate-manager/">Certificate Manager</a> to create a matching SSL certificate for our Custom domain.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6JcGBC7AYNxgWsM7ZI8FKQ.png" /></figure><p>Let’s look at the 4 main components that make up this architecture.</p><ol><li>A <em>Global</em> External Load Balancer (GALB). This will provide a single static, anycast IP that your Custom DNS A record will point to.</li><li>A <em>Global</em> <a href="https://cloud.google.com/load-balancing/docs/backend-service">Backend Service</a>. This acts as the service that your GALB will direct traffic to. Your Backend Service can attach a variety of <a href="https://cloud.google.com/load-balancing/docs/backend-service#backends">Backends</a> (such as GCE Managed Instance Groups) — in our case we are going to attach a <a href="https://cloud.google.com/load-balancing/docs/negs/serverless-neg-concepts">Serverless Network Endpoint Group</a> (NEG).</li><li>A Serverless NEG represents a Serverless resource such as Cloud Run, App Engine, or API Gateway. You will need to create a Serverless NEG in each region you want to service traffic and point it to the Cloud Run service in that region.</li><li>Cloud Run. This provides the actual compute environment to execute your application logic. We set this up already.</li></ol><p>Review the <a href="https://cloud.google.com/load-balancing/docs/https/setup-global-ext-https-serverless#deploy_serverless_app">Load Balancer docs</a> for detailed instructions on how to setup this architecture.</p><h4>Certificate Manager</h4><p>Create a global, Google Managed SSL certificate for your domain using DNS Authorization. You can create a domain specific cert or a wildcard cart for an entire subdomain. Before Google will activate your Certificate you must validate that you own the domain through a <a href="https://cloud.google.com/certificate-manager/docs/dns-authorizations">DNS AuthZ process</a>.</p><p>Certificate Manager will generate a key-value pair of DNS records that you must add to your Domain. Certificate Manager will lookup your domain and verify that the records match the values it generated. This process establishes ownership of the domain — preventing unauthorized certificate usage.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CZugg33qz8VRsSbJaSFC1g.png" /><figcaption>This record must be added to your DNS entry</figcaption></figure><h4>Cloud DNS</h4><p>In Cloud DNS, create a public Zone for your Domain. Then add two records to the Zone:</p><ol><li>An <strong>A record</strong> that points to the static IP for your GALB. This will allow global resolution of your domain to your GALB.</li><li>A <strong>CNAME</strong> <strong>record</strong> with matching values from the Certificate Manager step. The verification process usually finishes in under 30 minutes but could take up to 24 hours due to DNS propagation and caching.</li></ol><p>For example, my domain is <em>iamgeorge.demo.altostrat.com.</em> I added both records as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Vu_6fqphmx2fb-5SfKW72Q.png" /><figcaption>CNAME and A record added to my Zone. The SOA and NS records are automatically added by Google.</figcaption></figure><p>Monitor the Certificate status in Certificate manager —once the status shows Active you are good to go. Note the name for your Certificate as we will use it in the next step.</p><p>Now we need to deploy this cert to the GALB. At the time of this post, the only away to do this is via the gcloud CLI. The full process is described in the <a href="https://cloud.google.com/certificate-manager/docs/deploy-google-managed-lb-auth#deploy_the_certificate_to_a_load_balancer">Certificate Manager docs </a>here. The main steps are:</p><p><strong>Create a Certificate Map</strong></p><pre>gcloud certificate-manager maps create CERTIFICATE_MAP_NAME</pre><p><strong>Create a config Entry in the Map for your Cert and Domain</strong></p><pre>gcloud certificate-manager maps entries create CERTIFICATE_MAP_ENTRY_NAME \<br>    --map=&quot;CERTIFICATE_MAP_NAME&quot; \<br>    --certificates=&quot;Cert name from Certmanager&quot; \<br>    --hostname=&quot;Your cert&#39;s hostname&quot;</pre><p><strong>Attach the Certificate Map to your GALB</strong></p><pre>gcloud compute target-https-proxies create PROXY_NAME \<br>    --certificate-map=&quot;CERTIFICATE_MAP_NAME&quot; \<br>    --url-map=&quot;The name of your URL Map you created during the GALB step&quot; \<br>    --global</pre><h4>Cloud Armor</h4><p>You should secure your application by applying Layer 7 WAF rules with Cloud Armor. It’s easy to start with <a href="https://cloud.google.com/armor/docs/waf-rules">preconfigured</a> rules but you can also create custom rules.</p><p>Review your GALB configuration — under the <strong>Backend Services</strong> section you should see a Backend Security Policy<em> </em>link. There will be a default policy created that allows all traffic. It looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hrpt5vrcP1AJChkplxxPEQ.png" /></figure><p>Try adding a preconfigured rule such as a check for Cross-Site scripting:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/796/1*7AA9yXMFgvVEJ7oFcGr_Zg.png" /></figure><p>Use Logs Explorer to review how your security policies are enforcing requests. Here’s a basic log query to grab policy evaluation logs:</p><pre>resource.type:(http_load_balancer) <br>  OR resource.type:(tcp_ssl_proxy_rule) <br>  OR resource.type:(l4_proxy_rule) <br>  AND jsonPayload.enforcedSecurityPolicy.name:(name of your policy here)</pre><p>Look at the enforcedSecurityPolicy JSON block to figure out how your policy executed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TbHTXSfMi_BAmkcr0WdSpQ.png" /></figure><h3>Summary</h3><p>You should now be able issue a HTTP GET against your domain and get a response from the nearest region. You can VPN to a different region or use a GCE instance in a different region to verify that the GALB is routing traffic appropriately.</p><p>If you used my code for your CRF, here’s what you should see.</p><pre>{<br>  &quot;instanceID&quot;: &quot;...&quot;,<br>  &quot;counter&quot;: 1,<br>  &quot;region&quot;: &quot;projects/528923850333/regions/us-east4&quot;<br>}</pre><p>This architecture allows you to leverage the global Google network to maximize resiliency and cost efficiency, plus:</p><ul><li>It’s 100% serverless — there are zero servers to manage or patch including the compute used in the build and deployment phase</li><li>It autoscales and load balances with traffic and incurs cost per use, instead of incurring cost by uptime (with the exception of the GALB)</li><li>Security is built in at every stage — there is no public entry to any compute, layer 7 security is covered, and container images are auto patched and updated by Google</li></ul><h3>What’s next</h3><p>In my next post, I’ll show you how to enhance this architecture with:</p><ul><li>Cloud CDN to serve static assets for faster, cheaper delivery</li><li>Add <a href="https://cloud.google.com/security/products/iap">Identity Aware Proxy</a> on the <a href="https://cloud.google.com/iap/docs/load-balancer-howto#enable-iap"><em>GALB</em></a> as another form of Security</li><li>Add and configure various Datastore backends</li><li>Automate deployment of this architecture using <a href="https://www.pulumi.com/">Pulumi</a> IaC</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=96561750cba4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/build-a-planet-scale-global-architecture-for-modern-apps-on-google-cloud-96561750cba4">Build a planet scale, global architecture for modern apps on Google Cloud</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Optimize your Cloud Run functions]]></title>
            <link>https://medium.com/google-cloud/optimize-your-cloud-run-functions-7bf0b6c188f4?source=rss-6f6d7bda3a17------2</link>
            <guid isPermaLink="false">https://medium.com/p/7bf0b6c188f4</guid>
            <category><![CDATA[optimization]]></category>
            <category><![CDATA[google-cloud-run]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[gcp-app-dev]]></category>
            <dc:creator><![CDATA[George Mao]]></dc:creator>
            <pubDate>Mon, 18 Nov 2024 13:46:53 GMT</pubDate>
            <atom:updated>2024-11-19T18:17:10.668Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/69/0*Nz-j69Mi2AP9p3aK" /></figure><p>In my <a href="https://medium.com/google-cloud/go-serverless-with-google-cloud-run-functions-376b065c6c48">Intro to Cloud Run functions</a> post I covered the basics that all Cloud Run Functions developers should know. In this post, I’ll show you how to understand costs &amp; strategies you can use to optimize your functions.</p><h3>Pricing Deep Dive</h3><p>The primary driver of costs is consumption of resources <strong>configured</strong>. Here is an easy way to remember the pricing formula:</p><blockquote>Resources <strong>configured </strong>at deploy time (vCPU + Memory) * Duration of execution</blockquote><p>All components of the formula have a direct, linear effect on cost. So if you double the configuration, you double the cost. Or if the function runs twice as long, you double the cost. If you cut the configuration in half or the function runs half as long, you save 50%. Here is a simple example:</p><ul><li>You configure your function to use 2 vCPU / 4GB Memory and it runs for 1000ms on average</li><li>If this function is invoked 1M times a month, <a href="https://cloud.google.com/products/calculator?hl=en&amp;dl=CjhDaVJoTUROaE1UYzNZaTB3TURSbUxUUXpOVEV0T1Rsak9TMDJNalZrT1RCaFpHRmtaVGdRQVE9PRATGiQyOTE2QkJDNi1FRkJCLTQ2RUYtQjVFNy00M0YzMDYzMUI1QjY">it’ll cost you $58/month</a></li><li>If you cut this configuration in half (1 vCPU and 2GB memory) and the function continues to run for 1000 ms, you’ll reduce costs by 50%, or spend $29/month</li><li>If the the reduced resources causes the function to run slower and take 2000ms on average, you’re back to $59/month</li></ul><p>In general, your goal is to find the configuration “sweet spot” where performance and cost are optimal.</p><h3>Optimizations</h3><p>There are two categories of optimizations you can make. The first is <strong>configuration</strong> changes to your function — this is easier but requires a deep dive into metrics. The second is <strong>coding</strong> changes. Let’s start with configuration changes.</p><h4>Allocate resources to match workload needs</h4><p>The best way to begin is to determine the primary resource your workload consumes, is it CPU or Memory? Focus on tuning that resource properly. Tuning is generally a 3 step process:</p><ol><li>Start with a baseline configuration</li><li>Run tests to benchmark resource usage &amp; review results via metrics</li><li>Make changes and repeat</li></ol><p>Here’s a snippet of code that represents a CPU intensive workload with very little memory requirements — it computes SHA512 hashes in a sequential loop. Our goal is to determine where the CPU configuration gives us the best cost/performance value. Full source code available on <a href="https://gist.github.com/georgemao/9dd8ca335ab00a2b37c26854e747d955">Github</a>.</p><pre>N = 51200<br><br>@functions_framework.http<br>def hello_http(request):<br>    #HTTP Cloud Function that simulates CPU load<br><br>    m = hashlib.sha512()<br>    # Generate a random long string<br>    rstr = &#39;&#39;.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(N))<br><br>    t1 = datetime.now()<br>    print(&quot;Start time: &quot; + str(t1))<br><br>    # repeatedly compute the digest<br>    for i in range(10000):<br>        m.update(rstr.encode())<br>        m.digest()<br><br>    t2 = datetime.now()<br>    print(&quot;End time: &quot; + str(t2))<br>    print(&quot;Difference: &quot; + str(t2 - t1))<br><br>    difference = str(t2 - t1)<br><br>    return &#39;Performance {}!&#39;.format(difference)t</pre><ul><li>I started with the default 256MB memory configuration and set <strong>vCPU = 0.167.</strong> This resulted in <strong>~6.8 seconds</strong> to complete the execution.</li><li>Next, I double <strong>vCPU to 0.333.</strong> This doubles performance, reducing duration to<strong> ~3.3 seconds.</strong></li><li>Next, I triple <strong>vCPU to 1.</strong> This triples performance, further reducing duration to <strong>~1 second.</strong></li><li>Finally, I double <strong>vCPU to 2</strong> and another double to <strong>4 vCPU</strong>. Neither of these provide any meaningful performance gains.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nik4K-mAwJ3ZqwAbDSXCCA.png" /></figure><p>Just to verify, I check the Memory Utilization metric and confirm that I am well under the 256MB configuration.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uyfwCFqDM-cFsE5d2L0bNA.png" /></figure><blockquote>If this were a real workload, I would probably set my vCPU = 1. Anything more is simply a waste of money.</blockquote><h4>Use Concurrent Requests Per Instance</h4><p>This is the next configuration based optimization that you should look into. By default, the Cloud Run functions service will only allow a single request to be served concurrently by each instance of your function. With my vCPU set to 1, I investigate the Container CPU utilization metric. I see a peak of ~12.97% CPU. This leads me to believe my container’s CPU is underutilized.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4gD_OFlkIbGFgS1WgTLCaA.png" /></figure><p>Based on my hypothesis, I increase the Max Concurrent Requests per Instance from 1 → 2 and execute two simultaneous invokes of my function.</p><p>As I expected, this results in about double the CPU utilization, or ~25.98%. This makes sense as the same container is doing double the work now.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IfVVdz0IGVXzXBTUk8Odag.png" /></figure><p>This configuration allows a single container to service twice as many requests during the same billable window. I can check the Billable Container Instance Time metric to confirm.</p><blockquote>Note: Always check the Duration or Latency metric to confirm CPU utilization does not have a negative effect and degrade performance</blockquote><h4>Use global state correctly</h4><p>The final optimization technique involves moving heavy compute tasks to take place in global execution state. This allows the <a href="https://cloud.google.com/run/docs/configuring/services/cpu#startup-boost">CPU Boost</a> feature to provide extra cpu power (about 2x) during cold starts. Cloud Run Functions can run cpu intensive tasks for up to 10 seconds with boosted cpu.</p><p>All code in global state is only executed once for the life of the function’s instance and can be reused durning future warm invokes.</p><p>Below, I’ve refactored the SHA computation code to take place globally instead of inside hello_http function. It will run once during a cold start and benefit from boosted CPU. See updated code on <a href="https://gist.github.com/georgemao/df2b0b1812acb652e183f729c538d3a1">Github</a>.</p><pre>N = 51200<br><br># This function is called during cold start only<br>def computeHashes():<br>    m = hashlib.sha512()<br>    # Generate a random long string<br>    rstr = &#39;&#39;.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(N))<br><br>    t1 = datetime.now()<br>    print(&quot;Start time: &quot; + str(t1))<br><br>    # repeatedly compute the digest<br>    for i in range(10000):<br>        m.update(rstr.encode())<br>        m.digest()<br><br>    t2 = datetime.now()<br>    print(&quot;End time: &quot; + str(t2))<br>    print(&quot;Difference: &quot; + str(t2 - t1))<br><br>    difference = str(t2 - t1)<br>    return difference<br><br># Save results in global state    <br>perf = computeHashes()<br><br>@functions_framework.http<br>def hello_http(request):<br>    #HTTP Cloud Function that simulates CPU load<br><br>    return &#39;Performance {}!&#39;.format(perf)</pre><p>Time to test! I execute the function 3 times in a row. The first invoke runs the hash compute code in ~1 second, while the following two invokes don’t execute the hash code at all and simply return in 3 ms.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EZp0ykDtT4dLIq8wl9Tuhw.png" /><figcaption>1 cold invoke followed by two warm invokes</figcaption></figure><h3>Summary</h3><p>In my experience, tuning Serverless services always requires actual testing and analysis of results. It’s a bit of trial and error and Cloud Run functions is no different. In my next post, I’ll cover a great way to perform large scale load testing of Cloud Run functions.</p><p>In the mean time, be sure to review the Cloud Run functions <a href="https://cloud.google.com/functions/docs/bestpractices/tips">Tips &amp; Tricks</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7bf0b6c188f4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/optimize-your-cloud-run-functions-7bf0b6c188f4">Optimize your Cloud Run functions</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Go Serverless with Google Cloud Run Functions]]></title>
            <link>https://medium.com/google-cloud/go-serverless-with-google-cloud-run-functions-376b065c6c48?source=rss-6f6d7bda3a17------2</link>
            <guid isPermaLink="false">https://medium.com/p/376b065c6c48</guid>
            <category><![CDATA[gcp-app-dev]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[runcloud]]></category>
            <category><![CDATA[serverless]]></category>
            <dc:creator><![CDATA[George Mao]]></dc:creator>
            <pubDate>Tue, 12 Nov 2024 15:02:18 GMT</pubDate>
            <atom:updated>2024-11-13T04:50:45.315Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/69/0*HDYyXj_XuV1n61lB" /></figure><p>Serverless development is my favorite way to build modern applications — with Google <a href="https://cloud.google.com/functions?hl=en">Cloud Run Functions</a>, you bring the code and Google handles all of the heavy lifting of load balancing, scaling, availability, and Infrastructure/OS management. This means developers just focus on building <em>awesome</em> apps. Here is the first of a 3 part guide on Cloud Run Functions.</p><blockquote>Cloud Run Functions is the only serverless FaaS product in the market that supports scale up and down from zero, can attach L4 GPUs on demand, and deploy to multi-regions with one command</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/846/1*uL5yYZeVk_56ya1N-EcK4g.png" /></figure><h3>The Benefits</h3><ul><li>You only pay for the resources you <strong>consume</strong>. You don’t need to worry about idle VM instances or optimizing your cluster sizes. You’re <a href="https://cloud.google.com/run/pricing">billed</a> on a combination of Invocation count (first 2M / month are free) + resources allocated during theduration of execution — which we refer to as Vcpu-seconds and Gib-seconds.</li><li>There is no maintenance! Developers don’t need to debate Instance types, or configure VMs, and there is no Operating System to patch</li><li>You can operate nearly any workload, including deploying LLMs or running graphics intensive workloads using the new <a href="https://cloud.google.com/blog/products/application-development/run-your-ai-inference-applications-on-cloud-run-with-nvidia-gpus">L4 GPU support</a></li></ul><h3>The Basics</h3><h4>Functions respond to <a href="https://cloud.google.com/functions/docs/calling"><strong>Trigger</strong></a><strong>s</strong> —there are two types of triggers</h4><ol><li><strong>Event </strong>based<strong> </strong>triggers are an event generated by a GCP Service. Examples are EventArc, Pub/Sub, Cloud Storage, or Firestore. These services generate an event that results in an asynchronous invoke of your Cloud Run Function.</li><li>The second type of event is a <strong>synchronous</strong> HTTP trigger. Functions are auto assigned a <a href="https://cloud.google.com/functions/docs/calling/http&#39;"><strong>Function URL</strong></a> that is referred to as the HTTP trigger. You make a HTTP POST request to the url to invoke the function. By default, the URL is public and requires Authentication. In addition, you can <a href="https://cloud.google.com/run/docs/securing/ingress">restrict or disable </a>external traffic. It’s always in this format:</li></ol><pre>https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ePQbq6EA40bAd5Z8AJtsCw.png" /></figure><blockquote>Pro Tip: You can invoke your function for testing with a curl command:</blockquote><pre>curl -m 70 -X POST https://[region]-[project_id].cloudfunctions.net/[func_name] \<br>-H &quot;Authorization: bearer $(gcloud auth print-identity-token)&quot; \<br>-H &quot;Content-Type: application/json&quot; \<br>-d &#39;{<br>  &quot;name&quot;: &quot;Hello World&quot;<br>}&#39;</pre><h4>Security</h4><p>Functions must assign a <strong>Service Account (SA)</strong>. The attached SA provides an identity to all code running in the Function. By default, Functions will use the <a href="https://cloud.google.com/compute/docs/access/service-accounts#default_service_account">default compute service account</a>, which has broad permissions. It looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-cWYyMPI15MFdmYzWXvI0w.png" /></figure><pre>PROJECT_NUMBER-compute@developer.gserviceaccount.com</pre><p>You should always create your own SA and attach appropriate, scoped down permissions to specific resources your Function needs to access.</p><h4>Function Resources</h4><p>You configure the vCPU and Memory resources allocated to the function. You should choose settings that match your workload needs, since Google bills you based on resources <strong>allocated</strong>, not consumed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wkpo68wRWkLh4M5bemXIfQ.png" /></figure><h3>Let’s write a HTTP Function</h3><p>Here’s the basic required implementation for a HTTP function (NodeJs). Other language examples are available <a href="https://cloud.google.com/functions/docs/tutorials/http">here</a>. At minimum you need to register with the functions framework using the functions.http() method and then return a valid HTTP response. The req parameter will contain all information delivered as part of the HTTP request.</p><pre>const functions = require(&#39;@google-cloud/functions-framework&#39;);<br><br>// Register an HTTP function with the Functions Framework that will be executed<br>// when you make an HTTP request to the deployed function&#39;s endpoint.<br>functions.http(&#39;helloGET&#39;, async (req, res) =&gt; {<br>  // Retrieve information from the request<br>  const name = req.query.name<br>  <br>  // Return a valid HTTP response<br>  res.send(&#39;Hello World!&#39;);<br><br>  // Optionally, specify the status code and response type<br>  // res.status(200).json(retVal);<br>});</pre><blockquote>Pro Tip: Change the function syntax to async so you can use top level await</blockquote><h3>Let’s write an Event Driven Function</h3><p>You still have to register with the functions framework but this time use the functions.cloudEvent() method. Notice you do not return any values, since event triggers are an async process and do not expect a response.</p><p>The cloudEvent parameter will contain the entire payload for the event. Access the cloudEvent.data field to extract details specific to the event that occurred. The payload format for the data field will differ depending on the service that generated the event. For example, some services base64 encode this field but most services just write a JSON object. See the <a href="https://github.com/googleapis/google-cloudevents">Google-Events</a> repo to see all possible events and their format.</p><pre>const functions = require(&#39;@google-cloud/functions-framework&#39;);<br><br>// Register a CloudEvent callback with the Functions Framework<br>functions.cloudEvent(&#39;helloEvent&#39;, cloudEvent =&gt; {<br>  // Handle the Event here. The event is inside the cloudEvent.data field<br>  const eventPayload = cloudEvent.data;<br><br>  // If Cloud Storage, the payload is in JSON format<br>  // const {cloudStorageEvent} = {eventPayload.name, eventPayload.bucket}<br><br>  // If Pub/Sub, the data payload is base64 encoded<br>  // const base64Event = eventPayload.message.data;<br>  // const pubsubEvent = Buffer.from(base64name, &#39;base64&#39;).toString()<br><br>  console.log(`Hello!`);<br>});</pre><h3>What is Concurrency?</h3><p>There are two concepts you should be aware of when you think about function scaling. They are configurable and operate independently of each other.</p><h4>Concurrent instances per function</h4><p>Cloud Run Functions automatically scales the number of active containers behind the scenes to handle incoming work. This is the number of <strong>concurrent instances </strong>of your function that are active at any given time.</p><blockquote>Functions transition through 4 main lifecycle stages: Starting → Active → Idle → Shutting down</blockquote><ul><li>When a Trigger invokes a function, Google will attempt to use an instance that is warm &amp; available to serve the request. You can check if any are available using the Instance count metric and look for the idle count.</li><li>If no idle instances exist, Google will spin up an instance to serve the invoke — this is called a cold start because there is overhead to go from zero to active. The instance will remain active until all processing is complete.</li><li>After a function completes processing, it will transition to idle and wait for another invoke. Functions remain in idle state for up to 15 minutes then the instance is removed. At this point the instance transitions to Shutdown.</li><li>Here is an example of the Instance Count metric. At the highlighted time, there are 35 active instances processing workloads and 5 idle.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zwgey3c2ugiNq4_xjRwQow.png" /></figure><p>You can mitigate colds start by setting the Autoscaling configuration: minimum instances. You may also want to set maximum instances to control costs or protect downstream services from being overwhelmed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ftPmfIrTx-yvuC03xjtXDA.png" /></figure><h4>Max Concurrent requests per function instance</h4><p>Functions default to serving a single request at a time. However, you can increase this setting (up to 1000) to allow a single instance to serve multiple concurrent requests. This is referred to as <strong>concurrent requests per function instance.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w2sDeO-mAi0NWKDMYvGqkw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/918/1*i801GFbj4CbsfTGpnDmuMw.png" /><figcaption>1 concurrent request per instance vs 3 concurrent requests per instance</figcaption></figure><ul><li>Serving multiple requests concurrently generally provides significant cost savings since the majority of cost comes from instance uptime.</li><li>This feature results in shared global memory and cpu consumption. You need to ensure your code is able to safely operate this way.</li><li>You can view the Max Concurrent Requests metric to understand how your functions are serving requests.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*g-LK6nC8mG3RuX4Gw1qhJQ.png" /></figure><blockquote>Pro tip: By default this metric shows a Distribution over 1 minute, which isn’t very useful. You can modify it to group by count which is more useful.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BXvYeKM9fcgLJNQOK5pOGg.png" /><figcaption>Group the metric by Count instead</figcaption></figure><h3>Summary &amp; TLDR;</h3><p>When you transition from VM based scaling to Serverless scaling — Concurrency becomes the main scaling factor. Cloud Run has two layers of Concurrency that are critical to understand.</p><p>There also are other important Cloud Run Functions features I’ll cover in a future post but you can read through official docs now:</p><ul><li><a href="https://cloud.google.com/functions/docs/networking/network-settings">Networking</a></li><li><a href="https://cloud.google.com/functions/docs/configuring/secrets">Secrets</a></li><li><a href="https://cloud.google.com/functions/docs/bestpractices/tips">Load testing, Tuning, and Optimization</a></li></ul><p>Let me know if I can help you in your Serverless journey on Google Cloud!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=376b065c6c48" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/go-serverless-with-google-cloud-run-functions-376b065c6c48">Go Serverless with Google Cloud Run Functions</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>