
<rss version="2.0" 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" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[ The Cloudflare Blog ]]></title>
        <description><![CDATA[ Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet. ]]></description>
        <link>https://blog.cloudflare.com</link>
        <atom:link href="https://blog.cloudflare.com/" rel="self" type="application/rss+xml"/>
        <language>en-us</language>
        <image>
            <url>https://blog.cloudflare.com/favicon.png</url>
            <title>The Cloudflare Blog</title>
            <link>https://blog.cloudflare.com</link>
        </image>
        <lastBuildDate>Sat, 04 Apr 2026 10:48:01 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Containers are available in public beta for simple, global, and programmable compute]]></title>
            <link>https://blog.cloudflare.com/containers-are-available-in-public-beta-for-simple-global-and-programmable/</link>
            <pubDate>Tue, 24 Jun 2025 16:00:22 GMT</pubDate>
            <description><![CDATA[ Cloudflare Containers are now available in public beta. Deploy simple, global, and programmable containers alongside your Workers.  ]]></description>
            <content:encoded><![CDATA[ <p>We’re excited to announce that <a href="https://blog.cloudflare.com/cloudflare-containers-coming-2025/"><u>Cloudflare Containers</u></a> are now available in beta for all users on paid plans.</p><p>You can now run new kinds of applications alongside your Workers. From media and data processing at the edge, to backend services in any language, to CLI tools in batch workloads — Containers open up a world of possibilities.</p><p>Containers are tightly integrated with Workers and the rest of the developer platform, which means that:</p><ul><li><p>Your workflow stays <b>simple</b>: just define a Container in a few lines of code, and run<code> wrangler deploy</code>, just like you would with a Worker.</p></li><li><p>Containers are <b>global: </b>as with Workers, you just deploy to Region:Earth. No need to manage configs across 5 different regions for a global app.</p></li><li><p>You can <b>use the right tool for the job</b>: routing requests between Workers and Containers is easy. Use a Worker when you need to be ultra light-weight and scalable. Use a Container when you need more power and flexibility.</p></li><li><p>Containers are <b>programmable</b>: container instances are spun up on-demand and controlled by Workers code. If you need custom logic, just write some JavaScript instead of spending time chaining together API calls or writing Kubernetes operators.</p></li></ul><p>Want to try it today? Deploy your first Container-enabled Worker:</p><a href="https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/templates/tree/main/containers-template"><img src="https://deploy.workers.cloudflare.com/button" /></a>
<p></p>
    <div>
      <h2>A tour of Containers</h2>
      <a href="#a-tour-of-containers">
        
      </a>
    </div>
    <p>Let’s take a deeper look at Containers, using an example use case: code sandboxing.</p><p>Let’s imagine that you want to run user-generated (or AI-generated) code as part of a platform you’re building. To do this, you want to spin up containers on demand. Each user needs their own isolated container, the users are distributed globally, and you need to start each container quickly so the users aren’t waiting.</p><p>You can set this up easily on Cloudflare Containers.</p>
    <div>
      <h4>Configuring a Container</h4>
      <a href="#configuring-a-container">
        
      </a>
    </div>
    <p>In your Worker, use the <a href="https://github.com/cloudflare/containers"><u>Container class</u></a> and <a href="https://developers.cloudflare.com//workers/wrangler/configuration/#containers"><u>wrangler.jsonc</u></a> to declare some basic configuration, such as your Container’s default port, a sleep timeout, and which image to use, then route to it via the Worker.</p><p>For each unique ID passed to the Container’s binding, Cloudflare will spin up a new Container instance and route requests to it. When a new instance is requested, Cloudflare picks the best location across our global network where we’ve pre-provisioned a ready-to-go container. This means that you can deploy a container close to an end user no matter where they are. And the initial container start takes just a few seconds. You don’t have to worry about routing, provisioning, or scaling.</p><p>This example Worker will route requests to a unique container instance for each sandbox ID given at the path <code>/sandbox/ID</code> and will be handled by standard Worker JavaScript otherwise:</p>
            <pre><code>export class MyContainer extends Container {
  defaultPort = 8080; // The default port for the container to listen on
  sleepAfter = '5m'; // Sleep the container if no requests are made in this timeframe
}

export default {
  async fetch(request, env) {
    const pathname = new URL(request.url).pathname;

    // handle request with an on-demand container instance
    if (pathname.startsWith('/sandbox/')) {
      const sessionId = pathname.split("/")[2]
      const containerInstance = getContainer(env.CONTAINER_SANDBOX, sessionId)
      return await containerInstance.fetch(request);
    }

    // handle request with my Worker code otherwise
    return myWorkerRequestHandler(request);
  },
};</code></pre>
            
    <div>
      <h4>Familiar and easy development workflow with <code>wrangler dev</code></h4>
      <a href="#familiar-and-easy-development-workflow-with-wrangler-dev">
        
      </a>
    </div>
    <p>To configure which container image to use, you can provide an image URL in wrangler config or a path to a local Dockerfile. </p><p>This config tells wrangler to use a locally defined image:</p>
            <pre><code>"containers": [
  {
    "class_name": "ContainerSandbox",
    "image": "./Dockerfile",
    "max_instances": 80,
    "instance_type": "basic"
  }
]</code></pre>
            <p>When developing your application, you just run <code>wrangler dev</code> and the container image will be automatically built and routable via your local Worker. This makes it easy to iterate on container code while making changes to your Worker at the same time. When you want to rebuild your image, just press “R” on your keyboard from your terminal running <code>wrangler dev</code>, and the Container is rebuilt and restarted.</p>
    <div>
      <h4>Shipping your Container-enabled Worker to production with <code>wrangler deploy</code></h4>
      <a href="#shipping-your-container-enabled-worker-to-production-with-wrangler-deploy">
        
      </a>
    </div>
    <p>When it’s time to deploy, just run <code>wrangler deploy</code>. Wrangler will push your image to your account, then it will be provisioned in various locations across Cloudflare’s global network.</p><p>You don’t have to worry about “artifact management”, or distribution, or auth, or jump through hoops to integrate your container with Workers. You just write your code and deploy it.</p>
    <div>
      <h4>Observability is built-in</h4>
      <a href="#observability-is-built-in">
        
      </a>
    </div>
    <p>Once your Container is in production, you have the visibility you need into how things are going, with end-to-end <a href="https://www.cloudflare.com/developer-platform/products/workers-observability/">observability</a>. </p><p>From the Cloudflare dashboard, you can easily track status and resource usage across Container instances with built-in metrics:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1cMDL8qejpD9TnaVYgKeLD/cf42573c1315f0364c0d225bb77a76af/image4.png" />
          </figure><p>
And if you need to dive deeper, you can dig into logs, which will be retained in the Cloudflare UI for seven days or <a href="https://developers.cloudflare.com/logs/about/"><u>pushed to an external sink</u></a> of your choice:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2AusbmokynDaErssVFyu4A/3bb617ff9c2e488cdaee20612cb6bd15/image3.png" />
          </figure>
    <div>
      <h4>Try it yourself</h4>
      <a href="#try-it-yourself">
        
      </a>
    </div>
    <p>Want to give it a shot? Check out <a href="https://github.com/craigsdennis/sandboxing-day-containers"><u>this example Worker for running sandboxed code</u></a> in a Container, and deploy it with one click.</p><p>Or better yet, if you have an image sitting around that you’ve been dying to deploy to Cloudflare, you can get started with <a href="https://developers.cloudflare.com//containers/get-started/"><u>our docs here</u></a>. </p>
    <div>
      <h2>A world of possibilities</h2>
      <a href="#a-world-of-possibilities">
        
      </a>
    </div>
    <p>We’re excited about all the new types of applications that are now possible to build on Workers. We’ve heard many of you tell us over the years that you would love to run your entire application on Cloudflare, if only you could deploy this one piece that needs to run in a container.</p><p>Today, you can run libraries that you couldn't run in Workers before. For instance, try <a href="https://github.com/megaconfidence/wifski/"><u>this Worker that uses FFmpeg to convert video to a GIF</u></a>. </p><p>Or you can <a href="https://github.com/mikenomitch/cron-container"><u>run a container as part of a cron job</u></a>. Or deploy a <a href="https://github.com/mikenomitch/static-frontend-container-backend"><u>static frontend with a containerized backend</u></a>. Or even run a <a href="https://github.com/ghostwriternr/claude-code-containers"><u>Cloudflare Agent that uses a Container to run Claude Code</u></a> on your behalf.</p><p>The <a href="https://blog.cloudflare.com/cloudflare-containers-coming-2025/#integrating-with-more-of-cloudflares-developer-platform"><u>integration with the rest of the Developer Platform</u></a> makes Containers even more powerful: <a href="https://blog.cloudflare.com/cloudflare-containers-coming-2025/#durable-objects-as-programmable-sidecars"><u>use Durable Objects</u></a> for state management, <a href="https://developers.cloudflare.com/workflows/"><u>Workflows</u></a>, <a href="https://developers.cloudflare.com/queues/"><u>Queues</u></a>, and <a href="https://agents.cloudflare.com/"><u>Agents</u></a> to compose complex behaviors, <a href="https://developers.cloudflare.com/r2/"><u>R2</u></a> to store Container data or media, and more.</p>
    <div>
      <h2>Pricing and packaging</h2>
      <a href="#pricing-and-packaging">
        
      </a>
    </div>
    <p>As with the rest of our Cloudflare developer products, we wanted to apply the same principles to our developer platform with transparent pricing that scales up and down with your usage.</p><p>Today, you can select from the following instances at launch (and yes, we plan to add larger instances over time):  </p><table><tr><td><p><b>Name</b></p></td><td><p><b>Memory </b></p></td><td><p><b>CPU</b></p></td><td><p><b>Disk</b></p></td></tr><tr><td><p>dev</p></td><td><p>256 MiB</p></td><td><p>1/16 vCPU</p></td><td><p>2 GB</p></td></tr><tr><td><p>basic</p></td><td><p>1 GiB</p></td><td><p>1/4 vCPU</p></td><td><p>4 GB</p></td></tr><tr><td><p>standard</p></td><td><p>4 GiB</p></td><td><p>1/2 vCPU</p></td><td><p>4 GB</p></td></tr></table><p>You only pay for what you use — charges start when a request is sent to the container or when it is manually started. Charges stop after the container instance goes to sleep, which can happen automatically after a timeout. This makes it easy to scale to zero, and allows you to get high utilization even with bursty traffic.</p><p>Containers are billed for every 10ms that they are actively running at the following rates, with monthly amounts included in <i>Workers Standard</i>:</p><ul><li><p><b>Memory</b>: $0.0000025 per GiB-second, with 25 GiB-hours included</p></li><li><p><b>CPU</b>: $0.000020 per vCPU-second, with 375 vCPU-minutes included</p></li><li><p><b>Disk</b> $0.00000007 per GB-second, with 200 GB-hours included</p></li></ul><p>Egress from Containers is priced at the following rates, with monthly amounts included in <i>Workers Standard</i>:</p><ul><li><p>North America and Europe: $0.025 per GB with 1 TB included</p></li><li><p>Australia, New Zealand, Taiwan, and Korea: $0.050 per GB with 500 GB included</p></li><li><p>Everywhere else: $0.040 per GB with 500 GB included</p></li></ul><p>
See <a href="https://blog.cloudflare.com/cloudflare-containers-coming-2025/#pay-for-what-you-use-and-use-the-right-tool"><u>our previous blog post</u></a> for a more in-depth look into pricing with an example app.</p>
    <div>
      <h2>What’s coming next?</h2>
      <a href="#whats-coming-next">
        
      </a>
    </div>
    <p>With today’s release, we’ve only just begun to scratch the surface of what Containers will do on Workers. This is the first step of many towards our vision of a simple, global, and highly programmable Container platform.</p><p>We’re already thinking about what’s next, and wanted to give you a preview:</p><ul><li><p><b>Higher limits and larger instances</b> – We currently limit your concurrent instances to 40 total GiB of memory and 40 total vCPU. This is enough for some workloads, but many users will want to go higher —  a lot higher. Select customers are already scaling well into the thousands of concurrent containers, and we want to bring this ability to more users. We will be raising our limits over the coming months to allow for more total containers and larger instance sizes.</p></li><li><p><b>Global autoscaling and latency-aware routing</b> – Currently, containers are addressed by ID and started on-demand. For many use cases, users want to route to one of many stateless container instances deployed across the globe, then autoscale live instances automatically. Autoscaling will be activated with a single line of code, and will enable easy routing to the nearest ready instance.</p></li></ul>
            <pre><code>class MyBackend extends Container {
  defaultPort = 8080;
  autoscale = true; // global autoscaling on - new instances spin up when memory or CPU utilization is high
}

// routes requests to the nearest ready container and load balance globally
async fetch(request, env) {
  return getContainer(env.MY_BACKEND).fetch(request);
}</code></pre>
            <ul><li><p><b>More ways to communicate between Containers and Workers</b> – We will be adding more ways for your Worker to communicate with your container and vice versa. We will add an <code>exec</code> command to run shell commands in your instance and handlers for HTTP requests <i>from</i> the container to Workers. This will allow you to more easily extend your containers with functionality from the entire developer platform, reach out to other containers, and programmatically set up each container instance.</p></li></ul>
            <pre><code>class MyContainer extends Container {
  // sets up container-to-worker communication with handlers
  handlers = {
    "example.cf": "handleRequestFromContainer"
  };

  handleRequestFromContainer(req) {
    return new Response("You are responding from Workers to a Container request to a specific hostname")
  }

  // use exec to run commands in your container instance
  async cloneRepo(repoUrl) {
    let command = this.exec(`git clone ${repoUrl}`)
    await command.print()
  }  
}</code></pre>
            <ul><li><p><b>Further integrations with the Developer Platform</b> – We will continue to integrate with the developer platform with first-party APIs for our various services. We want it to be dead simple to mount <a href="https://developers.cloudflare.com/r2/"><u>R2 buckets</u></a>, reach <a href="https://developers.cloudflare.com/hyperdrive/"><u>Hyperdrive</u></a>, access <a href="https://developers.cloudflare.com/kv/"><u>KV</u></a>, and more.</p></li></ul><p>And we are just getting started. Stay tuned for more updates this summer and over the course of the entire year.</p>
    <div>
      <h2>Try Containers today</h2>
      <a href="#try-containers-today">
        
      </a>
    </div>
    <p>The first step is to deploy your own container. Run <code>npm create cloudflare@latest -- --template=cloudflare/templates/containers-template</code> or click the button below to deploy your first Container to Workers.</p><a href="https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/templates/tree/main/containers-template"><img src="https://deploy.workers.cloudflare.com/button" /></a>
<p></p>
<p>
We’re excited to see all the ways you will use Containers. From new languages and tools, to simplified Cloudflare-only architectures, to advanced programmatic control over container creation, you now have the ability to do even more on the Developer Platform. It is just a wrangler deploy away.</p> ]]></content:encoded>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Containers]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[AI]]></category>
            <guid isPermaLink="false">5StUs8TMNjY9aDG2TzFJ06</guid>
            <dc:creator>Gabi Villalonga Simón</dc:creator>
            <dc:creator>Ivan Chernetsky</dc:creator>
            <dc:creator>Josh Seba</dc:creator>
            <dc:creator>Nafeez Nazer</dc:creator>
            <dc:creator>Thomas Lefebvre</dc:creator>
        </item>
        <item>
            <title><![CDATA[Our container platform is in production. It has GPUs. Here’s an early look]]></title>
            <link>https://blog.cloudflare.com/container-platform-preview/</link>
            <pubDate>Fri, 27 Sep 2024 13:00:00 GMT</pubDate>
            <description><![CDATA[ We’ve been working on something new — a platform for running containers across Cloudflare’s network. We already use it in production, for AI inference and more. Today we want to share an early look at ]]></description>
            <content:encoded><![CDATA[ <p>We’ve been working on something new — a platform for running containers across Cloudflare’s network. We already use it in production for <a href="https://developers.cloudflare.com/workers-ai/"><u>Workers AI</u></a>, <a href="https://developers.cloudflare.com/workers/ci-cd/"><u>Workers Builds</u></a>, <a href="https://www.cloudflare.com/zero-trust/products/browser-isolation/"><u>Remote Browsing Isolation</u></a>, and the <a href="https://developers.cloudflare.com/browser-rendering/"><u>Browser Rendering API</u></a>. Today, we want to share an early look at how it’s built, why we built it, and how we use it ourselves.</p><p>In 2024, Cloudflare Workers celebrates its 7th birthday. <a href="https://blog.cloudflare.com/introducing-cloudflare-workers/"><u>When we first announced Workers</u></a>, it was a completely new model for running compute in a multi-tenant way — on isolates, as opposed to containers. While, at the time, Workers was a pretty bare-bones functions-as-a-service product, we took a big bet that this was going to become the way software was going to be written going forward. Since introducing Workers, in addition to expanding our developer products in general to include storage and AI, we have been steadily adding more compute capabilities to Workers:</p><table><tr><td><p><b>2020</b></p></td><td><p><a href="https://blog.cloudflare.com/introducing-cron-triggers-for-cloudflare-workers/"><u>Cron Triggers</u></a></p></td></tr><tr><td><p><b>2021</b></p></td><td><p><a href="https://blog.cloudflare.com/durable-objects-easy-fast-correct-choose-three/"><u>Durable Objects</u></a></p><p><a href="https://blog.cloudflare.com/workers-rust-sdk/"><u>Write Workers in Rust</u></a></p><p><a href="https://blog.cloudflare.com/introducing-worker-services/"><u>Service Bindings</u></a></p></td></tr><tr><td><p><b>2022</b></p></td><td><p><a href="https://blog.cloudflare.com/introducing-cloudflare-queues/"><u>Queues</u></a></p><p><a href="https://blog.cloudflare.com/announcing-route-to-workers/"><u>Email Workers</u></a></p><p><a href="https://blog.cloudflare.com/durable-objects-alarms/"><u>Durable Objects Alarms</u></a></p></td></tr><tr><td><p><b>2023</b></p></td><td><p><a href="https://blog.cloudflare.com/workers-tcp-socket-api-connect-databases/"><u>Workers TCP Socket API</u></a> </p><p><a href="https://blog.cloudflare.com/hyperdrive-making-regional-databases-feel-distributed/"><u>Hyperdrive</u></a></p><p><a href="https://blog.cloudflare.com/announcing-workers-smart-placement/"><u>Smart Placement</u></a></p><p><a href="https://blog.cloudflare.com/best-place-region-earth-inference/"><u>Workers AI</u></a></p></td></tr><tr><td><p><b>2024</b></p></td><td><p><a href="https://blog.cloudflare.com/python-workers/"><u>Python Workers</u></a></p><p><a href="https://blog.cloudflare.com/javascript-native-rpc/"><u>JavaScript-native RPC</u></a></p><p><a href="https://blog.cloudflare.com/more-npm-packages-on-cloudflare-workers-combining-polyfills-and-native-code/"><u>Node.js compatibility</u></a></p><p><a href="https://blog.cloudflare.com/sqlite-in-durable-objects"><u>SQLite in Durable Objects</u></a></p></td></tr></table><p>With each of these, we’ve faced a question — can we build this natively into the platform, in a way that removes, rather than adds complexity? Can we build it in a way that lets developers focus on building and shipping, rather than managing infrastructure, so that they don’t have to be a distributed systems engineer to build distributed systems?</p><p>In each instance, the answer has been YES. We try to solve problems in a way that simplifies things for developers in the long run, even if that is the harder path for us to take ourselves. If we didn’t, you’d be right to ask — why not self-host and manage all of this myself? What’s the point of the cloud if I’m still provisioning and managing infrastructure? These are the questions many are asking today about the earlier generation of cloud providers.</p><p>Pushing ourselves to build platform-native products and features helped us answer this question. Particularly because some of these actually use containers behind the scenes, even though as a developer you never interact with or think about containers yourself.</p><p>If you’ve used AI inference on GPUs with <a href="https://developers.cloudflare.com/workers-ai/"><u>Workers AI</u></a>, spun up headless browsers with <a href="https://developers.cloudflare.com/browser-rendering/"><u>Browser Rendering</u></a>, or enqueued build jobs with the new <a href="https://developers.cloudflare.com/workers/ci-cd/"><u>Workers Builds</u></a>, you’ve run containers on our network, without even knowing it. But to do so, we needed to be able to run untrusted code across Cloudflare’s network, outside a <a href="https://developers.cloudflare.com/workers/reference/how-workers-works/#isolates"><u>v8 isolate</u></a>, in a way that fits what we promise:</p><ol><li><p>You shouldn’t have to think about regions or data centers. Routing, scaling, load balancing, scheduling, and capacity are our problem to solve, not yours, with tools like <a href="https://blog.cloudflare.com/announcing-workers-smart-placement/"><u>Smart Placement</u></a>.</p></li><li><p>You should be able to build distributed systems without being a distributed systems engineer.</p></li><li><p>Every millisecond matters — Cloudflare has to be fast.</p></li></ol><p>There wasn’t an off-the-shelf container platform that solved for what we needed, so we built it ourselves — from scheduling to IP address management, pulling and caching images, to improving startup times and more. Our container platform powers many of our newest products, so we wanted to share how we built it, optimized it, and well, you can probably guess what’s next.</p>
    <div>
      <h2>Global scheduling — “The Network is the Computer”</h2>
      <a href="#global-scheduling-the-network-is-the-computer">
        
      </a>
    </div>
    <p>Cloudflare serves the entire world — region: earth. Rather than asking developers to provision resources in specific regions, data centers and availability zones, we think <a href="https://blog.cloudflare.com/the-network-is-the-computer/"><u>“The Network is the Computer”</u></a>. When you build on Cloudflare, you build software that runs on the Internet, not just in a data center.</p><p>When we started working on this, Cloudflare’s architecture was to just run every service via <a href="https://systemd.io/"><u>systemd</u></a> on every server (we call them “metals” — we <a href="https://blog.cloudflare.com/gen-12-servers"><u>run our own hardware</u></a>), allowing all services to take advantage of new capacity we add to our network. That fits running <a href="https://blog.cloudflare.com/upgrading-one-of-the-oldest-components-in-cloudflare-software-stack/"><u>NGINX</u></a> and a few dozen other services, but cannot fit a world where we need to run <i>many thousands</i> of different compute heavy, resource hungry workloads. We’d <a href="https://blog.cloudflare.com/its-crowded-in-here/"><u>run out of space</u></a> just trying to load all of them! Consider a canonical AI workload — deploying <a href="https://blog.cloudflare.com/meta-llama-3-1-available-on-workers-ai/"><u>Llama 3.1 8B</u></a> to an inference server. If we simply ran a Llama 3.1 8B service on every Cloudflare metal, we’d have no flexibility to use GPUs for the <a href="https://developers.cloudflare.com/workers-ai/models/"><u>many other models</u></a> that Workers AI supports.</p><p>We needed something that would allow us to still take advantage of the full capacity of Cloudflare’s entire network, not just the capacity of individual machines. And ideally not put that burden on the developer.</p><p>The answer: we built a control plane on our own Developer Platform that lets us schedule a container anywhere on <a href="https://www.cloudflare.com/en-gb/network/"><u>Cloudflare’s Network</u></a>:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/63nPDnWzVLvx2OB9ZPZXM5/d08be3dd36fbd08bb7205a82b20bf90e/BLOG-2573_2.png" />
          </figure><p>The global scheduler is built on Cloudflare <a href="https://developers.cloudflare.com/workers/"><u>Workers</u></a>, <a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects</u></a>, and <a href="https://developers.cloudflare.com/kv/"><u>KV</u></a>, and decides which Cloudflare location to schedule the container to run in. Each location then runs <i>its own</i> scheduler, which decides which metals within that location to schedule the container to run on. Location schedulers monitor compute capacity, and expose this to the global scheduler. This allows Cloudflare to dynamically place workloads based on capacity and hardware availability (e.g. multiple types of GPUs) across our network.</p>
    <div>
      <h2>Why does global scheduling matter?</h2>
      <a href="#why-does-global-scheduling-matter">
        
      </a>
    </div>
    <p>When you run compute on a first generation cloud, the “contract” between the developer and the platform is that the developer must specify what runs where. This is regional scheduling, the status quo.</p><p>Let’s imagine for a second if we applied regional scheduling to running compute on Cloudflare’s network, with locations in <a href="https://www.cloudflare.com/en-gb/network/"><u>330+ cities, across 120+ countries</u></a>. One of the obvious reasons people tell us they want to run on Cloudflare is because we have compute in places where others don’t, within 50ms of 95% of the world’s Internet-connected population. In South America, other clouds have one region in one city. Cloudflare has 19:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/53Ba51lqKDRNQPbEBselC3/0b9cd69b4f9fa29dad355df0076d05b9/BLOG-2573_3.png" />
          </figure><p>Running anywhere means you can be faster, highly available, and have more control over data location. But with regional scheduling, the more locations you run in, the more work you have to do. You configure and manage load balancing, routing, auto-scaling policies and more. Balancing performance and cost in a multi-region setup is literally a full-time job (or more) at most companies who have reached meaningful scale on traditional clouds.</p><p>But most importantly, no matter what tools you bring, you were the one who told the cloud provider, “run this container over here”. The cloud platform can’t move it for you, even if moving it would make your workload faster. This prevents the platform from adding locations, because for each location, it has to convince developers to take action themselves to move their compute workloads to the new location. Each new location carries a risk that developers won’t migrate workloads to it, or migrate too slowly, delaying the return on investment.</p><p>Global scheduling means Cloudflare can add capacity and use it immediately, letting you benefit from it. The “contract” between us and our customers isn’t tied to a specific datacenter or region, so we have permission to move workloads around to benefit customers. This flexibility plays an essential role in all of our own uses of our container platform, starting with GPUs and AI.</p>
    <div>
      <h2>GPUs everywhere: Scheduling large images with Workers AI</h2>
      <a href="#gpus-everywhere-scheduling-large-images-with-workers-ai">
        
      </a>
    </div>
    <p>In late 2023, <a href="https://blog.cloudflare.com/workers-ai/"><u>we launched Workers AI</u></a>, which provides fast, easy to use, and affordable GPU-backed AI inference.</p><p>The more efficiently we can use our capacity, the better pricing we can offer. And the faster we can make changes to which models run in which Cloudflare locations, the closer we can move AI inference to the application, <a href="https://blog.cloudflare.com/making-workers-ai-faster/"><u>lowering Time to First Token (TTFT)</u></a>. This also allows us to be more resilient to spikes in inference requests.</p><p>AI models that rely on GPUs present three challenges though:</p><ol><li><p>Models have different GPU memory needs. GPU memory is the most scarce resource, and different GPUs have different amounts of memory.</p></li><li><p>Not all container runtimes, such as <a href="https://github.com/firecracker-microvm/firecracker/issues/1179"><u>Firecracker</u></a>, support GPU drivers and other dependencies.</p></li><li><p>AI models, particularly LLMs, are very large. Even a smaller parameter model, like <a href="https://developers.cloudflare.com/workers-ai/models/llama-3.1-8b-instruct/"><u>@cf/meta/llama-3.1-8b-instruct</u></a>, is at least 5 GB. The larger the model, the more bytes we need to pull across the network when scheduling a model to run in a new location.</p></li></ol><p>Let’s dive into how we solved each of these…</p><p>First, GPU memory needs. The global scheduler knows which Cloudflare locations have blocks of GPU memory available, and then delegates scheduling the workload on a specific metal to the local scheduler. This allows us to prioritize placement of AI models that use a large amount of GPU memory, and then move smaller models to other machines in the same location. By doing this, we maximize the overall number of locations that we run AI models in, and maximize our efficiency.</p><p>Second, container runtimes and GPU support. Thankfully, from day one we built our container platform to be <i>runtime agnostic</i>. Using <a href="https://blog.cloudflare.com/how-we-use-hashicorp-nomad/"><u>a runtime agnostic scheduler</u></a>, we’re able to support <a href="https://gvisor.dev/"><u>gVisor</u></a>, <a href="https://firecracker-microvm.github.io/"><u>Firecracker</u></a> microVMs, and traditional VMs with <a href="https://www.qemu.org/"><u>QEMU</u></a>. We are also evaluating adding support for another one: <a href="https://github.com/cloud-hypervisor/cloud-hypervisor"><u>cloud-hypervisor</u></a> which is based on <a href="https://github.com/rust-vmm"><u>rust-vmm</u></a> and has a few compelling advantages for our use case:</p><ol><li><p>GPU passthrough <a href="https://github.com/cloud-hypervisor/cloud-hypervisor/blob/main/docs/vfio.md"><u>support</u></a> using VFIO</p></li><li><p><a href="https://github.com/cloud-hypervisor/cloud-hypervisor/blob/main/docs/device_model.md#vhost-user-net"><u>vhost-user-net</u></a> support, enabling high throughput between the host network interface and the VM</p></li><li><p><a href="https://github.com/cloud-hypervisor/cloud-hypervisor/blob/main/docs/device_model.md#vhost-user-net"><u>vhost-user-blk</u></a> support, adding flexibility to introduce novel network-based storage backed by other Cloudflare Workers products</p></li><li><p>all the while being a smaller codebase than QEMU and written in a memory-safe language</p></li></ol><p>Our goal isn’t to build a platform that makes you as the developer choose between runtimes, and ask, “should I use Firecracker or gVisor”. We needed this flexibility in order to be able to run workloads with different needs efficiently, including workloads that depend on GPUs. gVisor has <a href="https://gvisor.dev/docs/user_guide/gpu/"><u>GPU support</u></a>, while Firecracker microVMs currently <a href="https://github.com/firecracker-microvm/firecracker/issues/1179"><u>does not</u></a>.</p><p>gVisor’s main component is an application kernel (called Sentry) that implements a Linux-like interface but is written in a memory-safe language (Go) and runs in userspace. It works by intercepting application system calls and acting as the guest kernel, without the need for translation through virtualized hardware.</p><p>The resource footprint of a containerized application running on gVisor is lower than a VM because it does not require managing virtualized hardware and booting up a kernel instance. However, this comes at the price of reduced application compatibility and higher per-system call overhead.</p><p>To add GPU support, the Google team introduced <a href="https://gvisor.dev/docs/user_guide/gpu/"><u>nvproxy</u></a> which works using the same principles as described above for syscalls: it intercepts <a href="https://en.wikipedia.org/wiki/Ioctl"><u>ioctls</u></a> destined to the GPU and proxies a subset to the GPU kernel module.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1jqt4l5joKUkPNek1xACaM/64df1f3dcab2cae7d9ecf05deb407242/image__19_.png" />
          </figure><p>To solve the third challenge, and make scheduling fast with large models, we weren’t satisfied with the status quo. So we did something about it.</p>
    <div>
      <h2>Docker pull was too slow, so we fixed it (and cut the time in half)</h2>
      <a href="#docker-pull-was-too-slow-so-we-fixed-it-and-cut-the-time-in-half">
        
      </a>
    </div>
    <p>Many of the images we need to run for AI inference are over 15 GB. Specialized inference libraries and GPU drivers add up fast. For example, when we make a scheduling decision to run a fresh container in Tokyo, naively running docker pull to fetch the image from a storage bucket in Los Angeles would be unacceptably slow. And scheduling speed is critical to being able to scale up and down in new locations in response to changes in traffic.</p><p>We had 3 essential requirements:</p><ul><li><p>Pulling and pushing very large images should be fast</p></li><li><p>We should not rely on a single point of failure</p></li><li><p>Our teams shouldn’t waste time managing image registries</p></li></ul><p>We needed globally distributed storage, so we used <a href="https://developers.cloudflare.com/r2/"><u>R2</u></a>. We needed the highest cache hit rate possible, so we used Cloudflare’s <a href="https://developers.cloudflare.com/cache/"><u>Cache</u></a>, and will soon use <a href="https://developers.cloudflare.com/cache/how-to/tiered-cache/"><u>Tiered Cache</u></a>. And we needed a fast container image registry that we could run everywhere, in every Cloudflare location, so we built and open-sourced <a href="https://github.com/cloudflare/serverless-registry"><u>serverless-registry</u></a>, which is built on <a href="https://developers.cloudflare.com/workers/"><u>Workers</u></a>. You can deploy serverless-registry to your own Cloudflare account in about 5 minutes. We rely on it in production.</p><p>This is fast, but we can be faster. Our performance bottleneck was, somewhat surprisingly, <a href="https://docs.docker.com/reference/cli/docker/image/push/"><u>docker push</u></a>. Docker uses <a href="https://www.gzip.org/"><u>gzip</u></a> to compress and decompress layers of images while pushing and pulling. So we started using <a href="https://github.com/facebook/zstd"><u>Zstandard</u></a> (zstd) instead, which compresses and decompresses faster, and results in smaller compressed files.</p><p>In order to build, chunk, and push these images to the R2 registry, we built a custom CLI tool that we use internally in lieu of running docker build and docker push. This makes it easy to use zstd and split layers into 500 MB chunks, which allows uploads to be processed by Workers while staying under <a href="https://developers.cloudflare.com/workers/platform/limits/#request-limits"><u>body size limits</u></a>.</p><p>Using our custom build and push tool doubled the speed of image pulls. Our 30 GB GPU images now pull in 4 minutes instead of 8. We plan on open sourcing this tool in the near future.</p>
    <div>
      <h2>Anycast is the gift that keeps on simplifying — Virtual IPs and the Global State Router</h2>
      <a href="#anycast-is-the-gift-that-keeps-on-simplifying-virtual-ips-and-the-global-state-router">
        
      </a>
    </div>
    <p>We still had another challenge to solve. And yes, we solved it with anycast. We’re Cloudflare, did you expect anything else?</p><p>First, a refresher — Cloudflare operates <a href="https://blog.cloudflare.com/unimog-cloudflares-edge-load-balancer/"><u>Unimog</u></a>, a Layer 4 load balancer that handles all incoming Cloudflare traffic. Cloudflare’s network uses <a href="https://www.cloudflare.com/learning/cdn/glossary/anycast-network"><u>anycast</u></a>, which allows a single IP address to route requests to a variety of different locations. For most Cloudflare services with anycast, the given IP address will route to the nearest Cloudflare data center, reducing latency. Since Cloudflare runs almost every service in every data center, Unimog can simply route traffic to any Cloudflare metal that is online and has capacity, without needing to map traffic to a specific service that runs on specific metals, only in some locations.</p><p>The new compute-heavy, GPU-backed workloads we were taking on forced us to confront this fundamental “everything runs everywhere” assumption. If we run a containerized workflow in 20 Cloudflare locations, how does Unimog know which locations, and which metals, it runs in? You might say “just bring your own <a href="https://developers.cloudflare.com/reference-architecture/architectures/load-balancing/"><u>load balancer</u></a>” — but then what happens when you make scheduling decisions to migrate a workload between locations, scale up, or scale down?</p><p>Anycast is foundational to how we build fast and simple products on our network, and we needed a way to keep building new types of products this way — where a team can <a href="https://www.cloudflare.com/developer-platform/solutions/hosting/">deploy an application</a>, get back a single IP address, and rely on the platform to balance traffic, taking load, container health, and latency into account, without extra configuration. We started letting teams use the container platform without solving this, and it was painfully clear that we needed to do something about it.</p><p>So we started integrating directly into our networking stack, building a sidecar service to Unimog. We’ll call this the Global State Router. Here’s how it works:</p><ul><li><p>An eyeball makes a request to a virtual IP address issued by Cloudflare</p></li><li><p>Request sent to the best location as determined by BGP routing. This is anycast routing.</p></li><li><p>A small <a href="https://ebpf.io/what-is-ebpf/"><u>eBPF</u></a> program sits on the main networking interface and ensures packets bound to a virtual IP address are handled by the Global State Router.</p></li><li><p>The main Global State Router program contains a mapping of all anycast IPs addresses to potential end destination container IP addresses. It updates this mapping based on container health, readiness, distance, and latency. Using this information, it picks a best-fit container.</p></li><li><p>Packets are forwarded at the L4 layer.</p></li><li><p>When a target container’s server receives a packet, its own Global State Router program intercepts the packet and routes it to the local container.</p></li></ul>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/8ZICyfggbTEpkxki0OnYd/13a427a65b1e9d0c5d591468e3f10612/BLOG-2573_5.png" />
          </figure><p>This might sound like just a lower level networking detail, disconnected from developer experience. But by integrating directly with Unimog, we can let developers:</p><ol><li><p>Push a containerized application to Cloudflare.</p></li><li><p>Provide constraints, health checks, and load metrics that describe what the application needs.</p></li><li><p>Delegate scheduling and scaling many containers across Cloudflare’s network.</p></li><li><p>Get back a single IP address that can be used everywhere.</p></li></ol><p>We’re actively working on this, and are excited to continue building on Cloudflare’s anycast capabilities, and pushing to keep the simplicity of running “everywhere” with new categories of workloads.</p>
    <div>
      <h2>Low latency &amp; global — Remote Browser Isolation &amp; Browser Rendering</h2>
      <a href="#low-latency-global-remote-browser-isolation-browser-rendering">
        
      </a>
    </div>
    <p>Our container platform actually started because of a very specific challenge, running <a href="https://www.cloudflare.com/zero-trust/products/browser-isolation/"><u>Remote Browser Isolation</u></a> across our network. Remote Browser Isolation provides Chromium browsers that run on Cloudflare, in containers, rather than on the end user’s own computer. Only the rendered output is sent to the end user. This provides a layer of protection against zero-day browser vulnerabilities, phishing attacks, and ransomware.</p><p>Location is critical — people expect their interactions with a remote browser to feel just as fast as if it ran locally. If the server is thousands of miles away, the remote browser will feel slow. Running across <a href="https://www.cloudflare.com/network/"><u>Cloudflare’s network of over 330 locations</u></a> means the browser is nearly always as close to you as possible.</p><p>Imagine a user in Santiago, Chile, if they were to access a browser running in the same city, each interaction would incur negligible additional latency. Whereas a browser in Buenos Aires might add 21 ms, São Paulo might add 48 ms, Bogota might add 67 ms, and Raleigh, NC might add 128 ms. Where the container runs significantly impacts the latency of every user interaction with the browser, and therefore the experience as a whole.</p><p>It’s not just browser isolation that benefits from running near the user: WebRTC servers stream video better, multiplayer games have less lag, online advertisements can be served faster, financial transactions can be processed faster. Our container platform lets us run anything we need to near the user, no matter where they are in the world.</p>
    <div>
      <h2>Using spare compute — “off-peak” jobs for Workers CI/CD builds</h2>
      <a href="#using-spare-compute-off-peak-jobs-for-workers-ci-cd-builds">
        
      </a>
    </div>
    <p>At any hour of the day, Cloudflare has many CPU cores that sit idle. This is compute power that could be used for something else.</p><p>Via anycast, most of Cloudflare’s traffic is handled as close as possible to the eyeball (person) that requested it. Most of our traffic originates from eyeballs. And the eyeballs of (most) people are closed and asleep between midnight and 5:00 AM local time. While we use our compute capacity very efficiently during the daytime in any part of the world, overnight we have spare cycles. Consider what a map of the world looks like at nighttime in Europe and Africa:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/CtkNFxhvusjLx1khXh9gs/25b5a6579d8abd47bcaa15ba28eddc61/BLOG-2573_6.png" />
          </figure><p>As shown above, we can run containers during “off-peak” in Cloudflare locations receiving low traffic at night. During this time, the CPU utilization of a typical Cloudflare metal looks something like this:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6sXsQ3p19bFecokbR0X1yj/04419589e4f6796fe297435cd1029b5a/Screenshot_2024-09-27_at_2.20.50_PM.png" />
          </figure><p>We have many “background” compute workloads at Cloudflare. These are workloads that don’t actually need to run close to the eyeball because there is no eyeball waiting on the request. The challenge is that many of these workloads require running untrusted code — either a dependency on open-source code that we don’t trust enough to run outside of a sandboxed environment, or untrusted code that customers deploy themselves. And unlike <a href="https://developers.cloudflare.com/workers/configuration/cron-triggers/"><u>Cron Triggers</u></a>, which already make a best-effort attempt to use off-peak compute, these other workloads can’t run in <a href="https://developers.cloudflare.com/workers/reference/how-workers-works/#isolates"><u>v8 isolates</u></a>.</p><p>On Builder Day 2024, we <a href="https://blog.cloudflare.com/builder-day-2024-announcements"><u>announced Workers Builds in open beta</u></a>. You connect your Worker to a git repository, and Cloudflare builds and deploys your Worker each time you merge a pull request. Workers Builds run on our containers platform, using otherwise idle “off-peak” compute, allowing us to offer lower pricing, and hold more capacity for unexpected spikes in traffic in Cloudflare locations during daytime hours when load is highest. We preserve our ability to serve requests as close to the eyeball as possible where it matters, while using the full compute capacity of our network.</p><p>We developed a purpose-built API for these types of jobs. The Workers Builds service has zero knowledge of where Cloudflare has spare compute capacity on its network — it simply schedules an “off-peak” job to run on the containers platform, by defining a scheduling policy:</p><p><code>scheduling_policy: "off-peak"</code></p>
    <div>
      <h2>Making off-peak jobs faster with prewarmed images</h2>
      <a href="#making-off-peak-jobs-faster-with-prewarmed-images">
        
      </a>
    </div>
    <p>Just because a workload isn’t “eyeball-facing” doesn’t mean speed isn’t relevant. When a build job starts, you still want it to start as soon as possible.</p><p>Each new build requires a fresh container though, and we must avoid reusing containers to provide strong isolation between customers. How can we keep build job start times low, while using a new container for each job without over-provisioning? </p><p>We prewarm servers with the proper image. </p><p>Before a server becomes eligible to receive an “off peak” job, the container platform instructs it to download the correct image. Once the image is downloaded and cached locally, new containers can start quickly in a Firecracker VM after receiving a request for a new build. When a build completes, we throw away the container, and start the next build using a fresh container based on the prewarmed image.</p><p>Without prewarming, pulling and unpacking our Workers Build images would take roughly 75 seconds. With prewarming, we’re able to spin up a new container in under 10 seconds. We expect this to get even faster as we introduce optimizations like pre-booting images before new runs, or <a href="https://github.com/firecracker-microvm/firecracker/blob/main/docs/snapshotting/snapshot-support.md"><u>Firecracker snapshotting</u></a>, which can restore a VM in under 200ms.</p>
    <div>
      <h2>Workers and containers, better together</h2>
      <a href="#workers-and-containers-better-together">
        
      </a>
    </div>
    <p>As more of our own engineering teams rely on our containers platform in production, we’ve noticed a pattern: they want a deeper integration with Workers.</p><p>We plan to give it to them. </p><p>Let’s take a look at a project deployed on our container platform already, <a href="https://blog.cloudflare.com/key-transparency/"><u>Key Transparency</u></a>. If the container platform were highly integrated with Workers, what would this team’s experience look like?</p><p>Cloudflare regularly audits changes to public keys used by WhatsApp for encrypting messages between users. Much of the architecture is built on Workers, but there are long-running compute-intensive tasks that are better suited for containers.</p><p>We don’t want our teams to have to jump through hoops to deploy a container and integrate with Workers. They shouldn’t have to pick specific regions to run in, figure out scaling, expose IPs and handle IP updates, or set up Worker-to-container auth.</p><p>We’re still exploring many different ideas and API designs, and we want your feedback. But let’s imagine what it might look like to use Workers, Durable Objects and Containers together.</p><p>In this case, an outer layer of Workers handles most business logic and ingress, a specialized Durable Object is configured to run alongside our new container, and the platform ensures the image is loaded on the right metals and can scale to meet demand.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5GsXRuYvVELJY1syOmfq3z/47d3481116670de291be86438777fbc0/BLOG-2573_8.png" />
          </figure><p>I add a containerized app to the <a href="https://developers.cloudflare.com/workers/wrangler/configuration/"><code><u>wrangler.toml </u></code><u>configuration file</u></a> of my Worker (or <a href="https://blog.cloudflare.com/automatically-generating-cloudflares-terraform-provider"><u>Terraform</u></a>):</p>
            <pre><code>[[container-app]]
image = "./key-transparency/verifier/Dockerfile"
name = "verifier"

[durable_objects]
bindings = { name = "VERIFIER", class_name = "Verifier", container = "verifier" } }
</code></pre>
            <p>Then, in my Worker, I call the runVerification <a href="https://developers.cloudflare.com/workers/runtime-apis/rpc/"><u>RPC method</u></a> of my Durable Object:</p>
            <pre><code>fetch(request, env, ctx) {
  const id = new URL(request.url).searchParams.get('id')
  const durableObjectId = env.VERIFIER.idFromName(request.params.id);
  await env.VERIFIER.get(durableObjectId).runVerification()
  //...
}
</code></pre>
            <p>From my Durable Object I can boot, configure, mount storage buckets as directories, and make HTTP requests to the container:</p>
            <pre><code>class Verifier extends DurableObject {
  constructor(state, env) {
    this.ctx.blockConcurrency(async () =&gt; {

      // starts the container
      await this.ctx.container.start();

      // configures the container before accepting traffic
      const config = await this.state.storage.get("verifierConfig");
      await this.ctx.container.fetch("/set-config", { method: "PUT", body: config});
    })
  }

  async runVerification(updateId) {
    // downloads &amp; mounts latest updates from R2
    const latestPublicKeyUpdates = await this.env.R2.get(`public-key-updates/${updateId}`);
    await this.ctx.container.mount(`/updates/${updateId}`, latestPublicKeyUpdates);

    // starts verification via HTTP call 
    return await this.ctx.container.fetch(`/verifier/${updateId}`);
  }
}
</code></pre>
            <p>And… that’s it.</p><p>I didn’t have to worry about placement, scaling, service discovery authorization, and I was able to leverage integrations into other services like KV and R2 with just a few lines of code. The container platform took care of routing, placement, and auth. If I needed more instances, I could call the binding with a new ID, and the platform would scale up containers for me.</p><p>We are still in the early stages of building these integrations, but we’re excited about everything that containers will bring to Workers and vice versa.</p>
    <div>
      <h2>So, what do you want to build?</h2>
      <a href="#so-what-do-you-want-to-build">
        
      </a>
    </div>
    <p>If you’ve read this far, there’s a non-zero chance you were hoping to get to run a container yourself on our network. While we’re not ready (quite yet) to open up the platform to everyone, now that we’ve built a few GA products on our container platform, we’re looking for a handful of engineering teams to start building, in advance of wider availability in 2025. And we’re <a href="https://job-boards.greenhouse.io/cloudflare/jobs/5547603"><u>continuing to hire engineers</u></a> to work on this.</p><p>We’ve told you about our use cases for containers, and now it’s your turn. If you’re interested, <a href="https://forms.gle/msrkBLBYNFFYRaqY8"><u>tell us here</u></a> what you want to build, and why it goes beyond what’s possible today in <a href="https://developers.cloudflare.com/workers/"><u>Workers</u></a> and on our <a href="https://developers.cloudflare.com/products/?product-group=Developer+platform"><u>Developer Platform</u></a>. What do you wish you could build on Cloudflare, but can’t yet today?</p> ]]></content:encoded>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[AI]]></category>
            <category><![CDATA[Durable Objects]]></category>
            <guid isPermaLink="false">5KJejYd85inMDIZUZ6z17D</guid>
            <dc:creator>Brendan Irvine-Broque</dc:creator>
            <dc:creator>Thomas Lefebvre</dc:creator>
            <dc:creator>Mike Nomitch</dc:creator>
        </item>
        <item>
            <title><![CDATA[How we use HashiCorp Nomad]]></title>
            <link>https://blog.cloudflare.com/how-we-use-hashicorp-nomad/</link>
            <pubDate>Fri, 05 Jun 2020 11:00:00 GMT</pubDate>
            <description><![CDATA[ A walkthrough on how we are improving the reliability of our management services running in each data center by using Nomad for dynamic task scheduling. ]]></description>
            <content:encoded><![CDATA[ <p>In this blog post, we will walk you through the reliability model of services running in our more than 200 edge cities worldwide. Then, we will go over how deploying a new dynamic task scheduling system, HashiCorp Nomad, helped us improve the availability of services in each of those data centers, covering how we deployed Nomad and the challenges we overcame along the way. Finally, we will show you both how we currently use Nomad and how we are planning on using it in the future.</p>
    <div>
      <h2>Reliability model of services running in each data center</h2>
      <a href="#reliability-model-of-services-running-in-each-data-center">
        
      </a>
    </div>
    <p>For this blog post, we will distinguish between two different categories of services running in each data center:</p><ul><li><p><b>Customer-facing services</b>: all of our stack of products that our customers use, such as caching, <a href="https://www.cloudflare.com/learning/ddos/glossary/web-application-firewall-waf/">WAF</a>, DDoS protection, rate-limiting, load-balancing, etc.</p></li><li><p><b>Management services</b>: software required to operate the data center, that is not in the direct request path of customer traffic.</p></li></ul>
    <div>
      <h3>Customer-facing services</h3>
      <a href="#customer-facing-services">
        
      </a>
    </div>
    <p>The reliability model of our customer-facing services is to run them on all machines in each data center. This works well as it allows each data center’s capacity to scale dynamically by adding more machines.</p><p>Scaling is especially made easy thanks to our dynamic load balancing system, Unimog, which runs on each machine. Its role is to continuously re-balance traffic based on current resource usage and to check the health of services. This helps provide resiliency to individual machine failures and ensures resource usage is close to identical on all machines.</p><p>As an example, here is the CPU usage over a day in one of our data centers where each time series represents one machine and the different colors represent different generations of hardware. Unimog keeps all machines processing traffic and at roughly the same CPU utilization.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1zlf1rrhRjEVE9t65RwNsR/0b44268763f3527aac3cdda245c412c2/image2-1.png" />
            
            </figure>
    <div>
      <h3>Management services</h3>
      <a href="#management-services">
        
      </a>
    </div>
    <p>Some of our larger data centers have a substantial number of machines, but sometimes we need to reliably run just a single or a few instances of a management service in each location.</p><p>There are currently a couple of options to do this, each have their own pros and cons:</p><ol><li><p>Deploying the service to all machines in the data center:</p><ul><li><p><b>Pro</b>: it ensures the service’s reliability</p></li><li><p><b>Con</b>: it unnecessarily uses resources which could have been used to serve customer traffic and is not cost-effective</p></li></ul></li><li><p>Deploying the service to a static handful of machines in each data center:</p><ul><li><p><b>Pro</b>: it is less wasteful of resources and more cost-effective</p></li><li><p><b>Con</b>: it runs the risk of service unavailability when those handful of machines unexpectedly fail</p></li></ul></li></ol><p>A third, more viable option, is to use dynamic task scheduling so that only the right amount of resources are used while ensuring reliability.</p>
    <div>
      <h3>A need for more dynamic task scheduling</h3>
      <a href="#a-need-for-more-dynamic-task-scheduling">
        
      </a>
    </div>
    <p>Having to pick between two suboptimal reliability model options for management services we want running in each data center was not ideal.</p><p>Indeed, some of those services, even though they are not in the request path, are required to continue operating the data center. If the machines running those services become unavailable, in some cases we have to temporarily disable the data center while recovering them. Doing so automatically re-routes users to the next available data center and doesn’t cause disruption. In fact, the entire Cloudflare network is designed to operate with data centers being disabled and brought back automatically. But it’s optimal to route end users to a data center near them so we want to minimize any data center level downtime.</p><p>This led us to realize we needed a system to ensure a certain number of instances of a service were running in each data center, regardless of which physical machine ends up running it.</p><p>Customer-facing services run on all machines in each data center and do not need to be onboarded to that new system. On the other hand, services currently running on a fixed subset of machines with sub-optimal reliability guarantees and services which don’t need to run on all machines are good candidates for onboarding.</p>
    <div>
      <h3>Our pick: HashiCorp Nomad</h3>
      <a href="#our-pick-hashicorp-nomad">
        
      </a>
    </div>
    <p>Armed with our set of requirements, we conducted some research on candidate solutions.</p><p>While Kubernetes was another option, we decided to use HashiCorp’s <a href="https://www.nomadproject.io/">Nomad</a> for the following reasons:</p><ul><li><p>Satisfies our initial requirement, which was reliably running a single instance of a binary with resource isolation in each data center.</p></li><li><p>Has few dependencies and a straightforward integration with Consul. Consul is another piece of HashiCorp software we had already deployed in each datacenter. It provides distributed key-value storage and service discovery capabilities.</p></li><li><p>Is lightweight (single Go binary), easy to deploy and provision new clusters which is a plus when deploying as many clusters as we have data centers.</p></li><li><p>Has a modular task driver (part responsible for executing tasks and providing resource isolation) architecture to support not only containers but also binaries and any custom task driver.</p></li><li><p>Is open source and written in Go. We have Go language experience within the team, and Nomad has a responsive community of maintainers on GitHub.</p></li></ul>
    <div>
      <h3>Deployment architecture</h3>
      <a href="#deployment-architecture">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7bD7wQ3kcMFYwDyGWzH1Jp/a70cbcd42ca55f92643aafeecc16046b/image3.png" />
            
            </figure><p>Nomad is split in two different pieces:</p><ol><li><p><b>Nomad Server:</b> instances forming the cluster responsible for scheduling, five per data center to provide sufficient failure tolerance</p></li><li><p><b>Nomad Client:</b> instances executing the actual tasks, running on all machines in every data center</p></li></ol><p>To guarantee Nomad Server cluster reliability, we deployed instances on machines which are part of different failure domains:</p><ul><li><p>In different inter-connected physical data centers forming a single location</p></li><li><p>In different racks, connected to different switches</p></li><li><p>In different multi-node chassis (most of our edge hardware comes in the form of multi-node chassis, one chassis contains four individual servers)</p></li></ul><p>We also added logic to our configuration management tool to ensure we always keep a consistent number of Nomad Server instances regardless of the expansions and decommissions of servers happening on a close to daily basis.</p><p>The logic is rather simple, as server expansions and decommissions happen, the Nomad Server role gets redistributed to a new list of machines. Our configuration management tool then ensures that Nomad Server runs on the new machines before turning it off on the old ones.</p><p>Additionally, because server expansions and decommissions affect a subset of racks at a time and the Nomad Server role assignment logic provides rack-diversity guarantees, the cluster stays healthy as quorum is kept at all times.</p>
    <div>
      <h3>Job files</h3>
      <a href="#job-files">
        
      </a>
    </div>
    <p>Nomad job files are templated and checked into a git repository. Our configuration management tool then ensures the jobs are scheduled in every data center. From there, Nomad takes over and ensures the jobs are running at all times in each data center.</p><p>By exposing rack <a href="https://www.nomadproject.io/docs/job-specification/meta/">metadata</a> to each Nomad Client, we are able to make sure each instance of a particular service runs in a different rack and is tied to a different failure domain. This way we make sure that the failure of one rack of servers won’t impact the service health as the service is also running in a different rack, unaffected by the failure.</p><p>We achieve this with the following job file <a href="https://www.nomadproject.io/docs/job-specification/constraint/">constraint</a>:</p>
            <pre><code>constraint {
  attribute = "${meta.rack}"
  operator  = "distinct_property"
}</code></pre>
            
    <div>
      <h3>Service discovery</h3>
      <a href="#service-discovery">
        
      </a>
    </div>
    <p>We leveraged Nomad integration with Consul to get Nomad jobs dynamically added to the Consul Service Catalog. This allows us to discover where a particular service is currently running in each data center by querying Consul. Additionally, with the Consul DNS Interface enabled, we can also use DNS-based lookups to target services running on Nomad.</p>
    <div>
      <h3>Observability</h3>
      <a href="#observability">
        
      </a>
    </div>
    <p>To be able to properly operate as many Nomad clusters as we have data centers, good observability on Nomad clusters and services running on those clusters was essential.</p><p>We use Prometheus to scrape Nomad Server and Client instances running in each data center and Alertmanager to alert on key metrics. Using Prometheus metrics, we built a Grafana dashboard to provide visibility on each cluster.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2Y0I7dOMGQEs3TbRNyxbKF/17511a97b13b895e14424e80b0716fb2/image1-1.png" />
            
            </figure><p>We <a href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#consul_sd_config">set up</a> our Prometheus instances to discover services running on Nomad by querying the Consul Service Directory and scraping their metrics periodically using the following Prometheus configuration:</p>
            <pre><code>- consul_sd_configs:
  - server: localhost:8500
  job_name: management_service_via_consul
  relabel_configs:
  - action: keep
    regex: management-service
    source_labels:
    - __meta_consul_service</code></pre>
            <p>We then use those metrics to create Grafana dashboards and set up alerts for services running on Nomad.</p><p>To restrict access to Nomad API endpoints, we enabled mutual TLS authentication and are generating client certificates for each entity interacting with Nomad. This way, only entities with a valid client certificate can interact with Nomad API endpoints in order to schedule jobs or perform any CLI operation.</p>
    <div>
      <h2>Challenges</h2>
      <a href="#challenges">
        
      </a>
    </div>
    <p>Deploying a new component always comes with its set of challenges; here is a list of a few hurdles we have had to overcome along the way.</p>
    <div>
      <h3>Initramfs rootfs and pivot_root</h3>
      <a href="#initramfs-rootfs-and-pivot_root">
        
      </a>
    </div>
    <p>When starting to use the <code>exec</code> driver to run binaries isolated in a <code>chroot</code> environment, we noticed our stateless root partition running on <a href="https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt">initramfs</a> was not supported as the task would not start and we got this error message in our logs:</p><p><code>Feb 12 19:49:03 machine nomad-client[258433]: 2020-02-12T19:49:03.332Z [ERROR] client.alloc_runner.task_runner: running driver failed: alloc_id=fa202-63b-33f-924-42cbd5 task=server error="failed to launch command with executor: rpc error: code = Unknown desc = container_linux.go:346: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:109: jailing process inside rootfs caused \\\"pivot_root invalid argument\\\"\"""</code></p><p>We filed a GitHub <a href="https://github.com/hashicorp/nomad/issues/7136">issue</a> and submitted a workaround <a href="https://github.com/hashicorp/nomad/pull/7149">pull request</a> which was promptly reviewed and merged upstream.</p><p>In parallel, for maximum isolation security, we worked on enabling <code>pivot_root</code> in our setup by modifying our boot process and other team members developed and proposed a patch to the <a href="https://lore.kernel.org/linux-fsdevel/20200305193511.28621-1-ignat@cloudflare.com/">kernel mailing list</a> to make it easier in the future.</p>
    <div>
      <h3>Resource usage containment</h3>
      <a href="#resource-usage-containment">
        
      </a>
    </div>
    <p>One very important aspect was to make sure the resource usage of tasks running on Nomad would not disrupt other services colocated on the same machine.</p><p>Disk space is a shared resource on every machine and being able to set a quota for Nomad was a must. We achieved this by isolating the Nomad data directory to a dedicated fixed-size mount point on each machine. Limiting disk bandwidth and IOPS, however, is not currently supported out of the box by Nomad.</p><p>Nomad job files have a <a href="https://www.nomadproject.io/docs/job-specification/resources/#memory">resources section</a> where memory and CPU usage can be limited (memory is in MB, cpu is in MHz):</p>
            <pre><code>resources {
  memory = 2000
  cpu = 500
}</code></pre>
            <p>This uses cgroups under the hood and our testing showed that while memory limits are enforced as one would expect, the CPU limits are soft limits and not enforced as long as there is available CPU on the host machine.</p>
    <div>
      <h3>Workload (un)predictability</h3>
      <a href="#workload-un-predictability">
        
      </a>
    </div>
    <p>As mentioned above, all machines currently run the same customer-facing workload. Scheduling individual jobs dynamically with Nomad to run on single machines challenges that assumption.</p><p>While our dynamic load balancing system, Unimog, balances requests based on resource usage to ensure it is close to identical on all machines, batch type jobs with spiky resource usage can pose a challenge.</p><p>We will be paying attention to this as we onboard more services and:</p><ul><li><p>attempt to limit resource usage spikiness of Nomad jobs with constraints aforementioned</p></li><li><p>ensure Unimog adjusts to this batch type workload and does not end up in a positive feedback loop</p></li></ul>
    <div>
      <h2>What we are running on Nomad</h2>
      <a href="#what-we-are-running-on-nomad">
        
      </a>
    </div>
    <p>Now Nomad has been deployed in every data center, we are able to improve the reliability of management services essential to operations by gradually onboarding them. We took a first step by onboarding our reboot and maintenance management service.</p>
    <div>
      <h3>Reboot and maintenance management service</h3>
      <a href="#reboot-and-maintenance-management-service">
        
      </a>
    </div>
    <p>In each data center, we run a service which facilitates online unattended rolling reboots and maintenance of machines. This service used to run on a single well-known machine in each data center. This made it vulnerable to single machine failures and when down prevented machines from enabling automatically after a reboot. Therefore, it was a great first service to be onboarded to Nomad to improve its reliability.</p><p>We now have a guarantee this service is always running in each data center regardless of individual machine failures. Instead of other machines relying on a well-known address to target this service, they now query Consul DNS and dynamically figure out where the service is running to interact with it.</p><p>This is a big improvement in terms of reliability for this service, therefore many more management services are expected to follow in the upcoming months and we are very excited for this to happen.</p> ]]></content:encoded>
            <category><![CDATA[Edge]]></category>
            <category><![CDATA[HashiCorp]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <guid isPermaLink="false">26zJkCEC3Mp3Il7GFxJuX3</guid>
            <dc:creator>Thomas Lefebvre</dc:creator>
        </item>
    </channel>
</rss>