
<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>Fri, 03 Apr 2026 17:07:33 GMT</lastBuildDate>
        <item>
            <title><![CDATA[A one-line Kubernetes fix that saved 600 hours a year]]></title>
            <link>https://blog.cloudflare.com/one-line-kubernetes-fix-saved-600-hours-a-year/</link>
            <pubDate>Thu, 26 Mar 2026 13:00:00 GMT</pubDate>
            <description><![CDATA[ When we investigated why our Atlantis instance took 30 minutes to restart, we discovered a bottleneck in how Kubernetes handles volume permissions. By adjusting the fsGroupChangePolicy, we reduced restart times to 30 seconds. ]]></description>
            <content:encoded><![CDATA[ <p>Every time we restarted Atlantis, the tool we use to plan and apply Terraform changes, we’d be stuck for 30 minutes waiting for it to come back up. No plans, no applies, no infrastructure changes for any repository managed by Atlantis. With roughly 100 restarts a month for credential rotations and onboarding, that added up to over <b>50 hours of blocked engineering time every month</b>, and paged the on-call engineer every time.</p><p>This was ultimately caused by a safe default in Kubernetes that had silently become a bottleneck as the persistent volume used by Atlantis grew to millions of files. Here’s how we tracked it down and fixed it with a one-line change.</p>
    <div>
      <h3>Mysteriously slow restarts</h3>
      <a href="#mysteriously-slow-restarts">
        
      </a>
    </div>
    <p>We manage dozens of Terraform projects with GitLab merge requests (MRs) using <a href="https://www.runatlantis.io/"><u>Atlantis</u></a>, which handles planning and applying. It enforces locking to ensure that only one MR can modify a project at a time. </p><p>It runs on Kubernetes as a singleton StatefulSet and relies on a Kubernetes PersistentVolume (PV) to keep track of repository state on disk. Whenever a Terraform project needs to be onboarded or offboarded, or credentials used by Terraform are updated, we have to restart Atlantis to pick up those changes — a process that can take 30 minutes.</p><p>The slow restart was apparent when we recently ran out of inodes on the persistent storage used by Atlantis, forcing us to restart it to resize the volume. Inodes are consumed by each file and directory entry on disk, and the number available to a filesystem is determined by parameters passed when creating it. The Ceph persistent storage implementation provided by our Kubernetes platform does not expose a way to pass flags to <code>mkfs</code>, so we’re at the mercy of default values: growing the filesystem is the only way to grow available inodes, and restarting a PV requires a pod restart. </p><p>We talked about extending the alert window, but that would just mask the problem and delay our response to actual issues. Instead, we decided to investigate exactly why it was taking so long.</p>
    <div>
      <h3>Bad behavior</h3>
      <a href="#bad-behavior">
        
      </a>
    </div>
    <p>When we were asked to do a rolling restart of Atlantis to pick up a change to the secrets it uses, we would run <code>kubectl rollout restart statefulset atlantis</code>, which would gracefully terminate the existing Atlantis pod before spinning up a new one. The new pod would appear almost immediately, but looking at it would show:</p>
            <pre><code>$ kubectl get pod atlantis-0
atlantis-0                                                        0/1     
Init:0/1     0             30m
</code></pre>
            <p>...so what gives? Naturally, the first thing to check would be events for that pod. It's waiting around for an init container to run, so maybe the pod events would illuminate why?</p>
            <pre><code>$ kubectl events --for=pod/atlantis-0
LAST SEEN   TYPE      REASON                   OBJECT                   MESSAGE
30m         Normal    Killing                  Pod/atlantis-0   Stopping container atlantis-server
30m        Normal    Scheduled                Pod/atlantis-0   Successfully assigned atlantis/atlantis-0 to 36com1167.cfops.net
22s         Normal    Pulling                  Pod/atlantis-0   Pulling image "oci.example.com/git-sync/master:v4.1.0"
22s         Normal    Pulled                   Pod/atlantis-0   Successfully pulled image "oci.example.com/git-sync/master:v4.1.0" in 632ms (632ms including waiting). Image size: 58518579 bytes.</code></pre>
            <p>That looks almost normal... but what's taking so long between scheduling the pod and actually starting to pull the image for the init container? Unfortunately that was all the data we had to go on from Kubernetes itself. But surely there <i>had</i> to be something more that can tell us why it's taking so long to actually start running the pod.</p>
    <div>
      <h3>Going deeper</h3>
      <a href="#going-deeper">
        
      </a>
    </div>
    <p>In Kubernetes, a component called <code>kubelet</code> that runs on each node is responsible for coordinating pod creation, mounting persistent volumes, and many other things. From my time on our Kubernetes team, I know that <code>kubelet</code> runs as a systemd service and so its logs should be available to us in Kibana. Since the pod has been scheduled, we know the host name we're interested in, and the log messages from <code>kubelet</code> include the associated object, so we could filter for <code>atlantis</code> to narrow down the log messages to anything we found interesting.</p><p>We were able to observe the Atlantis PV being mounted shortly after the pod was scheduled. We also observed all the secret volumes mount without issue. However, there was still a big unexplained gap in the logs. We saw:</p>
            <pre><code>[operation_generator.go:664] "MountVolume.MountDevice succeeded for volume \"pvc-94b75052-8d70-4c67-993a-9238613f3b99\" (UniqueName: \"kubernetes.io/csi/rook-ceph-nvme.rbd.csi.ceph.com^0001-000e-rook-ceph-nvme-0000000000000002-a6163184-670f-422b-a135-a1246dba4695\") pod \"atlantis-0\" (UID: \"83089f13-2d9b-46ed-a4d3-cba885f9f48a\") device mount path \"/state/var/lib/kubelet/plugins/kubernetes.io/csi/rook-ceph-nvme.rbd.csi.ceph.com/d42dcb508f87fa241a49c4f589c03d80de2f720a87e36932aedc4c07840e2dfc/globalmount\"" pod="atlantis/atlantis-0"
[pod_workers.go:1298] "Error syncing pod, skipping" err="unmounted volumes=[atlantis-storage], unattached volumes=[], failed to process volumes=[]: context deadline exceeded" pod="atlantis/atlantis-0" podUID="83089f13-2d9b-46ed-a4d3-cba885f9f48a"
[util.go:30] "No sandbox for pod can be found. Need to start a new one" pod="atlantis/atlantis-0"</code></pre>
            <p>The last two messages looped several times until eventually we observed the pod actually start up properly.</p><p>So <code>kubelet</code> thinks that the pod is otherwise ready to go, but it's not starting it and something's timing out.</p>
    <div>
      <h3>The missing piece</h3>
      <a href="#the-missing-piece">
        
      </a>
    </div>
    <p>The lowest-level logs we had on the pod didn't show us what's going on. What else do we have to look at? Well, the last message before it hangs is the PV being mounted onto the node. Ordinarily, if the PV has issues mounting (e.g. due to still being stuck mounted on another node), that will bubble up as an event. But something's still going on here, and the only thing we have left to drill down on is the PV itself. So I plug that into Kibana, since the PV name is unique enough to make a good search term... and immediately something jumps out:</p>
            <pre><code>[volume_linux.go:49] Setting volume ownership for /state/var/lib/kubelet/pods/83089f13-2d9b-46ed-a4d3-cba885f9f48a/volumes/kubernetes.io~csi/pvc-94b75052-8d70-4c67-993a-9238613f3b99/mount and fsGroup set. If the volume has a lot of files then setting volume ownership could be slow, see https://github.com/kubernetes/kubernetes/issues/69699</code></pre>
            <p>Remember how I said at the beginning we'd just run out of inodes? In other words, we have a <i>lot</i> of files on this PV. When the PV is mounted, <code>kubelet</code> is running <code>chgrp -R</code> to recursively change the group on every file and folder across this filesystem. No wonder it was taking so long — that's a ton of entries to traverse even on fast flash storage!</p><p>The pod's <code>spec.securityContext</code> included <code>fsGroup: 1</code>, which ensures that processes running under GID 1 can access files on the volume. Atlantis runs as a non-root user, so without this setting it wouldn’t have permission to read or write to the PV. The way Kubernetes enforces this is by recursively updating ownership on the entire PV <i>every time it's mounted</i>.</p>
    <div>
      <h3>The fix</h3>
      <a href="#the-fix">
        
      </a>
    </div>
    <p>Fixing this was heroically...boring. Since version 1.20, Kubernetes has supported an additional field on <code>pod.spec.securityContext</code> called <code>fsGroupChangePolicy</code>. This field defaults to <code>Always</code>, which leads to the exact behavior we see here. It has another option, <code>OnRootMismatch</code>, to only change permissions if the root directory of the PV doesn't have the right permissions. If you don’t know exactly how files are created on your PV, do not set <code>fsGroupChangePolicy</code>: <code>OnRootMismatch</code>. We checked to make sure that nothing should be changing the group on anything in the PV, and then set that field: </p>
            <pre><code>spec:
  template:
    spec:
      securityContext:
        fsGroupChangePolicy: OnRootMismatch</code></pre>
            <p>Now, it takes about 30 seconds to restart Atlantis, down from the 30 minutes it was when we started.</p><p>Default Kubernetes settings are sensible for small volumes, but they can become bottlenecks as data grows. For us, this one-line change to <code>fsGroupChangePolicy</code> reclaimed nearly 50 hours of blocked engineering time per month. This was time our teams had been spending waiting for infrastructure changes to go through, and time that our on-call engineers had been spending responding to false alarms. That’s roughly 600 hours a year returned to productive work, from a fix that took longer to diagnose than deploy.</p><p>Safe defaults in Kubernetes are designed for small, simple workloads. But as you scale, they can slowly become bottlenecks. If you’re running workloads with large persistent volumes, it’s worth checking whether recursive permission changes like this are silently eating your restart time. Audit your <code>securityContext</code> settings, especially <code>fsGroup</code> and <code>fsGroupChangePolicy</code>. <code>OnRootMismatch</code> has been available since v1.20.</p><p>Not every fix is heroic or complex, and it’s usually worth asking “why does the system behave this way?”</p><p>If debugging infrastructure problems at scale sounds interesting, <a href="https://cloudflare.com/careers"><u>we’re hiring</u></a>. Come join us on the <a href="https://community.cloudflare.com/"><u>Cloudflare Community</u></a> or our <a href="https://discord.cloudflare.com/"><u>Discord</u></a> to talk shop.</p> ]]></content:encoded>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[Terraform]]></category>
            <category><![CDATA[Platform Engineering]]></category>
            <category><![CDATA[Infrastructure]]></category>
            <category><![CDATA[SRE]]></category>
            <guid isPermaLink="false">6bSk27AUeu3Ja7pTySyy0t</guid>
            <dc:creator>Braxton Schafer</dc:creator>
        </item>
        <item>
            <title><![CDATA[Leveraging Kubernetes virtual machines at Cloudflare with KubeVirt]]></title>
            <link>https://blog.cloudflare.com/leveraging-kubernetes-virtual-machines-with-kubevirt/</link>
            <pubDate>Tue, 08 Oct 2024 13:00:00 GMT</pubDate>
            <description><![CDATA[ The Kubernetes team runs several multi-tenant clusters across Cloudflare’s core data centers. When multi-tenant cluster isolation is too limiting for an application, we use KubeVirt. KubeVirt is a cloud-native solution that enables our developers to run virtual machines alongside containers. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Cloudflare runs several <a href="https://kubernetes.io/docs/concepts/security/multi-tenancy/"><u>multi-tenant</u></a> <a href="https://kubernetes.io/"><u>Kubernetes</u></a> clusters across our core data centers. These general-purpose clusters run on bare metal and power our <a href="https://www.cloudflare.com/learning/network-layer/what-is-the-control-plane/"><u>control plane</u></a>, analytics, and various engineering tools such as build infrastructure and continuous integration.</p><p>Kubernetes is a container orchestration platform. It enables software engineers to deploy containerized applications to a cluster of machines. This enables teams to build highly-available software on a scalable and resilient platform.</p><p>In this blog post we discuss our Kubernetes architecture, why we needed virtualization, and how we’re using it today.</p>
    <div>
      <h2>Multi-tenant clusters</h2>
      <a href="#multi-tenant-clusters">
        
      </a>
    </div>
    <p><a href="https://www.cloudflare.com/learning/cloud/what-is-multitenancy/"><u>Multi-tenancy</u></a> is a concept where one system can share its resources among a wide range of customers. This model allows us to build and manage a small number of general purpose Kubernetes clusters for our internal application teams. Keeping the number of clusters small reduces our operational toil. This model shrinks costs and increases computational efficiency by sharing hardware. Multi-tenancy also allows us to scale more efficiently. Scaling is done at either a cluster or application level. Cluster operators scale the platform by adding more hardware. Teams scale their applications by updating their Kubernetes manifests. They can scale <a href="https://en.wikipedia.org/wiki/Scalability#Vertical_or_scale_up"><u>vertically</u></a> by increasing their <a href="https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"><u>resource</u></a> requests or <a href="https://en.wikipedia.org/wiki/Scalability#Horizontal_or_scale_out"><u>horizontally</u></a> by increasing the number of replicas.</p><p>All of our Kubernetes clusters are multi-tenant with various components enabled for a secure and resilient platform.</p><p><a href="https://kubernetes.io/docs/concepts/workloads/pods/"><u>Pods</u></a> are secured using the latest standards recommended by the Kubernetes project. We use <a href="https://kubernetes.io/docs/concepts/security/pod-security-admission/"><u>Pod Security Admission</u></a> (PSA) and <a href="https://kubernetes.io/docs/concepts/security/pod-security-standards/"><u>Pod Security Standards</u></a> to ensure all workloads are following best practices. By default, all namespaces use the most <a href="https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted"><u>restrictive</u></a> profile, and only a few Kubernetes control plane namespaces are granted <a href="https://kubernetes.io/docs/concepts/security/pod-security-standards/#privileged"><u>privileged</u></a> access. For additional policies not covered by PSA, we built custom <a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook"><u>Validating Webhooks</u></a> on top of the <a href="https://github.com/kubernetes-sigs/controller-runtime/tree/main/pkg/webhook/admission"><u>controller-runtime</u></a> framework. PSA and our custom policies ensure clusters are secure and workloads are isolated.</p>
    <div>
      <h2>Our need for virtualization</h2>
      <a href="#our-need-for-virtualization">
        
      </a>
    </div>
    <p>A select number of teams needed tight integration with the Linux kernel. Examples include Docker daemons for build infrastructure and the ability to simulate servers running the software and configuration of our <a href="https://www.cloudflare.com/network/"><u>global network</u></a>. With our pod security requirements, these workloads are not permitted to interface with the host kernel at a deep level (e.g. no <a href="https://en.wikipedia.org/wiki/Iptables"><u>iptables</u></a> or <a href="https://en.wikipedia.org/wiki/Sysctl"><u>sysctls</u></a>). Doing so may disrupt other tenants sharing the node and open additional <a href="https://www.cloudflare.com/learning/security/glossary/attack-vector/"><u>attack vectors</u></a> if an application was compromised. A virtualization platform would enable these workloads to interact with their own kernel within a secured Kubernetes cluster.</p><p>We considered various different virtualization solutions. Running a separate virtualization platform outside of Kubernetes would have worked, but would not tightly integrate containerized workloads with virtual machines. It would also be an additional operational burden on our team, as backups, alerting, and fleet management would have to exist for both our Kubernetes and virtual machine clusters.</p><p>We then looked for solutions that run virtual machines within Kubernetes. Teams could already manually deploy <a href="https://www.qemu.org/"><u>QEMU</u></a> pods, but this was not an elegant solution. We needed a better way. There were several other options, but <a href="https://kubevirt.io/"><u>KubeVirt</u></a> was the tool that met the majority of our requirements. Other solutions required a privileged container to run a virtual machine, but KubeVirt did not – this was a crucial requirement in our goal of creating a more secure multi-tenant cluster. KubeVirt also uses a feature of the Kubernetes API called <a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/"><u>Custom Resource Definitions</u></a> (CRDs), which extends the Kubernetes API with new objects, increasing the flexibility of Kubernetes beyond its built-in types. For KubeVirt, this includes objects such as VirtualMachine and VirtualMachineInstanceReplicaSet. We felt the use of CRDs would allow KubeVirt to grow as more features were added.</p>
    <div>
      <h2>What is KubeVirt?</h2>
      <a href="#what-is-kubevirt">
        
      </a>
    </div>
    <p>KubeVirt is a virtualization platform that enables users to run virtual machines within Kubernetes. With KubeVirt, <a href="https://www.cloudflare.com/learning/cloud/what-is-a-virtual-machine/"><u>virtual machines</u></a> run alongside containerized workloads on the same platform. Kubernetes primitives such as <a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/"><u>network policies</u></a>, <a href="https://kubernetes.io/docs/concepts/configuration/configmap/"><u>configmaps</u></a>, and <a href="https://kubernetes.io/docs/concepts/services-networking/service/"><u>services</u></a> all integrate with virtual machines. KubeVirt scales with our needs and is successfully running hundreds of virtual machines across several clusters. We frequently <a href="https://blog.cloudflare.com/automatic-remediation-of-kubernetes-nodes"><u>remediate Kubernetes nodes</u></a>, so virtual machines and pods are always exercising their startup/shutdown processes.</p>
    <div>
      <h2>How Cloudflare uses KubeVirt</h2>
      <a href="#how-cloudflare-uses-kubevirt">
        
      </a>
    </div>
    <p>There are a number of internal projects leveraging virtual machines at Cloudflare. We’ll touch on a few of our more popular use cases:</p><ol><li><p>Kubernetes scalability testing</p></li><li><p>Development environments</p></li><li><p>Kernel and iPXE testing</p></li><li><p>Build pipelines</p></li></ol>
    <div>
      <h3>
Kubernetes scalability testing</h3>
      <a href="#kubernetes-scalability-testing">
        
      </a>
    </div>
    
    <div>
      <h4>Setup process</h4>
      <a href="#setup-process">
        
      </a>
    </div>
    <p>Our staging clusters are much smaller than our largest production clusters. They also run on bare metal and mirror the configuration we have for each production cluster. This is extremely useful when rolling out new software, operating systems, or kernel changes; however, they miss bugs that only surface at scale. We use KubeVirt to bridge this gap and virtualize Kubernetes clusters with hundreds of nodes and thousands of pods.</p><p>The setup process for virtualized clusters differs from our bare metal provisioning steps. For bare metal, we use <a href="https://saltproject.io/"><u>Salt</u></a> to provision clusters from start to finish. For our virtualized clusters we use <a href="https://www.ansible.com/"><u>Ansible</u></a> and <a href="https://kubernetes.io/docs/reference/setup-tools/kubeadm/"><u>kubeadm</u></a>.  Our bare metal staging clusters are responsible for testing and validating our Salt configuration. The virtualized clusters give us a vanilla Kubernetes environment without any Cloudflare customizations. Having a stock environment in addition to our Salt environment helps us isolate bugs down to a Kubernetes change, a kernel change, or a Cloudflare-specific configuration change.</p><p>Our virtualized clusters consist of a KubeVirt <a href="https://kubevirt.io/api-reference/v1.2.2/definitions.html#_v1_virtualmachine"><u>VirtualMachine</u></a> object per node. We create three control-plane nodes and any number of worker nodes. Each virtual machine starts out as a vanilla Debian generic <a href="https://cdimage.debian.org/images/cloud/"><u>cloud image</u></a>. Using KubeVirt’s <a href="https://kubevirt.io/user-guide/user_workloads/startup_scripts/#cloud-init"><u>cloud-init support</u></a>, the virtual machine downloads an internal <a href="https://www.ansible.com/"><u>Ansible</u></a> <a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_intro.html"><u>playbook</u></a> which installs a recent kernel, <a href="https://cri-o.io/"><u>cri-o</u></a> (the container runtime we use), and <a href="https://kubernetes.io/docs/reference/setup-tools/kubeadm/"><u>kubeadm</u></a>.</p>
            <pre><code>- name: Add the Kubernetes gpg key
  apt_key:
    url: https://pkgs.k8s.io/core:/stable:/{{ kube_version }}/deb/Release.key
    keyring: /etc/apt/keyrings/kubernetes-apt-keyring.gpg
    state: present

- name: Add the Kubernetes repository
  shell: echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/{{ kube_version }}/deb/ /" | tee /etc/apt/sources.list.d/kubernetes.list

- name: Add the CRI-O gpg key
  apt_key:
    url: https://pkgs.k8s.io/addons:/cri-o:/{{ crio_version }}/deb/Release.key
    keyring: /etc/apt/keyrings/cri-o-apt-keyring.gpg
    state: present

- name: Add the CRI-O repository
  shell: echo "deb [signed-by=/etc/apt/keyrings/cri-o-apt-keyring.gpg] https://pkgs.k8s.io/addons:/cri-o:/{{ crio_version }}/deb/ /" | tee /etc/apt/sources.list.d/cri-o.list

- name: Install CRI-O and Kubernetes packages
  apt:
    name:
      - cri-o
      - kubelet
      - kubeadm
      - kubectl
    update_cache: yes
    state: present

- name: Enable and start CRI-O service
  service:
    state: started
    enabled: yes
    name: crio.service</code></pre>
            <p><sup><i>Ansible playbook steps to download and install Kubernetes tooling</i></sup></p><p>Once each node has completed its individual playbook, we can <a href="http://ools/kubeadm/kubeadm-init/"><u>initialize</u></a> and <a href="https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-join/"><u>join</u></a> nodes to the cluster using another playbook that runs kubeadm. From there the cluster can be accessed by logging into a control plane node using kubectl.</p>
    <div>
      <h4>Simulating at scale</h4>
      <a href="#simulating-at-scale">
        
      </a>
    </div>
    <p>When losing 10s or 100s of nodes at once, Kubernetes needs to act quickly to minimize downtime. The sooner it recognizes node failure, the faster it can reroute traffic to healthy pods.</p><p>Using Kubernetes in KubeVirt we are able to simulate a large cluster undergoing a network cut and observe how Kubernetes reacts. The KubeVirt Kubernetes cluster allows us to rapidly iterate on configuration changes and code patches.</p><p>The following Ansible playbook task simulates a network segmentation failure where only the control-plane nodes remain online.</p>
            <pre><code>- name: Disable network interfaces on all workers
  command: ifconfig enp1s0 down
  async: 5
  poll: 0
  ignore_errors: yes
  when: inventory_hostname in groups['kube-node']</code></pre>
            <p><sup><i>An Ansible role which disables the network on all worker nodes simultaneously.</i></sup></p><p>This framework allows us to exercise the code in <a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/"><u>controller-manager</u></a>, Kubernetes’s daemon that reconciles the fundamental state of the system (Nodes, Pods, etc). Our simulation platform helped us drastically shorten full traffic recovery time when a large number of Kubernetes nodes <a href="https://blog.cloudflare.com/major-data-center-power-failure-again-cloudflare-code-orange-tested"><u>become unreachable</u></a>. We upstreamed our <a href="https://github.com/kubernetes/kubernetes/pull/114296"><u>changes</u></a> to Kubernetes and more controller-manager speed improvements are coming soon.</p>
    <div>
      <h3>Development environments</h3>
      <a href="#development-environments">
        
      </a>
    </div>
    <p>Compiling code on your laptop can be slow. Perhaps you’re working on a patch for a large open-source project (e.g. <a href="https://v8.dev/"><u>V8</u></a> or <a href="https://clickhouse.com/"><u>Clickhouse</u></a>) or need more bandwidth to upload and download containers. With KubeVirt, we enable our developers to rapidly iterate on software development and testing on <a href="https://blog.cloudflare.com/cloudflare-gen-12-server-bigger-better-cooler-in-a-2u1n-form-factor"><u>powerful server hardware</u></a>. KubeVirt <a href="https://kubevirt.io/user-guide/storage/disks_and_volumes/#persistentvolumeclaim"><u>integrates</u></a> with Kubernetes <a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/"><u>Persistent Volumes</u></a>, which enables teams to persist their development environment across restarts.</p><p>There are a number of teams at Cloudflare using KubeVirt for a variety of development and testing environments. Most notably is a project called Edge Test Fleet, which emulates a physical server and all the software that runs Cloudflare’s <a href="https://www.cloudflare.com/network/"><u>global network</u></a>. Teams can test their code and configuration changes against the entire software stack without reserving dedicated hardware. Cloudflare uses <a href="https://blog.cloudflare.com/tag/salt/"><u>Salt</u></a> to provision systems. It can be difficult to iterate and test Salt changes without a complete virtual environment. Edge Test Fleet makes iterating on Salt easier, ensuring states compile and render the right output. With Edge Test Fleet, new developers can better understand how Cloudflare’s global network works without touching staging or production.</p><p>Additionally, one Cloudflare team developed a framework that allows users to build and test changes to <a href="https://blog.cloudflare.com/log-analytics-using-clickhouse"><u>Clickhouse</u></a> using a <a href="https://code.visualstudio.com/"><u>VSCode</u></a> environment. This framework is generally applicable to all teams requiring a development environment. Once a template environment is provisioned, <a href="https://kubernetes.io/docs/concepts/storage/volume-pvc-datasource/"><u>CSI Volume Cloning</u></a> can duplicate a golden volume, separating persistent environments for each developer.</p>
            <pre><code>apiVersion: v1
kind: PersistentVolumeClaim
name: devspace-jcichra-rootfs
namespace: dev-clickhouse-vms
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: rook-ceph-nvme
  dataSource:
    kind: PersistentVolumeClaim
    name: dev-rootfs
  resources:
    requests:
      storage: 500Gi</code></pre>
            <p><sup><i>A PersistentVolumeClaim that clones data from another volume using CSI Volume Cloning</i></sup></p>
    <div>
      <h3>Kernel and iPXE testing</h3>
      <a href="#kernel-and-ipxe-testing">
        
      </a>
    </div>
    <p>Unlike <a href="https://en.wikipedia.org/wiki/User_space_and_kernel_space"><u>user space</u></a> software development, when a kernel crashes, the entire system crashes. The <a href="https://blog.cloudflare.com/tag/kernel"><u>kernel</u></a> team uses KubeVirt for development. KubeVirt gives all kernel engineers, regardless of laptop OS or architecture, the same x86 environment and <a href="https://en.wikipedia.org/wiki/Hypervisor"><u>hypervisor</u></a>. Virtual machines on server hardware can be scaled up to more cores and memory than on laptops. The Cloudflare kernel team has also found low-level issues which only surface in environments with many CPUs.</p><p>To make testing fast and easy, the kernel team serves <a href="https://ipxe.org/"><u>iPXE</u></a> images via an <a href="https://nginx.org/"><u>nginx</u></a> Pod and Service adjacent to the virtual machine. A recent kernel and Debian image are copied to the nginx pod via kubectl cp. The iPXE file can then be referenced in the KubeVirt virtual machine definition via the DNS name for the Kubernetes Service.</p>
            <pre><code>interfaces:
  name: default
  masquerade: {}
  model: e1000e
  ports:
    - port: 22
      dhcpOptions:
        bootFileName: http://httpboot.u-$K8S_USER.svc.cluster.local/boot.ipxe</code></pre>
            <p>When the virtual machine boots, it will get an IP address on the default interface behind <a href="https://en.wikipedia.org/wiki/Network_address_translation"><u>NAT</u></a> due to our <a href="https://kubevirt.io/user-guide/network/interfaces_and_networks/#masquerade"><u>masquerade</u></a> setting. Then it will download boot.ipxe, which describes what additional files should be downloaded to start the system. In this case, the kernel (<code>vmlinuz-amd64</code>), Debian (<code>baseimg-amd64.img</code>) and additional kernel modules (<code>modules-amd64.img</code>) are downloaded.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/74Pndk3FS6TVPACarSKD4N/fc7c3add6bae3c2c8b5e086ef9061872/image2.png" />
          </figure><p><sup><i>UEFI iPXE boot connecting and downloading files from nginx pod in user’s namespace</i></sup></p><p>Once the system is booted, a developer can log in to the system for testing:</p>
            <pre><code>linux login: root
Password: 
Linux linux 6.6.35-cloudflare-2024.6.7 #1 SMP PREEMPT_DYNAMIC Mon Sep 27 00:00:00 UTC 2010 x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
root@linux:~# </code></pre>
            <p>Custom kernels can be copied to the nginx pod via <code>kubectl cp</code>. Restarting the virtual machine will load that new kernel for testing. When a kernel panic occurs, the virtual machine can quickly be restarted with <code>virtctl restart linux</code> and it will go through the iPXE boot process again.</p>
    <div>
      <h3>Build pipelines</h3>
      <a href="#build-pipelines">
        
      </a>
    </div>
    <p>Cloudflare leverages KubeVirt to build a majority of software at Cloudflare. Virtual machines give build system users full control over their pipeline. For example, Debian packages can easily be installed and separate container daemons (such as <a href="https://www.docker.com/"><u>Docker</u></a>) can run all within a Kubernetes namespace using the <code>restricted</code> Pod Security Standard. KubeVirt’s <a href="https://kubevirt.io/user-guide/user_workloads/replicaset/"><u>VirtualMachineReplicaSet</u></a> concept allows us to quickly scale up and down the number of build agents to match demand. We can roll out different sets of virtual machines with varying sizes, kernels, and operating systems.</p><p>To scale efficiently, we leverage <a href="https://kubevirt.io/user-guide/storage/disks_and_volumes/#containerdisk"><u>container disks</u></a> to store our agent virtual machine images. Container disks allow us to store the virtual machine image (for example, a <a href="https://en.wikipedia.org/wiki/Qcow"><u>qcow</u></a> image) in our container registry. This strategy works well when the state in virtual machines is ephemeral. <a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-a-liveness-command"><u>Liveness probes</u></a> detect unhealthy or broken agents, shutting down the virtual machine and replacing them with a fresh instance. Other automation limits virtual machine uptime, capping it to 3–4 hours to keep build agents fresh.</p>
    <div>
      <h2>Next steps</h2>
      <a href="#next-steps">
        
      </a>
    </div>
    <p>We’re excited to expand our use of KubeVirt and unlock new capabilities for our internal users. KubeVirt’s Linux ARM64 support will allow us to build ARM64 packages in-cluster and simulate ARM64 systems.</p><p>Projects like <a href="https://kubevirt.io/user-guide/operations/containerized_data_importer/"><u>KubeVirt CDI</u></a> (Containerized Data Importer) will streamline our user’s virtual machine experience. Instead of users manually building container disks, we can provide a catalog of virtual machine images. It also allows us to copy virtual machine disks between namespaces.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>KubeVirt has proven to be a great tool for virtualization in our Kubernetes-first environment. We’ve unlocked the ability to support more workloads with our multi-tenant model. The KubeVirt platform allows us to offer a single compute platform supporting containers and virtual machines. Managing it has been simple, and upgrades have been straightforward and non-disruptive. We’re exploring additional features KubeVirt offers to improve the experience for our users.</p><p>Finally, our team is expanding! We’re looking for more people passionate about Kubernetes to <a href="https://boards.greenhouse.io/cloudflare/jobs/5579824"><u>join our team</u></a> and help us push Kubernetes to the next level.</p> ]]></content:encoded>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[Infrastructure]]></category>
            <guid isPermaLink="false">1149BgOuHn2l6ubvzlzHar</guid>
            <dc:creator>Justin Cichra</dc:creator>
        </item>
        <item>
            <title><![CDATA[Intelligent, automatic restarts for unhealthy Kafka consumers]]></title>
            <link>https://blog.cloudflare.com/intelligent-automatic-restarts-for-unhealthy-kafka-consumers/</link>
            <pubDate>Tue, 24 Jan 2023 14:00:00 GMT</pubDate>
            <description><![CDATA[ At Cloudflare, we take steps to ensure we are resilient against failure at all levels of our infrastructure. This includes Kafka, which we use for critical workflows such as sending time-sensitive emails and alerts. ]]></description>
            <content:encoded><![CDATA[ <p></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7eWbGD5pEX9bKf2p58iqOw/b55ba4bfd305da7ed38cf66fe770585c/image3-8-2.png" />
            
            </figure><p>At Cloudflare, we take steps to ensure we are resilient against failure at all levels of our infrastructure. This includes Kafka, which we use for critical workflows such as sending time-sensitive emails and alerts.</p><p>We learned a lot about keeping our applications that leverage Kafka healthy, so they can always be operational. Application health checks are notoriously hard to implement: What determines an application as healthy? How can we keep services operational at all times?</p><p>These can be implemented in many ways. We’ll talk about an approach that allows us to considerably reduce incidents with unhealthy applications while requiring less manual intervention.</p>
    <div>
      <h3>Kafka at Cloudflare</h3>
      <a href="#kafka-at-cloudflare">
        
      </a>
    </div>
    <p><a href="/using-apache-kafka-to-process-1-trillion-messages/">Cloudflare is a big adopter of Kafka</a>. We use Kafka as a way to decouple services due to its asynchronous nature and reliability. It allows different teams to work effectively without creating dependencies on one another. You can also read more about how other teams at Cloudflare use Kafka in <a href="/http-analytics-for-6m-requests-per-second-using-clickhouse/">this</a> post.</p><p>Kafka is used to send and receive messages. Messages represent some kind of event like a credit card payment or details of a new user created in your platform. These messages can be represented in multiple ways: JSON, Protobuf, Avro and so on.</p><p>Kafka organises messages in topics. A topic is an ordered log of events in which each message is marked with a progressive offset. When an event is written by an external system, that is appended to the end of that topic. These events are not deleted from the topic by default (retention can be applied).</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2KUYbqCCL74YZVU8NXOThl/4ec5024168993a2300add7221016af0d/1-4.png" />
            
            </figure><p>Topics are stored as log files on disk, which are finite in size. Partitions are a systematic way of breaking the one topic log file into many logs, each of which can be hosted on separate servers–enabling to scale topics.</p><p>Topics are managed by brokers–nodes in a Kafka cluster. These are responsible for writing new events to partitions, serving reads and replicating partitions among themselves.</p><p>Messages can be consumed by individual consumers or co-ordinated groups of consumers, known as consumer groups.</p><p>Consumers use a unique id (consumer id) that allows them to be identified by the broker as an application which is consuming from a specific topic.</p><p>Each topic can be read by an infinite number of different consumers, as long as they use a different id. Each consumer can replay the same messages as many times as they want.</p><p>When a consumer starts consuming from a topic, it will process all messages, starting from a selected offset, from each partition. With a consumer group, the partitions are divided amongst each consumer in the group. This division is determined by the consumer group leader. This leader will receive information about the other consumers in the group and will decide which consumers will receive messages from which partitions (partition strategy).</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6Qe2Qe5nQ5gcHyhV0zpTWw/5182eea9de66164a36a28e92270fdb3f/2-3.png" />
            
            </figure><p>The offset of a consumer’s commit can demonstrate whether the consumer is working as expected. Committing a processed offset is the way a consumer and its consumer group report to the broker that they have processed a particular message.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/29Y9mQiHkvGKUzc3RGF1sk/09d2987f53eef026c164e6c49cacc95c/unnamed-6.png" />
            
            </figure><p>A standard measurement of whether a consumer is processing fast enough is lag. We use this to measure how far behind the newest message we are. This tracks time elapsed between messages being written to and read from a topic. When a service is lagging behind, it means that the consumption is at a slower rate than new messages being produced.</p><p>Due to Cloudflare’s scale, message rates typically end up being very large and a lot of requests are time-sensitive so monitoring this is vital.</p><p>At Cloudflare, our applications using Kafka are deployed as microservices on Kubernetes.</p>
    <div>
      <h3>Health checks for Kubernetes apps</h3>
      <a href="#health-checks-for-kubernetes-apps">
        
      </a>
    </div>
    <p>Kubernetes uses <a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/">probes</a> to understand if a service is healthy and is ready to receive traffic or to run. When a liveness probe fails and the bounds for retrying are exceeded, Kubernetes restarts the services.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4FagbTygES9L7dmEQ6ratD/0a6f0d4c5ac117b723ad726a12d3936a/4-3.png" />
            
            </figure><p>When a readiness probe fails and the bounds for retrying are exceeded, it stops sending HTTP traffic to the targeted pods. In the case of Kafka applications this is not relevant as they don’t run an http server. For this reason, we’ll cover only liveness checks.</p><p>A classic Kafka liveness check done on a consumer checks the status of the connection with the broker. It’s often best practice to keep these checks simple and perform some basic operations - in this case, something like listing topics. If, for any reason, this check fails consistently, for instance the broker returns a TLS error, Kubernetes terminates the service and starts a new pod of the same service, therefore forcing a new connection. Simple Kafka liveness checks do a good job of understanding when the connection with the broker is unhealthy.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6gNWb3Rit0MmTutsurm7sf/70355c422fab7ebce7d59d8c2c682d6d/5-2.png" />
            
            </figure>
    <div>
      <h3>Problems with Kafka health checks</h3>
      <a href="#problems-with-kafka-health-checks">
        
      </a>
    </div>
    <p>Due to Cloudflare’s scale, a lot of our Kafka topics are divided into multiple partitions (in some cases this can be hundreds!) and in many cases the replica count of our consuming service doesn’t necessarily match the number of partitions on the Kafka topic. This can mean that in a lot of scenarios this simple approach to health checking is not quite enough!</p><p>Microservices that consume from Kafka topics are healthy if they are consuming and committing offsets at regular intervals when messages are being published to a topic. When such services are not committing offsets as expected, it means that the consumer is in a bad state, and it will start accumulating lag. An approach we often take is to manually terminate and restart the service in Kubernetes, this will cause a reconnection and rebalance.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/N4YalYdgNRxYJK7PVAlzY/26b55fc38c53855a6c28c71b25cdac02/lag.png" />
            
            </figure><p>When a consumer joins or leaves a consumer group, a rebalance is triggered and the consumer group leader must re-assign which consumers will read from which partitions.</p><p>When a rebalance happens, each consumer is notified to stop consuming. Some consumers might get their assigned partitions taken away and re-assigned to another consumer. We noticed when this happened within our library implementation; if the consumer doesn’t acknowledge this command, it will wait indefinitely for new messages to be consumed from a partition that it’s no longer assigned to, ultimately leading to a deadlock. Usually a manual restart of the faulty client-side app is needed to resume processing.</p>
    <div>
      <h3>Intelligent health checks</h3>
      <a href="#intelligent-health-checks">
        
      </a>
    </div>
    <p>As we were seeing consumers reporting as “healthy” but sitting idle, it occurred to us that maybe we were focusing on the wrong thing in our health checks. Just because the service is connected to the Kafka broker and can read from the topic, it does not mean the consumer is actively processing messages.</p><p>Therefore, we realised we should be focused on message ingestion, using the offset values to ensure that forward progress was being made.</p>
    <div>
      <h4>The PagerDuty approach</h4>
      <a href="#the-pagerduty-approach">
        
      </a>
    </div>
    <p>PagerDuty wrote an excellent <a href="https://www.pagerduty.com/eng/kafka-health-checks/">blog</a> on this topic which we used as inspiration when coming up with our approach.</p><p>Their approach used the current (latest) offset and the committed offset values. The current offset signifies the last message that was sent to the topic, while the committed offset is the last message that was processed by the consumer.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2fwem7NtBnO6M1RMhrezr8/af4cbbd7a63d3145f5c7fe9f405bd04d/pasted-image-0-4.png" />
            
            </figure><p>Checking the consumer is moving forwards, by ensuring that the latest offset was changing (receiving new messages) and the committed offsets were changing as well (processing the new messages).</p><p>Therefore, the solution we came up with:</p><ul><li><p>If we cannot read the current offset, fail liveness probe.</p></li><li><p>If we cannot read the committed offset, fail liveness probe.</p></li><li><p>If the committed offset == the current offset, pass liveness probe.</p></li><li><p>If the value for the committed offset has not changed since the last run of the health check, fail liveness probe.</p></li></ul>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5r76n2Iew7pSqA8vYNZzIy/c9e0f6a113a34d0c36a216c054e4d840/pasted-image-0--1--3.png" />
            
            </figure><p>To measure if the committed offset is changing, we need to store the value of the previous run, we do this using an in-memory map where partition number is the key. This means each instance of our service only has a view of the partitions it is currently consuming from and will run the health check for each.</p>
    <div>
      <h4>Problems</h4>
      <a href="#problems">
        
      </a>
    </div>
    <p>When we first rolled out our smart health checks we started to notice cascading failures some time after release. After initial investigations we realised this was happening when a rebalance happens. It would initially affect one replica then quickly result in the others reporting as unhealthy.</p><p>What we observed was due to us storing the previous value of the committed offset in-memory, when a rebalance happens the service may get re-assigned a different partition. When this happened it meant our service was incorrectly assuming that the committed offset for that partition had not changed (as this specific replica was no longer updating the latest value), therefore it would start to report the service as unhealthy. The failing liveness probe would then cause it to restart which would in-turn trigger another rebalancing in Kafka causing other replicas to face the same issue.</p>
    <div>
      <h4>Solution</h4>
      <a href="#solution">
        
      </a>
    </div>
    <p>To fix this issue we needed to ensure that each replica only kept track of the offsets for the partitions it was consuming from at that moment. Luckily, the Shopify Sarama library, which we use internally, has functionality to observe when a rebalancing happens. This meant we could use it to rebuild the in-memory map of offsets so that it would only include the relevant partition values.</p><p>This is handled by receiving the signal from the session context channel:</p>
            <pre><code>for {
  select {
  case message, ok := &lt;-claim.Messages(): // &lt;-- Message received

     // Store latest received offset in-memory
     offsetMap[message.Partition] = message.Offset


     // Handle message
     handleMessage(ctx, message)


     // Commit message offset
     session.MarkMessage(message, "")


  case &lt;-session.Context().Done(): // &lt;-- Rebalance happened

     // Remove rebalanced partition from in-memory map
     delete(offsetMap, claim.Partition())
  }
}</code></pre>
            <p>Verifying this solution was straightforward, we just needed to trigger a rebalance. To test this worked in all possible scenarios we spun up a single replica of a service consuming from multiple partitions, then proceeded to scale up the number of replicas until it matched the partition count, then scaled back down to a single replica. By doing this we verified that the health checks could safely handle new partitions being assigned as well as partitions being taken away.</p>
    <div>
      <h3>Takeaways</h3>
      <a href="#takeaways">
        
      </a>
    </div>
    <p>Probes in Kubernetes are very easy to set up and can be a powerful tool to ensure your application is running as expected. Well implemented probes can often be the difference between engineers being called out to fix trivial issues (sometimes outside of working hours) and a service which is self-healing.</p><p>However, without proper thought, “dumb” health checks can also lead to a false sense of security that a service is running as expected even when it’s not. One thing we have learnt from this was to think more about the specific behaviour of the service and decide what being unhealthy means in each instance, instead of just ensuring that dependent services are connected.</p> ]]></content:encoded>
            <category><![CDATA[Kafka]]></category>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Go]]></category>
            <category><![CDATA[Kubernetes]]></category>
            <guid isPermaLink="false">7s1ijlG7zMlxJPI6Hcs3zl</guid>
            <dc:creator>Chris Shepherd</dc:creator>
            <dc:creator>Andrea Medda</dc:creator>
        </item>
        <item>
            <title><![CDATA[Kubectl with Cloudflare Zero Trust]]></title>
            <link>https://blog.cloudflare.com/kubectl-with-zero-trust/</link>
            <pubDate>Fri, 24 Jun 2022 14:08:51 GMT</pubDate>
            <description><![CDATA[ Using Cloudflare Zero Trust with Kubernetes to enable kubectl without SOCKS proxies ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6qFUylvIuAuqfStdkrEDvb/c289f67a78b01fc25c7ec2d910aa2f02/Proxyless-KubeCTL-Support.png" />
            
            </figure><p>Cloudflare is a heavy user of Kubernetes for engineering workloads: it's used to power the backend of our APIs, to handle batch-processing such as analytics aggregation and bot detection, and engineering tools such as our <a href="https://www.cloudflare.com/learning/serverless/glossary/what-is-ci-cd/">CI/CD pipelines</a>. But between load balancers, API servers, etcd, ingresses, and pods, the surface area exposed by Kubernetes can be rather large.</p><p>In this post, we share a little bit about how our engineering team dogfoods Cloudflare Zero Trust to secure Kubernetes — and enables kubectl without proxies.</p>
    <div>
      <h2>Our General Approach to Kubernetes Security</h2>
      <a href="#our-general-approach-to-kubernetes-security">
        
      </a>
    </div>
    <p>As part of our security measures, we heavily limit what can access our clusters over the network. Where a network service is exposed, we add additional protections, such as requiring Cloudflare Access authentication or Mutual TLS (or both) to access ingress resources.</p><p>These network restrictions include access to the cluster's API server. Without access to this, engineers at Cloudflare would not be able to use tools like kubectl to introspect their team's resources. While we believe Continuous Deployments and GitOps are best practices, allowing developers to use the Kubernetes API aids in troubleshooting and increasing developer velocity. Not having access would have been a deal breaker.</p><p>To satisfy our security requirements, we're using Cloudflare Zero Trust, and we wanted to share how we're using it, and the process that brought us here.</p>
    <div>
      <h3>Before Zero Trust</h3>
      <a href="#before-zero-trust">
        
      </a>
    </div>
    <p>In the world before <a href="https://www.cloudflare.com/learning/security/glossary/what-is-zero-trust/">Zero Trust</a>, engineers could access the Kubernetes API by connecting to a VPN appliance. While this is common across the industry, and it does allow access to the API, it also dropped engineers as clients into the internal network: they had much more network access than necessary.</p><p>We weren't happy with this situation, but it was the status quo for several years. At the beginning of 2020, we retired our VPN and thus the Kubernetes team needed to find another solution.</p>
    <div>
      <h3>Kubernetes with Cloudflare Tunnels</h3>
      <a href="#kubernetes-with-cloudflare-tunnels">
        
      </a>
    </div>
    <p>At the time we worked closely with the team developing Cloudflare Tunnels to add support for handling <a href="/releasing-kubectl-support-in-access/">kubectl connections using Access</a> and cloudflared tunnels.</p><p>While this worked for our engineering users, it was a significant hurdle to on-boarding new employees. Each Kubernetes cluster required its own tunnel connection from the engineer's device, which made shuffling between clusters annoying. While kubectl supported connecting through SOCKS proxies, this support was not universal to all tools in the Kubernetes ecosystem.</p><p>We continued using this solution internally while we worked towards a better solution.</p>
    <div>
      <h2>Kubernetes with Zero Trust</h2>
      <a href="#kubernetes-with-zero-trust">
        
      </a>
    </div>
    <p>Since the launch of Cloudflare One, we've been dogfooding the Zero Trust agent in various configurations. At first we'd been using it to implement secure <a href="https://www.cloudflare.com/learning/dns/what-is-dns/">DNS</a> with 1.1.1.1. As time went on, we began to use it to dogfood additional Zero Trust features.</p><p>We're now leveraging the private network routing in Cloudflare Zero Trust to allow engineers to access the Kubernetes APIs without needing to setup cloudflared tunnels or configure kubectl and other Kubernetes ecosystem tools to use tunnels. This isn't something specific to Cloudflare, you can do this for your team today!</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3BYeJk98ZxUpcycaPOjfOi/be71df17cceed62dd8df98c6f39d5e9d/Kubectl-WIth-Warp-Diagram--2-.png" />
            
            </figure>
    <div>
      <h3>Configuring Zero Trust</h3>
      <a href="#configuring-zero-trust">
        
      </a>
    </div>
    <p>We use a <a href="https://github.com/cloudflare/terraform-provider-cloudflare">configuration management tool</a> for our Zero Trust configuration to enable infrastructure-as-code, which we've adapted below. However, the same configuration can be achieved using the Cloudflare Zero Trust dashboard.</p><p>The first thing we need to do is create a new tunnel. This tunnel will be used to connect the Cloudflare edge network to the Kubernetes API. We run the tunnel endpoints within Kubernetes, using configuration shown later in this post.</p>
            <pre><code>resource "cloudflare_argo_tunnel" "k8s_zero_trust_tunnel" {
  account_id = var.account_id
  name       = "k8s_zero_trust_tunnel"
  secret     = var.tunnel_secret
}</code></pre>
            <p>The "tunnel_secret" secret should be a 32-byte random number, which you should temporarily save as we'll reuse it later for the Kubernetes setup later.</p><p>After we've created the tunnel, we need to create the routes so the Cloudflare network knows what traffic to send through the tunnel.</p>
            <pre><code>resource "cloudflare_tunnel_route" "k8s_zero_trust_tunnel_ipv4" {
  account_id = var.account_id
  tunnel_id  = cloudflare_argo_tunnel.k8s_zero_trust_tunnel.id
  network    = "198.51.100.101/32"
  comment    = "Kubernetes API Server (IPv4)"
}
 
resource "cloudflare_tunnel_route" "k8s_zero_trust_tunnel_ipv6" {
  account_id = var.account_id
  tunnel_id  = cloudflare_argo_tunnel.k8s_zero_trust_tunnel.id
  network    = "2001:DB8::101/128"
  comment    = "Kubernetes API Server (IPv6)"
}</code></pre>
            <p>We support accessing the Kubernetes API via both IPv4 and IPv6 addresses, so we configure routes for both. If you're connecting to your API server via a hostname, these IP addresses should match what is returned via a DNS lookup.</p><p>Next we'll configure settings for Cloudflare Gateway so that it's compatible with the API servers and clients.</p>
            <pre><code>resource "cloudflare_teams_list" "k8s_apiserver_ips" {
  account_id = var.account_id
  name       = "Kubernetes API IPs"
  type       = "IP"
  items      = ["198.51.100.101/32", "2001:DB8::101/128"]
}
 
resource "cloudflare_teams_rule" "k8s_apiserver_zero_trust_http" {
  account_id  = var.account_id
  name        = "Don't inspect Kubernetes API"
  description = "Allow connections from kubectl to API"
  precedence  = 10000
  action      = "off"
  enabled     = true
  filters     = ["http"]
  traffic     = format("any(http.conn.dst_ip[*] in $%s)", replace(cloudflare_teams_list.k8s_apiserver_ips.id, "-", ""))
}</code></pre>
            <p>As we use mutual TLS between clients and the API server, and not all the traffic between kubectl and the API servers are HTTP, we've disabled HTTP inspection for these connections.</p><p>You can pair these rules with additional Zero Trust rules, such as <a href="https://developers.cloudflare.com/cloudflare-one/identity/devices/">device attestation</a>, <a href="https://developers.cloudflare.com/cloudflare-one/policies/filtering/enforce-sessions/">session lifetimes</a>, and <a href="https://developers.cloudflare.com/cloudflare-one/policies/filtering/identity-selectors/">user and group</a> access policies to further customize your security.</p>
    <div>
      <h3>Deploying Tunnels</h3>
      <a href="#deploying-tunnels">
        
      </a>
    </div>
    <p>Once you have your tunnels created and configured, you can deploy their endpoints into your network. We've chosen to deploy the tunnels as pods, as this allows us to use Kubernetes's deployment strategies for rolling out upgrades and handling node failures.</p>
            <pre><code>apiVersion: v1
kind: ConfigMap
metadata:
  name: tunnel-zt
  namespace: example
  labels:
    tunnel: api-zt
data:
  config.yaml: |
    tunnel: 8e343b13-a087-48ea-825f-9783931ff2a5
    credentials-file: /opt/zt/creds/creds.json
    metrics: 0.0.0.0:8081
    warp-routing:
        enabled: true</code></pre>
            <p>This creates a Kubernetes ConfigMap with a basic configuration that enables WARP routing for the tunnel ID specified. You can get this tunnel ID from your configuration management system, the Cloudflare Zero Trust dashboard, or by running the following command from another device logged into the same account.</p><p><code>cloudflared tunnel list</code></p><p>Next, we'll need to create a secret for our tunnel credentials. While you should use a secret management system, for simplicity we'll create one directly here.</p>
            <pre><code>jq -cn --arg accountTag $CF_ACCOUNT_TAG \
       --arg tunnelID $CF_TUNNEL_ID \
       --arg tunnelName $CF_TUNNEL_NAME \
       --arg tunnelSecret $CF_TUNNEL_SECRET \
   '{AccountTag: $accountTag, TunnelID: $tunnelID, TunnelName: $tunnelName, TunnelSecret: $tunnelSecret}' | \
kubectl create secret generic -n example tunnel-creds --from-file=creds.json=/dev/stdin</code></pre>
            <p>This creates a new secret "tunnel-creds" in the "example" namespace with the credentials file the tunnel expects.</p><p>Now we can deploy the tunnels themselves. We deploy multiple replicas to ensure some are always available, even while nodes are being drained.</p>
            <pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    tunnel: api-zt
  name: tunnel-api-zt
  namespace: example
spec:
  replicas: 3
  selector:
    matchLabels:
      tunnel: api-zt
  strategy:
    rollingUpdate:
      maxSurge: 0
      maxUnavailable: 1
  template:
    metadata:
      labels:
        tunnel: api-zt
    spec:
      containers:
        - args:
            - tunnel
            - --config
            - /opt/zt/config/config.yaml
            - run
          env:
            - name: GOMAXPROCS
              value: "2"
            - name: TZ
              value: UTC
          image: cloudflare/cloudflared:2022.5.3
          livenessProbe:
            failureThreshold: 1
            httpGet:
              path: /ready
              port: 8081
            initialDelaySeconds: 10
            periodSeconds: 10
          name: tunnel
          ports:
            - containerPort: 8081
              name: http-metrics
          resources:
            limits:
              cpu: "1"
              memory: 100Mi
          volumeMounts:
            - mountPath: /opt/zt/config
              name: config
              readOnly: true
            - mountPath: /opt/zt/creds
              name: creds
              readOnly: true
      volumes:
        - secret:
            name: tunnel-creds
          name: creds
        - configMap:
            name: tunnel-api-zt
          name: config</code></pre>
            
    <div>
      <h2>Using Kubectl with Cloudflare Zero Trust</h2>
      <a href="#using-kubectl-with-cloudflare-zero-trust">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2BGV6YJLH0Hnt1vh19F2lC/07c8806cd7c65674ddac42f8d2923b93/Screen-Shot-2022-06-23-at-3.43.37-PM.png" />
            
            </figure><p>After deploying the Cloudflare Zero Trust agent, members of your team can now access the Kubernetes API without needing to set up any special SOCKS tunnels!</p>
            <pre><code>kubectl version --short
Client Version: v1.24.1
Server Version: v1.24.1</code></pre>
            
    <div>
      <h2>What's next?</h2>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>If you try this out, send us your feedback! We're continuing to improve Zero Trust for non-HTTP workflows.</p> ]]></content:encoded>
            <category><![CDATA[Cloudflare One Week]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[Zero Trust]]></category>
            <category><![CDATA[Kubernetes]]></category>
            <guid isPermaLink="false">4VWDm5LM1jLa6cjopWrCRu</guid>
            <dc:creator>Terin Stock</dc:creator>
        </item>
        <item>
            <title><![CDATA[Automatic Remediation of Kubernetes Nodes]]></title>
            <link>https://blog.cloudflare.com/automatic-remediation-of-kubernetes-nodes/</link>
            <pubDate>Thu, 15 Jul 2021 12:59:42 GMT</pubDate>
            <description><![CDATA[ In Cloudflare’s core data centers, we are using Kubernetes to run many of the diverse services that help us control Cloudflare’s edge. We are automating some aspects of node remediation to keep the Kubernetes clusters healthy. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>We use <a href="https://kubernetes.io/">Kubernetes</a> to run many of the diverse services that help us control Cloudflare’s edge. We have five geographically diverse clusters, with hundreds of nodes in our largest cluster. These clusters are self-managed on bare-metal machines which gives us a good amount of power and flexibility in the software and integrations with Kubernetes. However, it also means we don’t have a cloud provider to rely on for virtualizing or managing the nodes. This distinction becomes even more prominent when considering all the different reasons that nodes degrade. With self-managed bare-metal machines, the list of reasons that cause a node to become unhealthy include:</p><ul><li><p>Hardware failures</p></li><li><p>Kernel-level software failures</p></li><li><p>Kubernetes cluster-level software failures</p></li><li><p>Degraded network communication</p></li><li><p>Software updates are required</p></li><li><p>Resource exhaustion<sup>1</sup></p></li></ul>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/59IDEspjCRavbrtH1Rtu4r/a5f057aa79b2aae10fc0073b8f3b5573/image2-5.png" />
            
            </figure>
    <div>
      <h2>Unhappy Nodes</h2>
      <a href="#unhappy-nodes">
        
      </a>
    </div>
    <p>We have plenty of examples of failures in the aforementioned categories, but one example has been particularly tedious to deal with. It starts with the following log line from the kernel:</p>
            <pre><code>unregister_netdevice: waiting for lo to become free. Usage count = 1</code></pre>
            <p>The issue is further observed with the number of network interfaces on the node owned by the Container Network Interface (CNI) plugin getting out of proportion with the number of running pods:</p>
            <pre><code>$ ip link | grep cali | wc -l
1088</code></pre>
            <p>This is unexpected as it shouldn't exceed the maximum number of pods allowed on a node (we use the default limit of 110). While this issue is interesting and perhaps worthy of a whole separate blog, the short of it is that the Linux network interfaces owned by the CNI are not getting cleaned up after a pod terminates.</p><p>Some history on this can be read in a <a href="https://github.com/moby/moby/issues/5618">Docker GitHub issue</a>. We found this seems to plague nodes with a longer uptime, and after rebooting the node it would be fine for about a month. However, with a significant number of nodes, this was happening multiple times per day. Each instance would need rebooting, which means going through our worker reboot procedure which looked like this:</p><ol><li><p>Cordon off the affected node to prevent new workloads from scheduling on it.</p></li><li><p>Collect any diagnostic information for later investigation.</p></li><li><p>Drain the node of current workloads.</p></li><li><p>Reboot and wait for the node to come back.</p></li><li><p>Verify the node is healthy.</p></li><li><p>Re-enable scheduling of new workloads to the node.</p></li></ol><p>While solving the underlying issue would be ideal, we needed a mitigation to avoid toil in the meantime — an automated node remediation process.</p>
    <div>
      <h2>Existing Detection and Remediation Solutions</h2>
      <a href="#existing-detection-and-remediation-solutions">
        
      </a>
    </div>
    <p>While not complicated, the manual remediation process outlined previously became tedious and distracting, as we had to reboot nodes multiple times a day. Some manual intervention is unavoidable, but for cases matching the following, we wanted automation:</p><ul><li><p>Generic worker nodes</p></li><li><p>Software issues confined to a given node</p></li><li><p>Already researched and diagnosed issues</p></li></ul><p>Limiting automatic remediation to generic worker nodes is important as there are other node types in our clusters where more care is required. For example, for control-plane nodes the process has to be augmented to check <a href="https://etcd.io/">etcd</a> cluster health and ensure proper redundancy for components servicing the Kubernetes API. We are also going to limit the problem space to known software issues confined to a node where we expect automatic remediation to be the right answer (as in our ballooning network interface problem). With that in mind, we took a look at existing solutions that we could use.</p>
    <div>
      <h3>Node Problem Detector</h3>
      <a href="#node-problem-detector">
        
      </a>
    </div>
    <p><a href="https://github.com/kubernetes/node-problem-detector">Node problem detector</a> is a daemon that runs on each node that detects problems and reports them to the Kubernetes API. It has a pluggable problem daemon system such that one can add their own logic for detecting issues with a node. Node problems are distinguished between temporary and permanent problems, with the latter being persisted as status conditions on the Kubernetes node resources.<sup>2</sup></p>
    <div>
      <h3>Draino and Cluster-Autoscaler</h3>
      <a href="#draino-and-cluster-autoscaler">
        
      </a>
    </div>
    <p><a href="https://github.com/planetlabs/draino">Draino</a> as its name implies, drains nodes but does so based on Kubernetes node conditions. It is meant to be used with <a href="https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler">cluster-autoscaler</a> which then can add or remove nodes via the cluster plugins to scale node groups.</p>
    <div>
      <h3>Kured</h3>
      <a href="#kured">
        
      </a>
    </div>
    <p><a href="https://github.com/weaveworks/kured">Kured</a> is a daemon that looks at the presence of a file on the node to initiate a drain, reboot and uncordon of the given node. It uses a locking mechanism via the Kubernetes API to ensure only a single node is acted upon at a time.</p>
    <div>
      <h3>Cluster-API</h3>
      <a href="#cluster-api">
        
      </a>
    </div>
    <p>The <a href="https://github.com/kubernetes/community/tree/master/sig-cluster-lifecycle">Kubernetes cluster-lifecycle SIG</a> has been working on the <a href="https://github.com/kubernetes-sigs/cluster-api">cluster-api</a> project to enable declaratively defining clusters to simplify provisioning, upgrading, and operating multiple Kubernetes clusters. It has a concept of machine resources which back Kubernetes node resources and furthermore has a concept of <a href="https://cluster-api.sigs.k8s.io/tasks/healthcheck.html">machine health checks</a>. Machine health checks use node conditions to determine unhealthy nodes and then the cluster-api provider is then delegated to replace that machine via create and delete operations.</p>
    <div>
      <h2>Proof of Concept</h2>
      <a href="#proof-of-concept">
        
      </a>
    </div>
    <p>Interestingly, with all the above except for Kured, there is a theme of pluggable components centered around Kubernetes node conditions. We wanted to see if we could build a proof of concept using the existing theme and solutions. For the existing solutions, draino with cluster-autoscaler didn’t make sense in a non-cloud environment like our bare-metal set up. The cluster-api health checks are interesting, however they require a more complete investment into the cluster-api project to really make sense. That left us with node-problem-detector and kured. Deploying node-problem-detector was simple, and we ended up testing a <a href="https://github.com/kubernetes/node-problem-detector/blob/master/docs/custom_plugin_monitor.md">custom-plugin-monitor</a> like the following:</p>
            <pre><code>apiVersion: v1
kind: ConfigMap
metadata:
  name: node-problem-detector-config
data:
  check_calico_interfaces.sh: |
    #!/bin/bash
    set -euo pipefail
    
    count=$(nsenter -n/proc/1/ns/net ip link | grep cali | wc -l)
    
    if (( $count &gt; 150 )); then
      echo "Too many calico interfaces ($count)"
      exit 1
    else
      exit 0
    fi
  cali-monitor.json: |
    {
      "plugin": "custom",
      "pluginConfig": {
        "invoke_interval": "30s",
        "timeout": "5s",
        "max_output_length": 80,
        "concurrency": 3,
        "enable_message_change_based_condition_update": false
      },
      "source": "calico-custom-plugin-monitor",
      "metricsReporting": false,
      "conditions": [
        {
          "type": "NPDCalicoUnhealthy",
          "reason": "CalicoInterfaceCountOkay",
          "message": "Normal amount of interfaces"
        }
      ],
      "rules": [
        {
          "type": "permanent",
          "condition": "NPDCalicoUnhealthy",
          "reason": "TooManyCalicoInterfaces",
          "path": "/bin/bash",
          "args": [
            "/config/check_calico_interfaces.sh"
          ],
          "timeout": "3s"
        }
      ]
    }</code></pre>
            <p>Testing showed that when the condition became true, a condition would be updated on the associated Kubernetes node like so:</p>
            <pre><code>kubectl get node -o json worker1a | jq '.status.conditions[] | select(.type | test("^NPD"))'
{
  "lastHeartbeatTime": "2020-03-20T17:05:17Z",
  "lastTransitionTime": "2020-03-20T17:05:16Z",
  "message": "Too many calico interfaces (154)",
  "reason": "TooManyCalicoInterfaces",
  "status": "True",
  "type": "NPDCalicoUnhealthy"
}</code></pre>
            <p>With that in place, the actual remediation needed to happen. Kured seemed to do most everything we needed, except that it was looking at a file instead of Kubernetes node conditions. We hacked together a patch to change that and tested it successfully end to end — we had a working proof of concept!</p>
    <div>
      <h2>Revisiting Problem Detection</h2>
      <a href="#revisiting-problem-detection">
        
      </a>
    </div>
    <p>While the above worked, we found that node-problem-detector was unwieldy because we were replicating our existing monitoring into shell scripts and node-problem-detector configuration. A <a href="https://www.infoq.com/news/2017/10/monitoring-cloudflare-prometheus/">2017 blog post</a> describes Cloudflare’s monitoring stack, although some things have changed since then. What hasn’t changed is our extensive usage of <a href="https://prometheus.io/">Prometheus</a> and <a href="https://github.com/prometheus/alertmanager">Alertmanager</a>.</p><p>For the network interface issue and other issues we wanted to address, we already had the necessary exported metrics and alerting to go with them. Here is what our already existing alert looked like<sup>3</sup>:</p>
            <pre><code>- alert: CalicoTooManyInterfaces
  expr: sum(node_network_info{device=~"cali.*"}) by (node) &gt;= 200
  for: 1h
  labels:
    priority: "5"
    notify: chat-sre-core chat-k8s</code></pre>
            <p>Note that we use a “notify” label to drive the routing logic in Alertmanager. However, that got us asking, could we just route this to a Kubernetes node condition instead?</p>
    <div>
      <h2>Introducing Sciuro</h2>
      <a href="#introducing-sciuro">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2Hx2xX6FXjjTsCdZZFmn7O/1e8be40972f81d59b9be18a1e60c68c4/image1-6.png" />
            
            </figure><p>Sciuro is our open-source replacement of node-problem-detector that has one simple job: synchronize Kubernetes node conditions with currently firing alerts in Alertmanager. Node problems can be defined with a more holistic view and using already existing exporters such as <a href="https://github.com/prometheus/node_exporter">node exporter</a>, <a href="https://github.com/google/cadvisor">cadvisor</a> or <a href="https://github.com/google/mtail">mtail</a>. It also doesn’t run on affected nodes which allows us to rely on out-of-band remediation techniques. Here is a high level diagram of how Sciuro works:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2TF20JXIsXxWApKN9yyEcy/fe4d2244e860fe8e0c1477e06ae24c30/image4-3.png" />
            
            </figure><p>Starting from the top, nodes are scraped by Prometheus, which collects those metrics and fires relevant alerts to Alertmanager. Sciuro polls Alertmanager for alerts with a matching receiver, matches them with a corresponding node resource in the Kubernetes API and updates that node’s conditions accordingly.</p><p>In more detail, we can start by defining an alert in Prometheus like the following:</p>
            <pre><code>- alert: CalicoTooManyInterfacesEarly
  expr: sum(node_network_info{device=~"cali.*"}) by (node) &gt;= 150
  labels:
    priority: "6"
    notify: node-condition-k8s</code></pre>
            <p>Note the two differences from the previous alert. First, we use a new name with a more sensitive trigger. The idea is that we want automatic node remediation to try fixing the node first as soon as possible, but if the problem worsens or automatic remediation is failing, humans will still get notified to act. The second difference is that instead of notifying chat rooms, we route to a target called “node-condition-k8s”.</p><p>Sciuro then comes into play, polling the Altertmanager API for alerts matching the “node-condition-k8s” receiver. The following shows the equivalent using <a href="https://github.com/prometheus/alertmanager/tree/master/cmd/amtool">amtool</a>:</p>
            <pre><code>$ amtool alert query -r node-condition-k8s
Alertname                 	Starts At            	Summary                                                               	 
CalicoTooManyInterfacesEarly  2021-05-11 03:25:21 UTC  Kubernetes node worker1a has too many Calico interfaces  </code></pre>
            <p>We can also check the labels for this alert:</p>
            <pre><code>$ amtool alert query -r node-condition-k8s -o json | jq '.[] | .labels'
{
  "alertname": "CalicoTooManyInterfacesEarly",
  "cluster": "a.k8s",
  "instance": "worker1a",
  "node": "worker1a",
  "notify": "node-condition-k8s",
  "priority": "6",
  "prometheus": "k8s-a"
}</code></pre>
            <p>Note the node and instance labels which Sciuro will use for matching with the corresponding Kubernetes node. Sciuro uses the excellent <a href="https://github.com/kubernetes-sigs/controller-runtime">controller-runtime</a> to keep track of and update node sources in the Kubernetes API. We can observe the updated node condition on the status field via kubectl:</p>
            <pre><code>$ kubectl get node worker1a -o json | jq '.status.conditions[] | select(.type | test("^AlertManager"))'
{
  "lastHeartbeatTime": "2021-05-11T03:31:20Z",
  "lastTransitionTime": "2021-05-11T03:26:53Z",
  "message": "[P6] Kubernetes node worker1a has too many Calico interfaces",
  "reason": "AlertIsFiring",
  "status": "True",
  "type": "AlertManager_CalicoTooManyInterfacesEarly"
}</code></pre>
            <p>One important note is Sciuro added the AlertManager_ prefix to the node condition type to prevent conflicts with other node condition types. For example, DiskPressure, a kubelet managed condition, could also be an alert name. Sciuro will also properly update heartbeat and transition times to reflect when it first saw the alert and its last update. With node conditions synchronized by Sciuro, remediation can take place via one of the existing tools. As mentioned previously we are using a modified version of Kured for now.</p><p>We’re happy to announce that we’ve open sourced Sciuro, and it can be found on <a href="https://github.com/cloudflare/sciuro">GitHub</a> where you can read the code, find the deployment instructions, or open a Pull Request for changes.</p>
    <div>
      <h2>Managing Node Uptime</h2>
      <a href="#managing-node-uptime">
        
      </a>
    </div>
    <p>While we began using automatic node remediation for obvious problems, we’ve expanded its purpose to additionally keep node uptime low. Low node uptime is desirable to further reduce drift on nodes, keep the node initialization process well-oiled, and encourage the best deployment practices on the Kubernetes clusters. To expand on the last point, services that are deployed with best practices and in a high availability fashion should see negligible impact when a single node leaves the cluster. However, services that are not deployed with best practices will most likely have problems especially if they rely on singleton pods. By draining nodes more frequently, it introduces regular chaos that encourages best practices. To enable this with automatic node remediation the following alert was defined:</p>
            <pre><code>- alert: WorkerUptimeTooHigh
  expr: |
    (
      (
        (
              max by(node) (kube_node_role{role="worker"})
            - on(node) group_left()
              (max by(node) (kube_node_role{role!="worker"}))
          or on(node)
            max by(node) (kube_node_role{role="worker"})
        ) == 1
      )
    * on(node) group_left()
      (
        (time() - node_boot_time_seconds) &gt; (60 * 60 * 24 * 7)
      )
    )
  labels:
    priority: "9"
    notify: node-condition-k8s</code></pre>
            <p>There is a bit of juggling with the kube_node_roles metric in the above to isolate the alert to generic worker nodes, but at a high level it looks at node_boot_time_seconds, a metric from <a href="https://github.com/prometheus/node_exporter">prometheus node_exporter</a>. Again the notify label is configured to send to node conditions which kicks off the automatic node remediation. One further detail is the priority here is set to “9” which is of lower precedence than our other alerts. Note that the message field of the node condition is prefixed with the alert priority in brackets. This allows the remediation process to take priority into account when choosing which node to remediate first, which is important because Kured uses a lock to act on a single node at a time.</p>
    <div>
      <h2>Wrapping Up</h2>
      <a href="#wrapping-up">
        
      </a>
    </div>
    <p>In the past 30 days, we’ve used the above automatic node remediation process to action 571 nodes. That has saved our humans a considerable amount of time. We’ve also been able to reduce the time to repair for some issues as automatic remediation can act at all times of the day and with a faster response time.</p><p>As mentioned before, we’re open sourcing Sciuro and its code can be found on <a href="https://github.com/cloudflare/sciuro">GitHub</a>. We’re open to issues, suggestions, and pull requests. We do have some ideas for future improvements. For Sciuro, we may look to reduce latency which is mainly due to polling and potentially add a push model from Altermanager although this isn’t a need we’ve had yet.  For the larger node remediation story, we hope to do an overhaul of the remediating component. As mentioned previously, we are currently using a fork of kured, but a future replacement component should include the following:</p><ul><li><p>Use out-of-band management interfaces to be able to shut down and power on nodes without a functional operating system.</p></li><li><p>Move from decentralized architecture to a centralized one that can integrate more complicated logic. This might include being able to act on entire failure domains in parallel.</p></li><li><p>Handle specialized nodes such as masters or storage nodes.</p></li></ul><p>Finally, we’re looking for more people passionate about Kubernetes to <a href="https://boards.greenhouse.io/cloudflare/jobs/816059?gh_jid=816059">join our team</a>. Come help us push Kubernetes to the next level to serve Cloudflare’s many needs!</p><hr /><p><sup>1</sup>Exhaustion can be applied to hardware resources, kernel resources, or logical resources like the amount of logging being produced.</p><p><sup>2</sup>Nearly all Kubernetes objects have <a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/">spec and status fields</a>. The status field is used to describe the current state of an object. For node resources, typically the kubelet manages a conditions field under the status field for reporting things like if the node is ready for servicing pods.
<sup>3</sup>The format of the following alert is documented on <a href="https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/">Prometheus Alerting Rules</a>.</p> ]]></content:encoded>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[Reliability]]></category>
            <category><![CDATA[Monitoring]]></category>
            <guid isPermaLink="false">76X316XLhCYnEUO7ZQR8CO</guid>
            <dc:creator>Andrew DeMaria</dc:creator>
        </item>
        <item>
            <title><![CDATA[Moving k8s communication to gRPC]]></title>
            <link>https://blog.cloudflare.com/moving-k8s-communication-to-grpc/</link>
            <pubDate>Sat, 20 Mar 2021 14:00:00 GMT</pubDate>
            <description><![CDATA[ How we use gRPC in combination with Kubernetes to improve the performance and usability of internal APIs. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Over the past year and a half, Cloudflare has been hard at work moving our back-end services running in our non-edge locations from bare metal solutions and Mesos Marathon to a more unified approach using <a href="https://kubernetes.io/">Kubernetes(K8s)</a>. We chose Kubernetes because it allowed us to split up our monolithic application into many different microservices with granular control of communication.</p><p>For example, a <a href="https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/">ReplicaSet</a> in Kubernetes can provide high availability by ensuring that the correct number of pods are always available. A <a href="https://kubernetes.io/docs/concepts/workloads/pods/">Pod</a> in Kubernetes is similar to a container in <a href="https://www.docker.com/">Docker</a>. Both are responsible for running the actual application. These pods can then be exposed through a Kubernetes <a href="https://kubernetes.io/docs/concepts/services-networking/service/">Service</a> to abstract away the number of replicas by providing a single endpoint that load balances to the pods behind it. The services can then be exposed to the Internet via an <a href="https://kubernetes.io/docs/concepts/services-networking/ingress/">Ingress</a>. Lastly, a network policy can protect against unwanted communication by ensuring the correct policies are applied to the application. These policies can include L3 or L4 rules.</p><p>The diagram below shows a simple example of this setup.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/33zWCqFZw2iuXfhllsHgdk/e290742e17a975a98a195bccc283297f/2-3.png" />
            
            </figure><p>Though Kubernetes does an excellent job at providing the tools for communication and traffic management, it does not help the developer decide the best way to communicate between the applications running on the pods. Throughout this blog we will look at some of the decisions we made and why we made them to discuss the pros and cons of two commonly used API architectures, REST and gRPC.</p>
    <div>
      <h3>Out with the old, in with the new</h3>
      <a href="#out-with-the-old-in-with-the-new">
        
      </a>
    </div>
    <p>When the DNS team first moved to Kubernetes, all of our pod-to-pod communication was done through REST APIs and in many cases also included Kafka. The general communication flow was as follows:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3GQIkngkqEYcBJNFzv1xNU/fe13bc8911a11a9c26dc5925b4fe6a19/1-5.png" />
            
            </figure><p>We use Kafka because it allows us to handle large spikes in volume without losing information. For example, during a Secondary DNS Zone zone transfer, Service A tells Service B that the zone is ready to be published to the edge. Service B then calls Service A’s REST API, generates the zone, and pushes it to the edge. If you want more information about how this works, I wrote an entire blog post about the <a href="/secondary-dns-deep-dive/">Secondary DNS pipeline</a> at Cloudflare.</p><p>HTTP worked well for most communication between these two services. However, as we scaled up and added new endpoints, we realized that as long as we control both ends of the communication, we could improve the usability and performance of our communication. In addition, sending large DNS zones over the network using HTTP often caused issues with sizing constraints and compression.</p><p>In contrast, gRPC can easily stream data between client and server and is commonly used in microservice architecture. These qualities made gRPC the obvious replacement for our REST APIs.</p>
    <div>
      <h3>gRPC Usability</h3>
      <a href="#grpc-usability">
        
      </a>
    </div>
    <p>Often overlooked from a developer’s perspective, HTTP client libraries are clunky and require code that defines paths, handles parameters, and deals with responses in bytes. gRPC abstracts all of this away and makes network calls feel like any other function calls defined for a struct.</p><p>The example below shows a very basic schema to set up a GRPC client/server system. As a result of gRPC using <a href="https://developers.google.com/protocol-buffers">protobuf</a> for serialization, it is largely language agnostic. Once a schema is defined, the <i>protoc</i> command can be used to generate code for <a href="https://grpc.io/docs/languages/">many languages</a>.</p><p>Protocol Buffer data is structured as <i>messages,</i> with each <i>message</i> containing information stored in the form of fields. The fields are strongly typed, providing type safety unlike JSON or XML. Two messages have been defined, <i>Hello</i> and <i>HelloResponse</i>. Next we define a service called <i>HelloWorldHandler</i> which contains one RPC function called <i>SayHello</i> that must be implemented if any object wants to call themselves a <i>HelloWorldHandler</i>.</p><p>Simple Proto:</p>
            <pre><code>message Hello{
   string Name = 1;
}

message HelloResponse{}

service HelloWorldHandler {
   rpc SayHello(Hello) returns (HelloResponse){}
}</code></pre>
            <p>Once we run our <i>protoc</i> command, we are ready to write the server-side code. In order to implement the <i>HelloWorldHandler</i>, we must define a struct that implements all of the RPC functions specified in the protobuf schema above_._ In this case, the struct <i>Server</i> defines a function <i>SayHello</i> that takes in two parameters, context and <i>*pb.Hello</i>. <i>*pb.Hello</i> was previously specified in the schema and contains one field, <i>Name. SayHello</i> must also return the <i>*pbHelloResponse</i> which has been defined without fields for simplicity.</p><p>Inside the main function, we create a TCP listener, create a new gRPC server, and then register our handler as a <i>HelloWorldHandlerServer</i>. After calling <i>Serve</i> on our gRPC server, clients will be able to communicate with the server through the function <i>SayHello</i>.</p><p>Simple Server:</p>
            <pre><code>type Server struct{}

func (s *Server) SayHello(ctx context.Context, in *pb.Hello) (*pb.HelloResponse, error) {
    fmt.Println("%s says hello\n", in.Name)
    return &amp;pb.HelloResponse{}, nil
}

func main() {
    lis, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic(err)
    }
    gRPCServer := gRPC.NewServer()
    handler := Server{}
    pb.RegisterHelloWorldHandlerServer(gRPCServer, &amp;handler)
    if err := gRPCServer.Serve(lis); err != nil {
        panic(err)
    }
}</code></pre>
            <p>Finally, we need to implement the gRPC Client. First, we establish a TCP connection with the server. Then, we create a new <i>pb.HandlerClient</i>. The client is able to call the server's <i>SayHello</i> function by passing in a *<i>pb.Hello</i> object.</p><p>Simple Client:</p>
            <pre><code>conn, err := gRPC.Dial("127.0.0.1:8080", gRPC.WithInsecure())
if err != nil {
    panic(err)
}
client := pb.NewHelloWorldHandlerClient(conn)
client.SayHello(context.Background(), &amp;pb.Hello{Name: "alex"})</code></pre>
            <p>Though I have removed some code for simplicity, these <i>services</i> and <i>messages</i> can become quite complex if needed. The most important thing to understand is that when a server attempts to announce itself as a <i>HelloWorldHandlerServer</i>, it is required to implement the RPC functions as specified within the protobuf schema. This agreement between the client and server makes cross-language network calls feel like regular function calls.</p><p>In addition to the basic Unary server described above, gRPC lets you decide between four types of service methods:</p><ul><li><p><b>Unary</b> (example above): client sends a single request to the server and gets a single response back, just like a normal function call.</p></li><li><p><b>Server Streaming:</b> server returns a stream of messages in response to a client's request.</p></li><li><p><b>Client Streaming:</b> client sends a stream of messages to the server and the server replies in a single message, usually once the client has finished streaming.</p></li><li><p><b>Bi-directional Streaming:</b> the client and server can both send streams of messages to each other asynchronously.</p></li></ul>
    <div>
      <h3>gRPC Performance</h3>
      <a href="#grpc-performance">
        
      </a>
    </div>
    <p>Not all HTTP connections are created equal. Though Golang natively supports HTTP/2, the HTTP/2 transport must be set by the client and the server must also support HTTP/2. Before moving to gRPC, we were still using HTTP/1.1 for client connections. We could have switched to HTTP/2 for performance gains, but we would have lost some of the benefits of native protobuf compression and usability changes.</p><p>The best option available in HTTP/1.1 is pipelining. Pipelining means that although requests can share a connection, they must queue up one after the other until the request in front completes. HTTP/2 improved pipelining by using connection multiplexing. Multiplexing allows for multiple requests to be sent on the same connection and at the same time.</p><p>HTTP REST APIs generally use JSON for their request and response format. Protobuf is the native request/response format of gRPC because it has a standard schema agreed upon by the client and server during registration. In addition, protobuf is known to be significantly faster than JSON due to its serialization speeds. I’ve run some benchmarks on my laptop, source code can be found <a href="https://github.com/Fattouche/protobuf-benchmark">here</a>.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/23TkQ80Iruo8IYflqIuobN/f7323dd5817ca47f9b203b8b63a3a980/image1-26.png" />
            
            </figure><p>As you can see, protobuf performs better in small, medium, and large data sizes. It is faster per operation, smaller after marshalling, and scales well with input size. This becomes even more noticeable when unmarshaling very large data sets. Protobuf takes 96.4ns/op but JSON takes 22647ns/op, a 235X reduction in time! For large DNS zones, this efficiency makes a massive difference in the time it takes us to go from record change in our API to serving it at the edge.</p><p>Combining the benefits of HTTP/2 and protobuf showed almost no performance change from our application’s point of view. This is likely due to the fact that our pods were already so close together that our connection times were already very low. In addition, most of our gRPC calls are done with small amounts of data where the difference is negligible. One thing that we did notice <b>—</b> likely related to the multiplexing of HTTP/2 <b>—</b> was greater efficiency when writing newly created/edited/deleted records to the edge. Our latency spikes dropped in both amplitude and frequency.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6PDR6bZkh8zzVVv2j6tmE5/9ce516e00daa832135964246eaf7b95c/image2-19.png" />
            
            </figure>
    <div>
      <h3>gRPC Security</h3>
      <a href="#grpc-security">
        
      </a>
    </div>
    <p>One of the best features in Kubernetes is the NetworkPolicy. This allows developers to control what goes in and what goes out.</p>
            <pre><code>apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - ipBlock:
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24
    - namespaceSelector:
        matchLabels:
          project: myproject
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 6379
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 5978</code></pre>
            <p>In this example, taken from the <a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/">Kubernetes docs</a>, we can see that this will create a network policy called test-network-policy. This policy controls both ingress and egress communication to or from any pod that matches the role <i>db</i> and enforces the following rules:</p><p>Ingress connections allowed:</p><ul><li><p>Any pod in default namespace with label “role=frontend”</p></li><li><p>Any pod in any namespace that has a label “project=myproject”</p></li><li><p>Any source IP address in 172.17.0.0/16 except for 172.17.1.0/24</p></li></ul><p>Egress connections allowed:</p><ul><li><p>Any dest IP address in 10.0.0.0/24</p></li></ul><p>NetworkPolicies do a fantastic job of protecting APIs at the network level, however, they do nothing to protect APIs at the application level. If you wanted to control which endpoints can be accessed within the API, you would need k8s to be able to not only distinguish between pods, but also endpoints within those pods. These concerns led us to <a href="https://grpc.io/docs/guides/auth/">per RPC credentials</a>. Per RPC credentials are easy to set up on top of the pre-existing gRPC code. All you need to do is add interceptors to both your stream and unary handlers.</p>
            <pre><code>func (s *Server) UnaryAuthInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    // Get the targeted function
    functionInfo := strings.Split(info.FullMethod, "/")
    function := functionInfo[len(functionInfo)-1]
    md, _ := metadata.FromIncomingContext(ctx)

    // Authenticate
    err := authenticateClient(md.Get("username")[0], md.Get("password")[0], function)
    // Blocked
    if err != nil {
        return nil, err
    }
    // Verified
    return handler(ctx, req)
}</code></pre>
            <p>In this example code snippet, we are grabbing the username, password, and requested function from the info object. We then authenticate against the client to make sure that it has correct rights to call that function. This interceptor will run before any of the other functions get called, which means one implementation protects all functions. The client would initialize its secure connection and send credentials like so:</p>
            <pre><code>transportCreds, err := credentials.NewClientTLSFromFile(certFile, "")
if err != nil {
    return nil, err
}
perRPCCreds := Creds{Password: grpcPassword, User: user}
conn, err := grpc.Dial(endpoint, grpc.WithTransportCredentials(transportCreds), grpc.WithPerRPCCredentials(perRPCCreds))
if err != nil {
    return nil, err
}
client:= pb.NewRecordHandlerClient(conn)
// Can now start using the client</code></pre>
            <p>Here the client first verifies that the server matches with the certFile. This step ensures that the client does not accidentally send its password to a bad actor. Next, the client initializes the <i>perRPCCreds</i> struct with its username and password and dials the server with that information. Any time the client makes a call to an rpc defined function, its credentials will be verified by the server.</p>
    <div>
      <h3>Next Steps</h3>
      <a href="#next-steps">
        
      </a>
    </div>
    <p>Our next step is to remove the need for many applications to access the database and ultimately DRY up our codebase by pulling all DNS-related code into a single API, accessed from one gRPC interface. This removes the potential for mistakes in individual applications and makes updating our database schema easier. It also gives us more granular control over which functions can be accessed rather than which tables can be accessed.</p><p>So far, the DNS team is very happy with the results of our gRPC migration. However, we still have a long way to go before we can move entirely away from REST. We are also patiently waiting for <a href="https://github.com/grpc/grpc/issues/19126">HTTP/3 support</a> for gRPC so that we can take advantage of those super <a href="https://en.wikipedia.org/wiki/QUIC">quic</a> speeds!</p> ]]></content:encoded>
            <category><![CDATA[DNS]]></category>
            <category><![CDATA[gRPC]]></category>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[HTTP2]]></category>
            <category><![CDATA[QUIC]]></category>
            <guid isPermaLink="false">6oeh7RRYqhqnS7vtJ2BpwP</guid>
            <dc:creator>Alex Fattouche</dc:creator>
        </item>
        <item>
            <title><![CDATA[Lessons Learned from Scaling Up Cloudflare’s Anomaly Detection Platform]]></title>
            <link>https://blog.cloudflare.com/lessons-learned-from-scaling-up-cloudflare-anomaly-detection-platform/</link>
            <pubDate>Fri, 12 Mar 2021 15:48:32 GMT</pubDate>
            <description><![CDATA[ Anomaly Detection uses an algorithm called Histogram-Based Outlier Scoring (HBOS) to detect anomalous traffic in a scalable way. While HBOS is less precise than algorithms like kNN when it comes to local outliers, it is able to score global outliers quickly (in linear time). ]]></description>
            <content:encoded><![CDATA[ 
    <div>
      <h3>Introduction to Anomaly Detection for Bot Management</h3>
      <a href="#introduction-to-anomaly-detection-for-bot-management">
        
      </a>
    </div>
    <p>Cloudflare’s <a href="https://www.cloudflare.com/products/bot-management/">Bot Management platform</a> follows a “defense in depth” model. Although each layer of Bot Management has its own strengths and weaknesses, the combination of many different detection systems — including Machine Learning, rule-based heuristics, JavaScript challenges, and more — makes for a robust platform in which different detection systems compensate for each other’s weaknesses.</p><p>One of these systems is Anomaly Detection, a platform motivated by a simple idea: because bots are made to accomplish specific goals, such as credential stuffing or <a href="https://www.cloudflare.com/learning/ai/how-to-prevent-web-scraping/">content scraping</a>, they interact with websites in distinct and difficult-to-disguise ways. Over time, the actions of a bot are likely to differ from those of a real user. Anomaly detection aims to model the characteristics of legitimate user traffic as a healthy baseline. Then, when automated bot traffic is set against this baseline, the bots appear as outlying anomalies that can be targeted for mitigation.</p><p>An anomaly detection approach is:</p><ul><li><p>Resilient against bots that try to circumvent protections by spoofing request metadata (e.g., user agents)</p></li><li><p>Able to catch previously unseen bots without being explicitly trained against them.</p></li></ul><p>So, how well does this work?</p><p>Today, Anomaly Detection processes more than 500K requests per second. This translates to over 200K CAPTCHAs issued per minute, not including traffic that’s already caught by other bot management systems or traffic that’s outright blocked. These suspected bots originate from over 140 different countries and 2,200 different ASNs. And all of this happens using automatically generated baselines and visitor models which are unique to every enrolled site — no cross-site analysis or manual intervention required.</p>
    <div>
      <h3>How Anomaly Detection Identifies Bots</h3>
      <a href="#how-anomaly-detection-identifies-bots">
        
      </a>
    </div>
    <p>Anomaly Detection uses an algorithm called <a href="https://www.goldiges.de/publications/HBOS-KI-2012.pdf">Histogram-Based Outlier Scoring (HBOS)</a> to detect anomalous traffic in a scalable way. While HBOS is less precise than algorithms like kNN when it comes to local outliers, it is able to score global outliers quickly (in linear time).</p><p>There are two parts to every behavioral bot detection: the site-specific baseline and the visitor-specific behavior model. We make heavy use of <a href="https://clickhouse.tech/">ClickHouse</a>, an open-source columnar database, for <a href="/http-analytics-for-6m-requests-per-second-using-clickhouse/">storing and analyzing enormous amounts of data</a>. When a customer opts-in to Anomaly Detection, we begin aggregating traffic data in ClickHouse to form a unique baseline for their site.</p><p>To understand visitor behavior, we maintain a sliding window of ephemeral feature aggregates in-memory using <a href="https://redis.io/">Redis</a>. <a href="https://en.wikipedia.org/wiki/HyperLogLog">HyperLogLog</a> data structures allow us to efficiently store and estimate unique counts of these high-cardinality features. Because these data are privacy-sensitive, they are retained only within a recent time window and are specific to each opted-in site. This makes efficient data representations due to the resulting high cardinality of the problem space.</p><p>The output of each detection run is an <i>outlier score</i>, representing how anomalous a visitor’s behavior is when viewed against the baseline for that particular site. This outlier score feeds into the final bot score calculation for use on the edge.</p>
    <div>
      <h3>The Anomaly Detection Platform</h3>
      <a href="#the-anomaly-detection-platform">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/17JKOorPN2YNRAHZAJxFd2/53fae073ecb2cf4dd98e5a8b6308d1dc/image1-13.png" />
            
            </figure><p>The Anomaly Detection platform consists of a series of microservices running on <a href="https://kubernetes.io/">Kubernetes</a>. Request data come in through a dedicated Kafka topic and are inserted to both ClickHouse and Redis.</p><p>The Detector service lazily retrieves (and caches) baseline data from the Baseline service and calculates outlier scores for visitors compared to the baselines. These scores are then written to another Kafka topic to be persisted for later analysis.</p><p>Finally, the Publisher service collects batches of detections (visitors whose behavior is anomalous or bot-like) and sends them out to the edge to be applied as part of our bot score calculations.</p><p>Each microservice runs independently and tolerates downtime from its dependencies. They are also sized very differently: some services require dozens of replicas and gigabytes of memory, while others are much cheaper.</p><p>Today, the Anomaly Detection platform handles nearly 500k requests per second across ~310M unique visitors, representing 2x growth over the last six months. But once upon a time, we struggled to handle even 10K rps.</p><p>The story of our growth is also the story of how we adapted, redesigned, and improved our infrastructure in order to respond to the corresponding increases in resource demand, customer support requests, and maintenance challenges.</p>
    <div>
      <h3>Launch, then Iterate</h3>
      <a href="#launch-then-iterate">
        
      </a>
    </div>
    <p>In an earlier incarnation, most of the core Anomaly Detection logic was contained in a single (replicated) service running under Kubernetes.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3nZtBtQAWGXjQBbez5abWH/fa6c555c497ae2b349b43961c1852522/image2-7.png" />
            
            </figure><p>Each service pod fulfilled multiple responsibilities: generating behavioral baselines from ClickHouse data, aggregating visitor profiles from Redis, and calculating outlier scores. These outlier detections were then forwarded to the edge by piggybacking on another bot management system’s existing integration with <a href="/introducing-quicksilver-configuration-distribution-at-internet-scale/">Quicksilver</a>, Cloudflare’s replicated key-value store.</p><p>This simple design was easy to understand and implement. It also reused existing infrastructure, making it perfect for a v1 deployment in keeping with Cloudflare’s culture of fast iteration. Of course, it also had some shortcomings:</p><ul><li><p>A monolithic service meant a single (logical) point of failure.</p></li><li><p>From a resource (CPU, memory) perspective, it was difficult to scale pieces of functionality independently.</p></li><li><p>The “reused” integration with Quicksilver was never meant to support something like Anomaly Detection, causing instability for both systems.</p></li></ul><p>It’s easy in hindsight to focus on the flaws of an existing system, but it’s important to keep in mind that priorities can and should evolve over time. A design that doesn’t meet today’s needs was likely suited to the needs of yesterday.</p><p>One key benefit of a launch-and-iterate approach is that you get a concrete, real-world understanding of where your system needs improvement. Having a real system to observe means that improvements are both targeted and measurable.</p>
    <div>
      <h3>Tuning Redis</h3>
      <a href="#tuning-redis">
        
      </a>
    </div>
    <p>As mentioned above, Redis is a key part of the Anomaly Detection platform, used to store and aggregate features about site visitors. Although we only keep these data in a sliding window, the cardinality of the set is very large (per visitor per site). For this reason, many of the early improvements to Anomaly Detection performance centered on Redis. In fact, the first launch of Anomaly Detection struggled to keep up with only 10k requests per second.</p><p>Profiling revealed that load was centered on our heavy use of the Redis PFMERGE command for merging HyperLogLog structures. Unlike most Redis commands, which are O(1), PFMERGE runs in linear time (proportional to the number of features * window size). As the demand for scoring increased, this proved to be a serious bottleneck.</p><p>To resolve this problem, we looked for ways to further optimize our use of Redis. One idea was lowering the threshold for promoting a sparse HyperLogLog representation to a dense one - trading memory for compute, as dense HyperLogLogs are generally faster to merge.</p><p>However, as is so often the case, a big win came from a simple idea: we introduced a “recency register,” essentially a cache that placed a time bound on how often we would run expensive detection logic on a given site-visitor pair. Since behavior patterns need to be established over a time window, the potential extra detection latency from the recency register was not a significant concern. This straightforward solution was enough to raise our throughput by an order of magnitude.</p><p>Working with Redis involves a lot of balancing between memory and compute resources. For example, our Redis shards’ memory sizes were empirically determined based on the observed CPU utilization when reaching memory bounds. A higher memory bound meant more visitors tracked per shard and thus more commands per second. The fact that Redis shards are single-threaded made reasoning about these situations easier as well.</p><p>As the number of features and visitors grew, we discovered that “standard” Redis recommendations didn’t always work for us in practice. Redis typically recommends using human-readable keys, even if they are longer.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/l8FHluRsIC5sNkJzVeELp/535f4a5123f405f0657cd3a2fe74209f/image3-11.png" />
            
            </figure><p>However, by encoding our keys in a compact, binary-encoded format, we observed roughly 30% memory savings, as well as CPU savings — which again demonstrates the value of iterating on a real-world system.</p>
    <div>
      <h3>Moving to Microservices</h3>
      <a href="#moving-to-microservices">
        
      </a>
    </div>
    <p>As Anomaly Detection continued to grow in adoption, it became clear that optimizing individual pieces of the pipeline was no longer sufficient: our platform needed to scale up as well. But, as it turns out, scaling isn’t as simple as requesting more resources and running more replicas of whatever service isn’t keeping up with demand. As we expanded, the amount of load we placed on external (shared!) dependencies like ClickHouse grew. The way we piggybacked on Quicksilver to send updates to the edge coupled two systems together in a bloated and unreliable way.</p><p>So we set out to do more with less - to build a more resilient system that would also be a better steward of Cloudflare’s shared resources.</p><p>The idea of a microservice-based architecture was not a new one; in fact, even early Anomaly Detection designs suggested the eventual need for such a migration. But real-world observations indicated that the redesign was now fully worth the time investment.</p><p>Why did we think moving to microservices would help us solve our scalability issues?</p><p>First, we observed that a large contributor to our load on ClickHouse was repeated baseline aggregation. Because each replica of the monolithic Anomaly Detection service calculated its own copies of the baseline profiles, our pressure on ClickHouse would increase each time we horizontally scaled our service deployment. What’s more, this work was essentially duplicated. There was no reason each replica should need to recalculate copies of the same baseline. Moving this work to a dedicated baseline service cut out the duplication to the tune of a 10x reduction in load from this particular operation.</p><p>Secondly, we noticed that part of our use case (accept a stream of data from Kafka, apply simple transformations, and persist batches of this data to ClickHouse) was a pretty common one at Cloudflare. There already existed robust, battle-tested inserter code for launching microservices with exactly this pattern of operation. Adapting this code to suit our needs not only saved us development time, but brought us more in line with wider convention.</p><p>We also learned the importance of concrete details during design. When we initially began working on the redesign of the Anomaly Detection platform, we felt that Kafka might have a role to play in connecting some of our services. Still, we couldn’t fully justify the initial investment required to move away from the RESTful interfaces we already had.</p><p>The benefits of using Kafka only became clear and concrete once we committed to using ClickHouse as the storage solution for our outlier score data. ClickHouse performs best when data is inserted in larger, less frequent batches, rather than rapid, small operations, which create a lot of internal overhead. Transporting outlier scores via Kafka allowed us to batch updates while being resilient to data loss during transient downtime.</p>
    <div>
      <h3>The Future</h3>
      <a href="#the-future">
        
      </a>
    </div>
    <p>It’s been a journey getting to this point, but we’re far from done. Cloudflare’s mission is to help make the Internet better for everyone - from small websites to the largest enterprises. For Anomaly Detection, this means expanding into a problem space with huge cardinality: roughly the cross-product of the number of sites and the number of unique visitors. To do this, we’re continuing to improve the efficiency of our infrastructure through smarter traffic sampling, compressed baseline windows, and more memory-efficient data representations.</p><p>Additionally, we want to deliver even better detection accuracy on sites with multiple distinct traffic types. Traffic coming to a site from web browsers behaves quite differently than traffic coming from mobile apps — but both sources of traffic are legitimate. While the HBOS outlier detection algorithm is lightweight and efficient, there are alternatives which are more performant in the presence of multiple traffic profiles.</p><p>One of these alternatives is local outlier factor (LOF) detection. LOF automatically builds baselines that capture “local clusters” of behavior corresponding to multiple traffic streams, rather than a single site-wide baseline. These new baselines allow Anomaly Detection to better distinguish between human use of a web browser and automated abuse of an API on the same site. Of course, there are trade-offs here as well: generating, storing, and working with these larger and more sophisticated behavioral baselines requires careful and creative engineering. But the reward for doing so is enhanced protection for an even wider range of sites using Anomaly Detection.</p><p>Finally, let’s not forget the very human side of building, supporting, and expanding Anomaly Detection and Bot Management. We’ve recently launched features that speed up model experimentation for Anomaly Detection, allow us to run “shadow” models to record and evaluate performance behind the scenes, give us instant “escape hatches” in case of unexpected customer impact, and more. But our team — as well as the many Solutions Engineers, Product Managers, Subject Matter Experts, and others who support Bot Management — are continuing to invest in improved tooling and education. It’s no small challenge, but it’s an exciting one.</p> ]]></content:encoded>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[Bots]]></category>
            <guid isPermaLink="false">5KIqXowbX5rR6s5pmhujsE</guid>
            <dc:creator>Jeffrey Tang</dc:creator>
        </item>
        <item>
            <title><![CDATA[Getting to the Core: Benchmarking Cloudflare’s Latest Server Hardware]]></title>
            <link>https://blog.cloudflare.com/getting-to-the-core/</link>
            <pubDate>Fri, 20 Nov 2020 12:00:00 GMT</pubDate>
            <description><![CDATA[ A refresh of the hardware that Cloudflare uses to run analytics provided big efficiency improvements. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Maintaining a server fleet the size of Cloudflare’s is an operational challenge, to say the least. Anything we can do to lower complexity and improve efficiency has effects for our SRE (Site Reliability Engineer) and Data Center teams that can be felt throughout a server’s 4+ year lifespan.</p><p>At the Cloudflare Core, we process logs to analyze attacks and compute analytics. In 2020, our Core servers were in need of a refresh, so we decided to redesign the hardware to be more in line with our Gen X edge servers. We designed two major server variants for the core. The first is Core Compute 2020, an AMD-based server for analytics and general-purpose compute paired with solid-state storage drives. The second is Core Storage 2020, an Intel-based server with twelve spinning disks to run database workloads.</p>
    <div>
      <h2>Core Compute 2020</h2>
      <a href="#core-compute-2020">
        
      </a>
    </div>
    <p>Earlier this year, we blogged about our 10th generation edge servers or Gen X and the <a href="/technical-details-of-why-cloudflare-chose-amd-epyc-for-gen-x-servers/">improvements</a> they delivered to our edge in <a href="/an-epyc-trip-to-rome-amd-is-cloudflares-10th-generation-edge-server-cpu/">both</a> performance and <a href="/securing-memory-at-epyc-scale/">security</a>. The new Core Compute 2020 server leverages many of our learnings from the edge server. The Core Compute servers run a variety of workloads including Kubernetes, Kafka, and various smaller services.</p>
    <div>
      <h3>Configuration Changes (Kubernetes)</h3>
      <a href="#configuration-changes-kubernetes">
        
      </a>
    </div>
    <table><tr><td><p>
</p></td><td><p><b>Previous Generation Compute</b></p></td><td><p><b>Core Compute 2020</b></p></td></tr><tr><td><p>CPU</p></td><td><p>2 x Intel Xeon Gold 6262</p></td><td><p>1 x AMD EPYC 7642</p></td></tr><tr><td><p>Total Core / Thread Count</p></td><td><p>48C / 96T</p></td><td><p>48C / 96T</p></td></tr><tr><td><p>Base / Turbo Frequency</p></td><td><p>1.9 / 3.6 GHz</p></td><td><p>2.3 / 3.3 GHz</p></td></tr><tr><td><p>Memory</p></td><td><p>8 x 32GB DDR4-2666</p></td><td><p>8 x 32GB DDR4-2933</p></td></tr><tr><td><p>Storage</p></td><td><p>6 x 480GB SATA SSD</p></td><td><p>2 x 3.84TB NVMe SSD</p></td></tr><tr><td><p>Network</p></td><td><p>Mellanox CX4 Lx 2 x 25GbE</p></td><td><p>Mellanox CX4 Lx 2 x 25GbE</p></td></tr></table><p><b>Configuration Changes (Kafka)</b></p><table><tr><td><p>
</p></td><td><p><b>Previous Generation (Kafka)</b></p></td><td><p><b>Core Compute 2020</b></p></td></tr><tr><td><p>CPU</p></td><td><p>2 x Intel Xeon Silver 4116</p></td><td><p>1 x AMD EPYC 7642</p></td></tr><tr><td><p>Total Core / Thread Count</p></td><td><p>24C / 48T</p></td><td><p>48C / 96T</p></td></tr><tr><td><p>Base / Turbo Frequency</p></td><td><p>2.1 / 3.0 GHz</p></td><td><p>2.3 / 3.3 GHz</p></td></tr><tr><td><p>Memory</p></td><td><p>6 x 32GB DDR4-2400</p></td><td><p>8 x 32GB DDR4-2933</p></td></tr><tr><td><p>Storage</p></td><td><p>12 x 1.92TB SATA SSD</p></td><td><p>10 x 3.84TB NVMe SSD</p></td></tr><tr><td><p>Network</p></td><td><p>Mellanox CX4 Lx 2 x 25GbE</p></td><td><p>Mellanox CX4 Lx 2 x 25GbE</p></td></tr></table><p>Both previous generation servers were Intel-based platforms, with the Kubernetes server based on Xeon 6262 processors, and the Kafka server based on Xeon 4116 processors. One goal with these refreshed versions was to converge the configurations in order to simplify spare parts and firmware management across the fleet.</p><p>As the above tables show, the configurations have been converged with the only difference being the number of NVMe drives installed depending on the workload running on the host. In both cases we moved from a dual-socket configuration to a single-socket configuration, and the number of cores and threads per server either increased or stayed the same. In all cases, the base frequency of those cores was significantly improved. We also moved from SATA SSDs to NVMe SSDs.</p>
    <div>
      <h3>Core Compute 2020 Synthetic Benchmarking</h3>
      <a href="#core-compute-2020-synthetic-benchmarking">
        
      </a>
    </div>
    <p>The heaviest user of the SSDs was determined to be Kafka. The majority of the time Kafka is sequentially writing 2MB blocks to the disk. We created a simple FIO script with 75% sequential write and 25% sequential read, scaling the block size from a standard page table entry size of 4096B to Kafka’s write size of 2MB. The results aligned with what we expected from an NVMe-based drive.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6gqCJeXAL1sUcfVmBW3tVx/7b8a3a9a233086a321967ebb20878434/image5-5.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6jvSoQBw4BnPCkDYGyeqBH/b2c0a79f10afbdd73ee73d2545f5700f/image4-9.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1N6j9iEmdVaGiomZaoT1wH/cea3efb6d4781f8c0856743869feeb39/image3-8.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3KfgNVcwPiNMcb9Gv3T2KU/74c8932b2f69bacfa32754c4c7c1e2d8/image6-5.png" />
            
            </figure>
    <div>
      <h3>Core Compute 2020 Production Benchmarking</h3>
      <a href="#core-compute-2020-production-benchmarking">
        
      </a>
    </div>
    <p>Cloudflare runs many of our Core Compute services in Kubernetes containers, some of which are multi-core. By transitioning to a single socket, problems associated with dual sockets were eliminated, and we are guaranteed to have all cores allocated for any given container on the same socket.</p><p>Another heavy workload that is constantly running on Compute hosts is the Cloudflare <a href="/the-csam-scanning-tool/">CSAM Scanning Tool</a>. Our Systems Engineering team isolated a Compute 2020 compute host and the previous generation compute host, had them run just this workload, and measured the time to compare the fuzzy hashes for images to the NCMEC hash lists and verify that they are a “miss”.</p><p>Because the CSAM Scanning Tool is very compute intensive we specifically isolated it to take a look at its performance with the new hardware. We’ve spent a great deal of effort on software optimization and improved algorithms for this tool but investing in faster, better hardware is also important.</p><p>In these heatmaps, the X axis represents time, and the Y axis represents “buckets” of time taken to verify that it is not a match to one of the NCMEC hash lists. For a given time slice in the heatmap, the red point is the bucket with the most times measured, the yellow point the second most, and the green points the least. The red points on the Compute 2020 graph are all in the 5 to 8 millisecond bucket, while the red points on the previous Gen heatmap are all in the 8 to 13 millisecond bucket, which shows that on average, the Compute 2020 host is verifying hashes significantly faster.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6JYLpJjG9bAUuQLyeC2kyH/9168b62067b8776a7521e7d831472f74/image2-10.png" />
            
            </figure>
    <div>
      <h2>Core Storage 2020</h2>
      <a href="#core-storage-2020">
        
      </a>
    </div>
    <p>Another major workload we identified was <a href="/clickhouse-capacity-estimation-framework/">ClickHouse</a>, which performs analytics over large datasets. The last time we upgraded our servers running ClickHouse was back in <a href="/http-analytics-for-6m-requests-per-second-using-clickhouse/">2018</a>.</p>
    <div>
      <h3>Configuration Changes</h3>
      <a href="#configuration-changes">
        
      </a>
    </div>
    <table><tr><td><p>
</p></td><td><p><b>Previous Generation</b></p></td><td><p><b>Core Storage 2020</b></p></td></tr><tr><td><p>CPU</p></td><td><p>2 x Intel Xeon E5-2630 v4</p></td><td><p>1 x Intel Xeon Gold 6210U</p></td></tr><tr><td><p>Total Core / Thread Count</p></td><td><p>20C / 40T</p></td><td><p>20C / 40T</p></td></tr><tr><td><p>Base / Turbo Frequency</p></td><td><p>2.2 / 3.1 GHz</p></td><td><p>2.5 / 3.9 GHz</p></td></tr><tr><td><p>Memory</p></td><td><p>8 x 32GB DDR4-2400</p></td><td><p>8 x 32GB DDR4-2933</p></td></tr><tr><td><p>Storage</p></td><td><p>12 x 10TB 7200 RPM 3.5” SATA</p></td><td><p>12 x 10TB 7200 RPM 3.5” SATA</p></td></tr><tr><td><p>Network</p></td><td><p>Mellanox CX4 Lx 2 x 25GbE</p></td><td><p>Mellanox CX4 Lx 2 x 25GbE</p></td></tr></table><p><b>CPU Changes</b></p><p>For ClickHouse, we use a 1U chassis with 12 x 10TB 3.5” hard drives. At the time we were designing Core Storage 2020 our server vendor did not yet have an AMD version of this chassis, so we remained on Intel. However, we moved Core Storage 2020 to a single 20 core / 40 thread Xeon processor, rather than the previous generation’s dual-socket 10 core / 20 thread processors. By moving to the single-socket Xeon 6210U processor, we were able to keep the same core count, but gained 17% higher base frequency and 26% higher max turbo frequency. Meanwhile, the total CPU thermal design profile (TDP), which is an approximation of the maximum power the CPU can draw, went down from 165W to 150W.</p><p>On a dual-socket server, remote memory accesses, which are memory accesses by a process on socket 0 to memory attached to socket 1, incur a latency penalty, as seen in this table:</p><table><tr><td><p>
</p></td><td><p><b>Previous Generation</b></p></td><td><p><b>Core Storage 2020</b></p></td></tr><tr><td><p>Memory latency, socket 0 to socket 0</p></td><td><p>81.3 ns</p></td><td><p>86.9 ns</p></td></tr><tr><td><p>Memory latency, socket 0 to socket 1</p></td><td><p>142.6 ns</p></td><td><p>N/A</p></td></tr></table><p>An additional advantage of having a CPU with all 20 cores on the same socket is the elimination of these remote memory accesses, which take 76% longer than local memory accesses.</p>
    <div>
      <h3>Memory Changes</h3>
      <a href="#memory-changes">
        
      </a>
    </div>
    <p>The memory in the Core Storage 2020 host is rated for operation at 2933 MHz; however, in the 8 x 32GB configuration we need on these hosts, the Intel Xeon 6210U processor clocks them at 2666 MH. Compared to the previous generation, this gives us a 13% boost in memory speed. While we would get a slightly higher clock speed with a balanced, 6 DIMMs configuration, we determined that we are willing to sacrifice the slightly higher clock speed in order to have the additional RAM capacity provided by the 8 x 32GB configuration.</p>
    <div>
      <h3>Storage Changes</h3>
      <a href="#storage-changes">
        
      </a>
    </div>
    <p>Data capacity stayed the same, with 12 x 10TB SATA drives in RAID 0 configuration for best  throughput. Unlike the previous generation, the drives in the Core Storage 2020 host are helium filled. Helium produces less drag than air, resulting in potentially lower latency.</p>
    <div>
      <h3>Core Storage 2020 Synthetic benchmarking</h3>
      <a href="#core-storage-2020-synthetic-benchmarking">
        
      </a>
    </div>
    <p>We performed synthetic four corners benchmarking: IOPS measurements of random reads and writes using 4k block size, and bandwidth measurements of sequential reads and writes using 128k block size. We used the fio tool to see what improvements we would get in a lab environment. The results show a 10% latency improvement and 11% IOPS improvement in random read performance. Random write testing shows 38% lower latency and 60% higher IOPS. Write throughput is improved by 23%, and read throughput is improved by a whopping 90%.</p><p></p><table><tr><td><p>
</p></td><td><p><b>Previous Generation</b></p></td><td><p><b>Core Storage 2020</b></p></td><td><p><b>% Improvement</b></p></td></tr><tr><td><p>4k Random Reads (IOPS)</p></td><td><p>3,384</p></td><td><p>3,758</p></td><td><p>11.0%</p></td></tr><tr><td><p>4k Random Read Mean Latency (ms, lower is better)</p></td><td><p>75.4</p></td><td><p>67.8</p></td><td><p>10.1% lower</p></td></tr><tr><td><p>4k Random Writes (IOPS)</p></td><td><p>4,009</p></td><td><p>6,397</p></td><td><p>59.6%</p></td></tr><tr><td><p>4k Random Write Mean Latency (ms, lower is better)</p></td><td><p>63.5</p></td><td><p>39.7</p></td><td><p>37.5% lower</p></td></tr><tr><td><p>128k Sequential Reads (MB/s)</p></td><td><p>1,155</p></td><td><p>2,195</p></td><td><p>90.0%</p></td></tr><tr><td><p>128k Sequential Writes (MB/s)</p></td><td><p>1,265</p></td><td><p>1,558</p></td><td><p>23.2%</p></td></tr></table>
    <div>
      <h3>CPU frequencies</h3>
      <a href="#cpu-frequencies">
        
      </a>
    </div>
    <p>The higher base and turbo frequencies of the Core Storage 2020 host’s Xeon 6210U processor allowed that processor to achieve higher average frequencies while running our production ClickHouse workload. A recent snapshot of two production hosts showed the Core Storage 2020 host being able to sustain an average of 31% higher CPU frequency while running ClickHouse.</p><table><tr><td><p>
</p></td><td><p><b>Previous generation (average core frequency)</b></p></td><td><p><b>Core Storage 2020 (average core frequency)</b></p></td><td><p><b>% improvement</b></p></td></tr><tr><td><p>Mean Core Frequency</p></td><td><p>2441 MHz</p></td><td><p>3199 MHz</p></td><td><p>31%</p></td></tr></table>
    <div>
      <h3>Core Storage 2020 Production benchmarking</h3>
      <a href="#core-storage-2020-production-benchmarking">
        
      </a>
    </div>
    <p>Our ClickHouse database hosts are continually performing merge operations to optimize the database data structures. Each individual merge operation takes just a few seconds on average, but since they’re constantly running, they can consume significant resources on the host. We sampled the average merge time every five minutes over seven days, and then sampled the data to find the average, minimum, and maximum merge times reported by a Compute 2020 host and by a previous generation host. Results are summarized below.</p>
    <div>
      <h3>ClickHouse merge operation performance improvement</h3>
      <a href="#clickhouse-merge-operation-performance-improvement">
        
      </a>
    </div>
    <table><tr><td><p><b>Time</b></p></td><td><p><b>Previous generation</b></p></td><td><p><b>Core Storage 2020</b></p></td><td><p><b>% improvement</b></p></td></tr><tr><td><p>Mean time to merge</p></td><td><p>1.83</p></td><td><p>1.15</p></td><td><p>37% lower</p></td></tr><tr><td><p>Maximum merge time</p></td><td><p>3.51</p></td><td><p>2.35</p></td><td><p>33% lower</p></td></tr><tr><td><p>Minimum merge time</p></td><td><p>0.68</p></td><td><p>0.32</p></td><td><p>53% lower</p></td></tr></table><p>Our lab-measured CPU frequency and storage performance improvements on Core Storage 2020 have translated into significantly reduced times to perform this database operation.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>With our Core 2020 servers, we were able to realize significant performance improvements, both in synthetic benchmarking outside production and in the production workloads we tested. This will allow Cloudflare to run the same workloads on fewer servers, saving CapEx costs and data center rack space. The similarity of the configuration of the Kubernetes and Kafka hosts should help with fleet management and spare parts management. For our next redesign, we will try to further converge the designs on which we run the major Core workloads to further improve efficiency.</p><p>Special thanks to Will Buckner and Chris Snook for their help in the development of these servers, and to Tim Bart for validating CSAM Scanning Tool’s performance on Compute.</p> ]]></content:encoded>
            <category><![CDATA[Hardware]]></category>
            <category><![CDATA[Kafka]]></category>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[ClickHouse]]></category>
            <category><![CDATA[Gen X]]></category>
            <guid isPermaLink="false">4fzfrcZ8XekQ1ykkMxltp1</guid>
            <dc:creator>Brian Bassett</dc:creator>
        </item>
        <item>
            <title><![CDATA[Automated Origin CA for Kubernetes]]></title>
            <link>https://blog.cloudflare.com/automated-origin-ca-for-kubernetes/</link>
            <pubDate>Fri, 13 Nov 2020 12:00:00 GMT</pubDate>
            <description><![CDATA[ Today we're releasing origin-ca-issuer, an extension to cert-manager integrating with Cloudflare Origin CA to easily create and renew certificates for your account's domains. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>In 2016, we launched the <a href="/cloudflare-ca-encryption-origin/">Cloudflare Origin CA</a>, a certificate authority optimized for making it easy to secure the connection between Cloudflare and an origin server. Running our own CA has allowed us to support fast issuance and renewal, simple and effective revocation, and wildcard certificates for our users.</p><p>Out of the box, managing <a href="https://www.cloudflare.com/application-services/products/ssl/">TLS certificates</a> and keys within Kubernetes can be challenging and error prone. The secret resources have to be constructed correctly, as components expect secrets with specific fields. Some forms of domain verification require manually rotating secrets to pass. Once you're successful, don't forget to renew before the certificate expires!</p><p><a href="https://cert-manager.io/">cert-manager</a> is a project to fill this operational gap, providing Kubernetes resources that <a href="https://www.cloudflare.com/application-services/solutions/certificate-lifecycle-management/">manage the lifecycle of a certificate.</a> Today we're releasing <a href="https://github.com/cloudflare/origin-ca-issuer">origin-ca-issuer</a>, an extension to cert-manager integrating with Cloudflare Origin CA to easily create and renew certificates for your account's domains.</p>
    <div>
      <h2>Origin CA Integration</h2>
      <a href="#origin-ca-integration">
        
      </a>
    </div>
    
    <div>
      <h3>Creating an Issuer</h3>
      <a href="#creating-an-issuer">
        
      </a>
    </div>
    <p>After installing cert-manager and origin-ca-issuer, you can create an OriginIssuer resource. This resource creates a binding between cert-manager and the Cloudflare API for an account. Different issuers may be connected to different Cloudflare accounts in the same Kubernetes cluster.</p>
            <pre><code>apiVersion: cert-manager.k8s.cloudflare.com/v1
kind: OriginIssuer
metadata:
  name: prod-issuer
  namespace: default
spec:
  signatureType: OriginECC
  auth:
    serviceKeyRef:
      name: service-key
      key: key
      ```</code></pre>
            <p>This creates a new OriginIssuer named "prod-issuer" that issues certificates using ECDSA signatures, and the secret "service-key" in the same namespace is used to authenticate to the Cloudflare API.</p>
    <div>
      <h3>Signing an Origin CA Certificate</h3>
      <a href="#signing-an-origin-ca-certificate">
        
      </a>
    </div>
    <p>After creating an OriginIssuer, we can now create a Certificate with cert-manager. This defines the domains, including wildcards, that the certificate should be issued for, how long the certificate should be valid, and when cert-manager should renew the certificate.</p>
            <pre><code>apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com
  namespace: default
spec:
  # The secret name where cert-manager
  # should store the signed certificate.
  secretName: example-com-tls
  dnsNames:
    - example.com
  # Duration of the certificate.
  duration: 168h
  # Renew a day before the certificate expiration.
  renewBefore: 24h
  # Reference the Origin CA Issuer you created above,
  # which must be in the same namespace.
  issuerRef:
    group: cert-manager.k8s.cloudflare.com
    kind: OriginIssuer
    name: prod-issuer
</code></pre>
            <p>Once created, cert-manager begins managing the lifecycle of this certificate, including creating the key material, crafting a certificate signature request (CSR), and constructing a certificate request that will be processed by the origin-ca-issuer.</p><p>When signed by the Cloudflare API, the certificate will be made available, along with the private key, in the Kubernetes secret specified within the secretName field. You'll be able to use this certificate on servers proxied behind Cloudflare.</p>
    <div>
      <h3>Extra: Ingress Support</h3>
      <a href="#extra-ingress-support">
        
      </a>
    </div>
    <p>If you're using an Ingress controller, you can use cert-manager's <a href="https://cert-manager.io/docs/usage/ingress/">Ingress support</a> to automatically manage Certificate resources based on your Ingress resource.</p>
            <pre><code>apiVersion: networking/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/issuer: prod-issuer
    cert-manager.io/issuer-kind: OriginIssuer
    cert-manager.io/issuer-group: cert-manager.k8s.cloudflare.com
  name: example
  namespace: default
spec:
  rules:
    - host: example.com
      http:
        paths:
          - backend:
              serviceName: examplesvc
              servicePort: 80
            path: /
  tls:
    # specifying a host in the TLS section will tell cert-manager 
    # what DNS SANs should be on the created certificate.
    - hosts:
        - example.com
      # cert-manager will create this secret
      secretName: example-tls
</code></pre>
            
    <div>
      <h2>Building an External cert-manager Issuer</h2>
      <a href="#building-an-external-cert-manager-issuer">
        
      </a>
    </div>
    <p>An external cert-manager issuer is a specialized Kubernetes controller. There's no direct communication between cert-manager and external issuers at all; this means that you can use any existing tools and best practices for developing controllers to develop an external issuer.</p><p>We've decided to use the excellent <a href="https://github.com/kubernetes-sigs/controller-runtime">controller-runtime</a> project to build origin-ca-issuer, running two reconciliation controllers.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/MASdtZ7CMaW8JO3SCgb5t/4956cb2ac00a920a9901605adf1e9f68/image1-4.png" />
            
            </figure>
    <div>
      <h3>OriginIssuer Controller</h3>
      <a href="#originissuer-controller">
        
      </a>
    </div>
    <p>The OriginIssuer controller watches for creation and modification of OriginIssuer custom resources. The controllers create a Cloudflare API client using the details and credentials referenced. This client API instance will later be used to sign certificates through the API. The controller will periodically retry to create an API client; once it is successful, it updates the OriginIssuer's status to be ready.</p>
    <div>
      <h3>CertificateRequest Controller</h3>
      <a href="#certificaterequest-controller">
        
      </a>
    </div>
    <p>The CertificateRequest controller watches for the creation and modification of cert-manager's CertificateRequest resources. These resources are created automatically by cert-manager as needed during a certificate's lifecycle.</p><p>The controller looks for Certificate Requests that reference a known OriginIssuer, this reference is copied by cert-manager from the origin Certificate resource, and ignores all resources that do not match. The controller then verifies the OriginIssuer is in the ready state, before transforming the certificate request into an API request using the previously created clients.</p><p>On a successful response, the signed certificate is added to the certificate request, and which cert-manager will use to create or update the secret resource. On an unsuccessful request, the controller will periodically retry.</p>
    <div>
      <h2>Learn More</h2>
      <a href="#learn-more">
        
      </a>
    </div>
    <p>Up-to-date documentation and complete installation instructions can be found in our <a href="https://github.com/cloudflare/origin-ca-issuer">GitHub repository</a>. Feedback and contributions are greatly appreciated. If you're interested in Kubernetes at Cloudflare, including building controllers like these, <a href="https://www.cloudflare.com/careers/jobs/">we're hiring</a>.</p> ]]></content:encoded>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[SSL]]></category>
            <category><![CDATA[TLS]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[API]]></category>
            <guid isPermaLink="false">7akG4xBepli4ZP133CXBCf</guid>
            <dc:creator>Terin Stock</dc:creator>
        </item>
        <item>
            <title><![CDATA[Secondary DNS - Deep Dive]]></title>
            <link>https://blog.cloudflare.com/secondary-dns-deep-dive/</link>
            <pubDate>Tue, 15 Sep 2020 11:00:00 GMT</pubDate>
            <description><![CDATA[ The goal of Cloudflare operated Secondary DNS is to allow our customers with custom DNS solutions, be it on-premise or some other DNS provider, to be able to take advantage of Cloudflare's DNS performance and more recently, through Secondary Override, our proxying and security capabilities too. ]]></description>
            <content:encoded><![CDATA[ 
    <div>
      <h2>How Does Secondary DNS Work?</h2>
      <a href="#how-does-secondary-dns-work">
        
      </a>
    </div>
    <p>If you already understand how Secondary DNS works, please feel free to skip this section. It does not provide any Cloudflare-specific information.</p><p>Secondary DNS has many use cases across the Internet; however, traditionally, it was used as a synchronized backup for when the primary DNS server was unable to respond to queries. A more modern approach involves focusing on redundancy across many different nameservers, which in many cases broadcast the same anycasted IP address.</p><p>Secondary DNS involves the unidirectional transfer of DNS zones from the primary to the Secondary DNS server(s). One primary can have any number of Secondary DNS servers that it must communicate with in order to keep track of any zone updates. A zone update is considered a change in the contents of a  zone, which ultimately leads to a Start of Authority (SOA) serial number increase. The zone’s SOA serial is one of the key elements of Secondary DNS; it is how primary and secondary servers synchronize zones. Below is an example of what an SOA record might look like during a dig query.</p>
            <pre><code>example.com	3600	IN	SOA	ashley.ns.cloudflare.com. dns.cloudflare.com. 
2034097105  // Serial
10000 // Refresh
2400 // Retry
604800 // Expire
3600 // Minimum TTL</code></pre>
            <p>Each of the numbers is used in the following way:</p><ol><li><p>Serial - Used to keep track of the status of the zone, must be incremented at every change.</p></li><li><p>Refresh - The maximum number of seconds that can elapse before a Secondary DNS server must check for a SOA serial change.</p></li><li><p>Retry - The maximum number of seconds that can elapse before a Secondary DNS server must check for a SOA serial change, after previously failing to contact the primary.</p></li><li><p>Expire - The maximum number of seconds that a Secondary DNS server can serve stale information, in the event the primary cannot be contacted.</p></li><li><p>Minimum TTL - Per <a href="https://tools.ietf.org/html/rfc2308">RFC 2308</a>, the number of seconds that a DNS negative response should be cached for.</p></li></ol><p>Using the above information, the Secondary DNS server stores an SOA record for each of the zones it is tracking. When the serial increases, it knows that the zone must have changed, and that a zone transfer must be initiated.  </p>
    <div>
      <h2>Serial Tracking</h2>
      <a href="#serial-tracking">
        
      </a>
    </div>
    <p>Serial increases can be detected in the following ways:</p><ol><li><p>The fastest way for the Secondary DNS server to keep track of a serial change is to have the primary server NOTIFY them any time a zone has changed using the DNS protocol as specified in <a href="https://www.ietf.org/rfc/rfc1996.txt">RFC 1996</a>, Secondary DNS servers will instantly be able to initiate a zone transfer.</p></li><li><p>Another way is for the Secondary DNS server to simply poll the primary every “Refresh” seconds. This isn’t as fast as the NOTIFY approach, but it is a good fallback in case the notifies have failed.</p></li></ol><p>One of the issues with the basic NOTIFY protocol is that anyone on the Internet could potentially notify the Secondary DNS server of a zone update. If an initial SOA query is not performed by the Secondary DNS server before initiating a zone transfer, this is an easy way to perform an <a href="https://www.cloudflare.com/learning/ddos/dns-amplification-ddos-attack/">amplification attack</a>. There is two common ways to prevent anyone on the Internet from being able to NOTIFY Secondary DNS servers:</p><ol><li><p>Using transaction signatures (TSIG) as per <a href="https://tools.ietf.org/html/rfc2845">RFC 2845</a>. These are to be placed as the last record in the extra records section of the DNS message. Usually the number of extra records (or ARCOUNT) should be no more than two in this case.</p></li><li><p>Using IP based access control lists (ACL). This increases security but also prevents flexibility in server location and IP address allocation.</p></li></ol><p>Generally NOTIFY messages are sent over UDP, however TCP can be used in the event the primary server has reason to believe that TCP is necessary (i.e. firewall issues).</p>
    <div>
      <h2>Zone Transfers</h2>
      <a href="#zone-transfers">
        
      </a>
    </div>
    <p>In addition to serial tracking, it is important to ensure that a standard protocol is used between primary and Secondary DNS server(s), to efficiently transfer the zone. DNS zone transfer protocols do not attempt to solve the confidentiality, authentication and integrity triad (CIA); however, the use of TSIG on top of the basic zone transfer protocols can provide integrity and authentication. As a result of <a href="https://www.cloudflare.com/learning/dns/what-is-dns/">DNS</a> being a public protocol, confidentiality during the zone transfer process is generally not a concern.</p>
    <div>
      <h3>Authoritative Zone Transfer (AXFR)</h3>
      <a href="#authoritative-zone-transfer-axfr">
        
      </a>
    </div>
    <p>AXFR is the original zone transfer protocol that was specified in <a href="https://tools.ietf.org/html/rfc1034">RFC 1034</a> and <a href="https://tools.ietf.org/html/rfc1035">RFC 1035</a> and later further explained in <a href="https://tools.ietf.org/html/rfc5936">RFC 5936</a>. AXFR is done over a TCP connection because a reliable protocol is needed to ensure packets are not lost during the transfer. Using this protocol, the primary DNS server will transfer all of the zone contents to the Secondary DNS server, in one connection, regardless of the serial number. AXFR is recommended to be used for the first zone transfer, when none of the records are propagated, and IXFR is recommended after that.</p>
    <div>
      <h3>Incremental Zone Transfer (IXFR)</h3>
      <a href="#incremental-zone-transfer-ixfr">
        
      </a>
    </div>
    <p>IXFR is the more sophisticated zone transfer protocol that was specified in <a href="https://tools.ietf.org/html/rfc1995">RFC 1995</a>. Unlike the AXFR protocol, during an IXFR, the primary server will only send the secondary server the records that have changed since its current version of the zone (based on the serial number). This means that when a Secondary DNS server wants to initiate an IXFR, it sends its current serial number to the primary DNS server. The primary DNS server will then format its response based on previous versions of changes made to the zone. IXFR messages must obey the following pattern:</p><ol><li><p><b><i>Current latest SOA</i></b></p></li><li><p><b><i>Secondary server current SOA</i></b></p></li><li><p><b><i>DNS record deletions</i></b></p></li><li><p><b><i>Secondary server current SOA + changes</i></b></p></li><li><p><b><i>DNS record additions</i></b></p></li><li><p><b><i>Current latest SOA</i></b></p></li></ol><p>Steps 2,3,4,5,6 can be repeated any number of times, as each of those represents one change set of deletions and additions, ultimately leading to a new serial.</p><p>IXFR can be done over UDP or TCP, but again TCP is generally recommended to avoid packet loss.</p>
    <div>
      <h2>How Does Secondary DNS Work at Cloudflare?</h2>
      <a href="#how-does-secondary-dns-work-at-cloudflare">
        
      </a>
    </div>
    <p>The DNS team loves microservice architecture! When we initially implemented Secondary DNS at Cloudflare, it was done using <a href="https://mesosphere.github.io/marathon/">Mesos Marathon</a>. This allowed us to separate each of our services into several different marathon apps, individually scaling apps as needed. All of these services live in our core data centers. The following services were created:</p><ol><li><p>Zone Transferer - responsible for attempting IXFR, followed by AXFR if IXFR fails.</p></li><li><p>Zone Transfer Scheduler - responsible for periodically checking zone SOA serials for changes.</p></li><li><p>Rest API - responsible for registering new zones and primary nameservers.</p></li></ol><p>In addition to the marathon apps, we also had an app external to the cluster:</p><ol><li><p>Notify Listener - responsible for listening for notifies from primary servers and telling the Zone Transferer to initiate an AXFR/IXFR.</p></li></ol><p>Each of these microservices communicates with the others through <a href="https://kafka.apache.org/">Kafka</a>.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6cS9HY6kpiqdrdyD3tqQjI/55d2a781d351cc854f416dd9b1e7d4f3/image8-1.png" />
            
            </figure><p>Figure 1: Secondary DNS Microservice Architecture‌‌</p><p>Once the zone transferer completes the AXFR/IXFR, it then passes the zone through to our zone builder, and finally gets pushed out to our edge at each of our <a href="https://www.cloudflare.com/network/">200 locations.</a></p><p>Although this current architecture worked great in the beginning, it left us open to many vulnerabilities and scalability issues down the road. As our Secondary DNS product became more popular, it was important that we proactively scaled and reduced the technical debt as much as possible. As with many companies in the industry, Cloudflare has recently migrated all of our core data center services to <a href="https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/">Kubernetes</a>, moving away from individually managed apps and Marathon clusters.</p><p>What this meant for Secondary DNS is that all of our Marathon-based services, as well as our NOTIFY Listener, had to be migrated to Kubernetes. Although this long migration ended up paying off, many difficult challenges arose along the way that required us to come up with unique solutions in order to have a seamless, zero downtime migration.</p>
    <div>
      <h2>Challenges When Migrating to Kubernetes</h2>
      <a href="#challenges-when-migrating-to-kubernetes">
        
      </a>
    </div>
    <p>Although the entire DNS team agreed that kubernetes was the way forward for Secondary DNS, it also introduced several challenges. These challenges arose from a need to properly scale up across many distributed locations while also protecting each of our individual data centers. Since our core does not rely on anycast to automatically distribute requests, as we introduce more customers, it opens us up to denial-of-service attacks.</p><p>The two main issues we ran into during the migration were:</p><ol><li><p>How do we create a distributed and reliable system that makes use of kubernetes principles while also making sure our customers know which IPs we will be communicating from?</p></li><li><p>When opening up a public-facing UDP socket to the Internet, how do we protect ourselves while also preventing unnecessary spam towards primary nameservers?.</p></li></ol>
    <div>
      <h3>Issue 1:</h3>
      <a href="#issue-1">
        
      </a>
    </div>
    <p>As was previously mentioned, one form of protection in the Secondary DNS protocol is to only allow certain IPs to initiate zone transfers. There is a fine line between primary servers allow listing too many IPs and them having to frequently update their IP ACLs. We considered several solutions:</p><ol><li><p><a href="https://github.com/nirmata/kube-static-egress-ip">Open source k8s controllers</a></p></li><li><p>Altering <a href="https://en.wikipedia.org/wiki/Network_address_translation">Network Address Translation(NAT)</a> entries</p></li><li><p>Do not use k8s for zone transfers</p></li><li><p>Allowlist all Cloudflare IPs and dynamically update</p></li><li><p>Proxy egress traffic</p></li></ol><p>Ultimately we decided to proxy our egress traffic from k8s, to the DNS primary servers, using static proxy addresses. <a href="https://github.com/shadowsocks/shadowsocks-libev">Shadowsocks-libev</a> was chosen as the <a href="https://en.wikipedia.org/wiki/SOCKS">SOCKS5</a> implementation because it is fast, secure and known to scale. In addition, it can handle both UDP/TCP and IPv4/IPv6.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2JcJoxa44FoxHtMrWBXCP5/62805a1a8091ba1597f33614bece420b/image1-8.png" />
            
            </figure><p>Figure 2: Shadowsocks proxy Setup</p><p>The partnership of k8s and Shadowsocks combined with a large enough IP range brings many benefits:</p><ol><li><p>Horizontal scaling</p></li><li><p>Efficient load balancing</p></li><li><p>Primary server ACLs only need to be updated once</p></li><li><p>It allows us to make use of kubernetes for both the Zone Transferer and the Local ShadowSocks Proxy.</p></li><li><p>Shadowsocks proxy can be reused by many different Cloudflare services.</p></li></ol>
    <div>
      <h3>Issue 2:</h3>
      <a href="#issue-2">
        
      </a>
    </div>
    <p>The Notify Listener requires listening on static IPs for NOTIFY Messages coming from primary DNS servers. This is mostly a solved problem through the use of <a href="https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typeloadbalancer">k8s services of type loadbalancer</a>, however exposing this service directly to the Internet makes us uneasy because of its susceptibility to <a href="https://www.cloudflare.com/learning/ddos/dns-flood-ddos-attack/">attacks</a>. Fortunately <a href="https://www.cloudflare.com/ddos/">DDoS protection</a> is one of Cloudflare's strengths, which lead us to the likely solution of <a href="https://en.wikipedia.org/wiki/Eating_your_own_dog_food">dogfooding</a> one of our own products, <a href="https://www.cloudflare.com/products/cloudflare-spectrum/">Spectrum</a>.</p><p>Spectrum provides the following features to our service:</p><ol><li><p>Reverse proxy TCP/UDP traffic</p></li><li><p>Filter out Malicious traffic</p></li><li><p>Optimal routing from edge to core data centers</p></li><li><p><a href="https://www.cisco.com/c/dam/en_us/solutions/industries/docs/gov/IPV6at_a_glance_c45-625859.pdf">Dual Stack</a> technology</p></li></ol>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1S3Ha1uEWxV2bbrzSJY9y2/84a6c819cf54b3ade09d9e36ecad8ac2/image5-3.png" />
            
            </figure><p>Figure 3: Spectrum interaction with Notify Listener</p><p>Figure 3 shows two interesting attributes of the system:</p><ol><li><p><b>Spectrum &lt;-&gt; k8s IPv4 only:</b></p></li><li><p>This is because our custom k8s load balancer currently only supports IPv4; however, Spectrum has no issue terminating the IPv6 connection and establishing a new IPv4 connection.</p></li><li><p><b>Spectrum &lt;-&gt; k8s routing decisions based of L4 protocol</b>:</p></li><li><p>This is because k8s only supports one of TCP/UDP/SCTP per service of type load balancer. Once again, spectrum has no issues proxying this correctly.</p></li></ol><p>One of the problems with using a L4 proxy in between services is that source IP addresses get changed to the source IP address of the proxy (Spectrum in this case). Not knowing the source IP address means we have no idea who sent the NOTIFY message, opening us up to attack vectors. Fortunately, Spectrum’s <a href="https://developers.cloudflare.com/spectrum/getting-started/proxy-protocol/">proxy protocol</a> feature is capable of adding custom headers to TCP/UDP packets which contain source IP/Port information.</p><p>As we are using <a href="https://github.com/miekg/dns">miekg/dns</a> for our Notify Listener, adding proxy headers to the DNS NOTIFY messages would cause failures in validation at the DNS server level. Alternatively, we were able to implement custom <a href="https://github.com/miekg/dns/blob/master/server.go#L156-L162">read and write decorators</a> that do the following:</p><ol><li><p><b>Reader:</b> Extract source address information on inbound NOTIFY messages. Place extracted information into new DNS records located in the additional section of the message.</p></li><li><p><b>Writer:</b> Remove additional records from the DNS message on outbound NOTIFY replies. Generate a new reply using proxy protocol headers.</p></li></ol><p>There is no way to spoof these records, because the server only permits two extra records, one of which is the optional TSIG. Any other records will be overwritten.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5pg2MCnr4MUQapXAtQRLeK/365f1bdaec1c60c88ae408b5c0dea128/image4-6.png" />
            
            </figure><p>Figure 4: Proxying Records Between Notifier and Spectrum‌‌</p><p>This custom decorator approach abstracts the proxying away from the Notify Listener through the use of the DNS protocol.  </p><p>Although knowing the source IP will block a significant amount of bad traffic, since NOTIFY messages can use both UDP and TCP, it is prone to <a href="/the-root-cause-of-large-ddos-ip-spoofing/">IP spoofing</a>. To ensure that the primary servers do not get spammed, we have made the following additions to the Zone Transferer:</p><ol><li><p>Always ensure that the SOA has actually been updated before initiating a zone transfer.</p></li><li><p>Only allow at most one working transfer and one scheduled transfer per zone.</p></li></ol>
    <div>
      <h2>Additional Technical Challenges</h2>
      <a href="#additional-technical-challenges">
        
      </a>
    </div>
    
    <div>
      <h3>Zone Transferer Scheduling</h3>
      <a href="#zone-transferer-scheduling">
        
      </a>
    </div>
    <p>As shown in figure 1, there are several ways of sending Kafka messages to the Zone Transferer in order to initiate a zone transfer. There is no benefit in having a large backlog of zone transfers for the same zone. Once a zone has been transferred, assuming no more changes, it does not need to be transferred again. This means that we should only have at most one transfer ongoing, and one scheduled transfer at the same time, for any zone.</p><p>If we want to limit our number of scheduled messages to one per zone, this involves ignoring Kafka messages that get sent to the Zone Transferer. This is not as simple as ignoring specific messages in any random order. One of the benefits of Kafka is that it holds on to messages until the user actually decides to acknowledge them, by committing that messages offset. Since Kafka is just a queue of messages, it has no concept of order other than first in first out (FIFO). If a user is capable of reading from the Kafka topic concurrently, it is entirely possible that a message in the middle of the queue be committed before a message at the end of the queue.</p><p>Most of the time this isn’t an issue, because we know that one of the concurrent readers has read the message from the end of the queue and is processing it. There is one Kubernetes-related catch to this issue, though: pods are ephemeral. The kube master doesn’t care what your concurrent reader is doing, it will kill the pod and it’s up to your application to handle it.</p><p>Consider the following problem:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2LbPUGXSdf0uzNeI68U06Q/13295276ff697029d06c58b2e4a9df81/image2-2.png" />
            
            </figure><p>Figure 5: Kafka Partition‌‌</p><ol><li><p>Read offset 1. Start transferring zone 1.</p></li><li><p>Read offset 2. Start transferring zone 2.</p></li><li><p>Zone 2 transfer finishes. Commit offset 2, essentially also marking offset 1.</p></li><li><p>Restart pod.</p></li><li><p>Read offset 3 Start transferring zone 3.</p></li></ol><p>If these events happen, zone 1 will never be transferred. It is important that zones stay up to date with the primary servers, otherwise stale data will be served from the Secondary DNS server. The solution to this problem involves the use of a list to track which messages have been read and completely processed. In this case, when a zone transfer has finished, it does not necessarily mean that the kafka message should be immediately committed. The solution is as follows:</p><ol><li><p>Keep a list of Kafka messages, sorted based on offset.</p></li><li><p>If finished transfer, remove from list:</p></li><li><p>If the message is the oldest in the list, commit the messages offset.</p></li></ol>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1g6Ku6Is2fixeHyiTIoEwC/0bb4026298db401018aefc89b027a717/image9-2.png" />
            
            </figure><p>Figure 6: Kafka Algorithm to Solve Message Loss</p><p>This solution is essentially soft committing Kafka messages, until we can confidently say that all other messages have been acknowledged. It’s important to note that this only truly works in a distributed manner if the Kafka messages are keyed by zone id, this will ensure the same zone will always be processed by the same Kafka consumer.</p>
    <div>
      <h2>Life of a Secondary DNS Request</h2>
      <a href="#life-of-a-secondary-dns-request">
        
      </a>
    </div>
    <p>Although Cloudflare has a <a href="https://www.cloudflare.com/network/">large global network</a>, as shown above, the zone transferring process does not take place at each of the edge datacenter locations (which would surely overwhelm many primary servers), but rather in our core data centers. In this case, how do we propagate to our edge in seconds? After transferring the zone, there are a couple more steps that need to be taken before the change can be seen at the edge.</p><ol><li><p>Zone Builder - This interacts with the Zone Transferer to build the zone according to what Cloudflare edge understands. This then writes to <a href="/introducing-quicksilver-configuration-distribution-at-internet-scale/">Quicksilver</a>, our super fast, distributed KV store.</p></li><li><p>Authoritative Server - This reads from Quicksilver and serves the built zone.</p></li></ol>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3INig0h7w1oSks6FwirZls/c99581c1c0019f5fc5dd2a80884dc943/image3-6.png" />
            
            </figure><p>Figure 7: End to End Secondary DNS‌‌</p>
    <div>
      <h2>What About Performance?</h2>
      <a href="#what-about-performance">
        
      </a>
    </div>
    <p>At the time of writing this post, according to <a href="http://dnsperf.com">dnsperf.com</a>, Cloudflare leads in global performance for both <a href="https://www.dnsperf.com/">Authoritative</a> and <a href="https://www.dnsperf.com/#!dns-resolvers">Resolver</a> DNS. Here, Secondary DNS falls under the authoritative DNS category here. Let’s break down the performance of each of the different parts of the Secondary DNS pipeline, from the primary server updating its records, to them being present at the Cloudflare edge.</p><ol><li><p>Primary Server to Notify Listener - Our most accurate measurement is only precise to the second, but we know UDP/TCP communication is likely much faster than that.</p></li><li><p>NOTIFY to Zone Transferer - This is negligible</p></li><li><p>Zone Transferer to Primary Server - 99% of the time we see ~800ms as the average latency for a zone transfer.</p></li></ol>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/70uQkuuFtfY5UPVrza1ZpK/ccfa5d2a7c522369d34c8b0671056167/image7-2.png" />
            
            </figure><p>Figure 8: Zone XFR latency</p><p>4. Zone Transferer to Zone Builder - 99% of the time we see ~10ms to build a zone.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3cfNN2ilrDOES8QwG5ok4I/ab8e3bafc66ec05e55530d997f628a68/image11-1.png" />
            
            </figure><p>Figure 9: Zone Build time</p><p>5. Zone Builder to Quicksilver edge: 95% of the time we see less than 1s propagation.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1imVuigo0GB7gLug2ISqL7/2ad64a24445b9aa75094d78437710d16/image6-2.png" />
            
            </figure><p>Figure 10: Quicksilver propagation time</p><p>End to End latency: less than 5 seconds on average. Although we have several external probes running around the world to test propagation latencies, they lack precision due to their sleep intervals, location, provider and number of zones that need to run. The actual propagation latency is likely much lower than what is shown in figure 10. Each of the different colored dots is a separate data center location around the world.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5BjcchQWzBqyYbX6ksIYMj/0f167f037efdeaf5cece6a054b3095ab/image10.png" />
            
            </figure><p>Figure 11: End to End Latency</p><p>An additional test was performed manually to get a real world estimate, the test had the following attributes:</p><p>Primary server: NS1Number of records changed: 1Start test timer event: Change record on NS1Stop test timer event: Observe record change at Cloudflare edge using digRecorded timer value: 6 seconds</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>Cloudflare serves 15.8 trillion DNS queries per month, operating within 100ms of 99% of the Internet-connected population. The goal of Cloudflare operated Secondary DNS is to allow our customers with custom DNS solutions, be it on-premise or some other DNS provider, to be able to take advantage of Cloudflare's DNS performance and more recently, through <a href="/orange-clouding-with-secondary-dns/">Secondary Override</a>, our proxying and security capabilities too. Secondary DNS is currently available on the Enterprise plan, if you’d like to take advantage of it, please let your account team know. For additional documentation on Secondary DNS, please refer to our <a href="https://support.cloudflare.com/hc/en-us/articles/360001356152-How-do-I-setup-and-manage-Secondary-DNS-">support article</a>.</p> ]]></content:encoded>
            <category><![CDATA[DNS]]></category>
            <category><![CDATA[Growth]]></category>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <guid isPermaLink="false">2W2no7YfwWXkJXtgYtK4CU</guid>
            <dc:creator>Alex Fattouche</dc:creator>
        </item>
        <item>
            <title><![CDATA[Releasing kubectl support in Access]]></title>
            <link>https://blog.cloudflare.com/releasing-kubectl-support-in-access/</link>
            <pubDate>Mon, 27 Apr 2020 11:00:00 GMT</pubDate>
            <description><![CDATA[ Starting today, you can use Cloudflare Access and Argo Tunnel to securely manage your Kubernetes cluster with the kubectl command-line tool. SSO requirements and a zero-trust model to your Kubernetes management in under 30 minutes. ]]></description>
            <content:encoded><![CDATA[ <p>Starting today, you can use Cloudflare Access and Argo Tunnel to securely manage your Kubernetes cluster with the kubectl command-line tool.</p><p>We built this to address one of the edge cases that stopped all of Cloudflare, as well as some of our customers, from disabling the VPN. With this workflow, you can add SSO requirements and a zero-trust model to your Kubernetes management in under 30 minutes.</p><p>Once deployed, you can migrate to Cloudflare Access for controlling Kubernetes clusters without disrupting your current <code>kubectl</code> workflow, a lesson we learned the hard way from dogfooding here at Cloudflare.</p>
    <div>
      <h3>What is kubectl?</h3>
      <a href="#what-is-kubectl">
        
      </a>
    </div>
    <p>A Kubernetes <a href="https://kubernetes.io/docs/concepts/overview/components/">deployment consists</a> of a cluster that contains nodes, which run the containers, as well as a control plane that can be used to manage those nodes. Central to that control plane is the Kubernetes API server, which interacts with components like the scheduler and manager.</p><p><a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/">kubectl</a> is the Kubernetes command-line tool that developers can use to interact with that API server. Users run <code>kubectl</code> commands to perform actions like starting and stopping the nodes, or modifying other elements of the control plane.</p><p>In most deployments, users connect to a VPN that allows them to run commands against that API server by addressing it over the same local network. In that architecture, user traffic to run these commands must be backhauled through a physical or virtual VPN appliance. More concerning, in most cases the user connecting to the API server will also be able to connect to other addresses and ports in the private network where the cluster runs.</p>
    <div>
      <h3>How does Cloudflare Access apply?</h3>
      <a href="#how-does-cloudflare-access-apply">
        
      </a>
    </div>
    <p>Cloudflare Access can secure web applications as well as non-HTTP connections like <a href="https://www.cloudflare.com/learning/access-management/what-is-ssh/">SSH</a>, RDP, and the commands sent over <code>kubectl</code>. Access deploys Cloudflare’s network in front of all of these resources. Every time a request is made to one of these destinations, Cloudflare’s network checks for identity like a bouncer in front of each door.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/135CckjPYvwTnJPzEwqwjq/ecafea64a120ae117e7b63e192eb778b/image1-21.png" />
            
            </figure><p>If the request lacks identity, we send the user to your team’s SSO provider, like Okta, AzureAD, and G Suite, where the user can login. Once they login, they are redirected to Cloudflare where we check their identity against a list of users who are allowed to connect. If the user is permitted, we let their request reach the destination.</p><p>In most cases, those granular checks on every request would slow down the experience. However, Cloudflare Access completes the entire check in just a few milliseconds. The authentication flow relies on Cloudflare’s serverless product, <a href="https://workers.cloudflare.com/">Workers</a>, and runs in every one of our data centers in 200 cities around the world. With that distribution, we can improve performance for your applications while also authenticating every request.</p>
    <div>
      <h3>How does it work with kubectl?</h3>
      <a href="#how-does-it-work-with-kubectl">
        
      </a>
    </div>
    <p>To replace your VPN with Cloudflare Access for <code>kubectl</code>, you need to complete two steps:</p><ul><li><p>Connect your cluster to Cloudflare with Argo Tunnel</p></li><li><p>Connect from a client machine to that cluster with Argo Tunnel</p></li></ul>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/8oP2nLOQ22C1KHNxRH6AG/d5dbd3feecad56115db653a9250aa407/kubectl.png" />
            
            </figure>
    <div>
      <h3>Connecting the cluster to Cloudflare</h3>
      <a href="#connecting-the-cluster-to-cloudflare">
        
      </a>
    </div>
    <p>On the cluster side, Cloudflare Argo Tunnel connects those resources to our network by creating a secure tunnel with the Cloudflare daemon, <code>cloudflared</code>. As an administrator, you can run <code>cloudflared</code> in any space that can connect to the kubectl API server over TCP.</p><p>Once installed, an administrator authenticates the instance of <code>cloudflared</code> by logging in to a browser with their Cloudflare account and choosing a hostname to use. Once selected, Cloudflare will issue a certificate to <code>cloudflared</code> that can be used to create a subdomain for the cluster.</p><p>Next, an administrator starts the tunnel. In the example below, the <code>hostname</code> value can be any subdomain of the hostname selected in Cloudflare; the <code>url</code> value should be the API server for the cluster.</p>
            <pre><code>cloudflared tunnel --hostname cluster.site.com --url tcp://kubernetes.docker.internal:6443 --socks5=true </code></pre>
            <p>This should be run as a <code>systemd</code> process to ensure the tunnel reconnects if the resource restarts.</p>
    <div>
      <h3>Connecting as an end user</h3>
      <a href="#connecting-as-an-end-user">
        
      </a>
    </div>
    <p>End users do not need an agent or client application to connect to web applications secured by Cloudflare Access. They can authenticate to on-premise applications through a browser, without a VPN, like they would for SaaS tools. When we apply that same security model to non-HTTP protocols, we need to establish that secure connection from the client with an alternative to the web browser.</p><p>Unlike our SSH flow, end users cannot modify <code>kubeconfig</code> to proxy requests through <code>cloudflared</code>. <a href="https://github.com/kubernetes/kubernetes/pull/81443">Pull requests</a> have been submitted to add this functionality to <code>kubeconfig</code>, but in the meantime users can set an alias to serve a similar function.</p><p>First, users need <a href="https://developers.cloudflare.com/argo-tunnel/quickstart/">to download</a> the same <code>cloudflared</code> tool that administrators deploy on the cluster. Once downloaded, they will need to run a corresponding command to create a local SOCKS proxy. When the user runs the command, <code>cloudflared</code> will launch a browser window to prompt them to login with their SSO and check that they are allowed to reach this hostname.</p>
            <pre><code>$ cloudflared access tcp --hostname cluster.site.com url --127.0.0.1:1234</code></pre>
            <p>The proxy allows your local kubectl tool to connect to <code>cloudflared</code> via a SOCKS5 proxy, which helps avoid issues with TLS handshakes to the cluster itself. In this model, TLS verification can still be exchanged with the <code>kubectl</code> API server without disabling or modifying that flow for end users.</p><p>Users can then create an alias to save time when connecting. The example below aliases all of the steps required to connect in a single command. This can be added to the user’s bash profile so that it persists between restarts.</p>
            <pre><code>$ alias kubeone="env HTTPS_PROXY=socks5://127.0.0.1:1234 kubectl"</code></pre>
            
    <div>
      <h3>A (hard) lesson when dogfooding</h3>
      <a href="#a-hard-lesson-when-dogfooding">
        
      </a>
    </div>
    <p>When we build products at Cloudflare, we release them to our own organization first. The entire company becomes a feature’s first customer, and we ask them to submit feedback in a candid way.</p><p>Cloudflare Access began as a product we built <a href="/dogfooding-from-home/">to solve our own challenges</a> with security and connectivity. The product impacts every user in our team, so as we’ve grown, we’ve been able to gather more expansive feedback and catch more edge cases.</p><p>The <code>kubectl</code> release was no different. At Cloudflare, we have a team that manages our own Kubernetes deployments and we went to them to discuss the prototype. However, they had more than just some casual feedback and notes for us.</p><p>They told us to stop.</p><p>We had started down an implementation path that was technically sound and solved the use case, but did so in a way that engineers who spend all day working with pods and containers would find to be a real irritant. The flow required a small change in presenting certificates, which did not feel cumbersome when we tested it, but we do not use it all day. That grain of sand would cause real blisters as a new requirement in the workflow.</p><p>With their input, we stopped the release, and changed that step significantly. We worked through ideas, iterated with them, and made sure the Kubernetes team at Cloudflare felt this was not just good enough, but better.</p>
    <div>
      <h3>What’s next?</h3>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>Support for <code>kubectl</code> is available in the latest release of the <code>cloudflared</code> tool. You can begin using it today, on any plan. More <a href="https://developers.cloudflare.com/access/other-protocols/kubectl/">detailed instructions are available</a> to get started.</p><p>If you try it out, <a href="https://community.cloudflare.com/t/feedback-for-cloudflare-access-support-for-kubectl/168530">please send us your feedback</a>! We’re focused on improving the ease of use for this feature, and other non-HTTP workflows in Access, and need your input.</p><p>New to Cloudflare for Teams? You can use all of the Teams products for free through September, including Cloudflare Access and Argo Tunnel. You can learn more about the program, and request a dedicated onboarding session, <a href="https://teams.cloudflare.com/">here</a>.</p> ]]></content:encoded>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[Cloudflare Access]]></category>
            <category><![CDATA[Zero Trust]]></category>
            <category><![CDATA[IETF]]></category>
            <guid isPermaLink="false">Q0Qzl6N8Ct6pqsN9MMbUE</guid>
            <dc:creator>Sam Rhea</dc:creator>
        </item>
        <item>
            <title><![CDATA[How To Minikube + Cloudflare]]></title>
            <link>https://blog.cloudflare.com/minikube-cloudflare/</link>
            <pubDate>Sun, 08 Jul 2018 13:00:00 GMT</pubDate>
            <description><![CDATA[ A step-by-step guide for how to run production Minikube deployments using the Cloudflare Ingress Controller. ]]></description>
            <content:encoded><![CDATA[ <p><sub><i>The following is a guest blog post by </i></sub><a href="https://www.linkedin.com/in/nathanfranzen/"><sub><i>Nathan Franzen</i></sub></a><sub><i>, Software Engineer at </i></sub><a href="https://stackpoint.io/"><sub><i>StackPointCloud</i></sub></a><sub><i>. StackPointCloud is the creator of Stackpoint.io, the leading multi-cloud management platform for cloud native workloads. They are the developers of the </i></sub><a href="https://github.com/cloudflare/cloudflare-warp-ingress"><sub><i>Cloudflare Ingress Controller</i></sub></a><sub><i> for Kubernetes.</i></sub></p>
    <div>
      <h3>Deploying Applications on Minikube with Argo Tunnels</h3>
      <a href="#deploying-applications-on-minikube-with-argo-tunnels">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7lIbr7ys1slKmEegs1MUET/6062af04b716df41a8b014a1419adb06/minikube_plus_cloudflare.png" />
          </figure><blockquote><p>This article assumes basic knowledge of Kubernetes. If you're not familiar with Kubernetes, visit <a href="https://kubernetes.io/docs/tutorials/kubernetes-basics/">https://kubernetes.io/docs/tutorials/kubernetes-basics/</a> to learn the basics.</p></blockquote><p>Minikube is a tool which allows you to run a Kubernetes cluster locally. It’s not only a great way to experiment with Kubernetes, but also a great way to try out deploying services using a reverse tunnel.</p><p>At Cloudflare, we've created a product called <a href="https://www.cloudflare.com/products/argo-tunnel/">Argo Tunnel</a> which allows you to host services through a tunnel using Cloudflare as your edge. Tunnels provide a way to expose your services to the internet by creating a connection to Cloudflare's edge and routing your traffic over it. Since your service is creating its own outbound connection to the edge, you don’t have to open ports, configure a firewall, or even have a public IP address for your service. All traffic flows through Cloudflare, blocking attacks and intrusion attempts before they ever make it to you, completely securing your origin.</p><p>Deploying your service to more locations around the world is as simple as spinning up more containers. Anything which uses the Ingress Controller will receive your traffic, wherever the container is running in the world or on the Internet. Tunnels make it simpler to have robust security even while deploying across multiple regions or cloud providers.</p><p>Usually Minikube applications need to be ported over to a production Kubernetes setup to be deployed, but with Argo Tunnel, you can easily deploy a locally-running yet publicly-available Minikube instance making it a great way to try out both Kubernetes and Argo Tunnel. In this example, we’ll create a simple microservice that returns data when given a key, deploy it into Minikube, and start up the Argo Tunnel machinery to get it exposed to the Internet.</p>
    <div>
      <h3>Getting Started with an Application API</h3>
      <a href="#getting-started-with-an-application-api">
        
      </a>
    </div>
    <p>We'll start by by creating a web service in Python using <a href="http://flask.pocoo.org/">Flask</a>. We'll write a simple application to represent a small piece of an API in just a few lines of code. The complete application, secret_token.py is simply:</p>
            <pre><code>from flask import Flask, jsonify, abort  
  
app = Flask(__name__)

@app.route('/api/v1/token/&lt;key&gt;', methods=['GET'])  
def token(key):  
	test_data = {  
		"e8990ab9be26": "3OX9+p39QLIvE6+x/w=",  
		"b01323031589": "wBvlo9G7Wqxsb2P9YS=",  
	}  
	secret = test_data.get(key)  
	if secret is None:  
		abort(404)  
	return jsonify({"key": key, "token": secret})</code></pre>
            <p></p><p>This tiny service will simply respond to a GET request with some secret data, given a key.</p>
    <div>
      <h3>Using Docker</h3>
      <a href="#using-docker">
        
      </a>
    </div>
    <p>We’ll take the next step toward deployment and package our application into a portable Docker image with a Dockerfile:</p>
            <pre><code>FROM  python:alpine3.7  
RUN  pip install flask gunicorn
COPY  secret_token.py .
CMD  gunicorn -b 0.0.0.0:8000 secret_token:app</code></pre>
            <p></p><p>This will allow us to define a Docker image, the blueprint for the containers Minikube will build.</p>
    <div>
      <h4>Deploying into Minikube</h4>
      <a href="#deploying-into-minikube">
        
      </a>
    </div>
    <blockquote><p>If you don't have Minikube installed, install it here: <a href="https://kubernetes.io/docs/tasks/tools/install-minikube/">https://kubernetes.io/docs/tasks/tools/install-minikube/</a></p></blockquote><p>Usually, we would build the Docker image with our Docker daemon and push it to a repository where the cluster can access it. With Minikube, however, that’s a round-trip we don’t need. We can share the Minikube Docker daemon with the Docker build process and avoid pushing to a cloud repository:</p>
            <pre><code>$ eval $(minikube docker-env)  
$ docker build -t myrepo/secret_token .</code></pre>
            <p></p><p>The image is now present on the Minikube VM where Kubernetes is running.</p><p>In a production Kubernetes system, we might spend a good deal of time going over the details of the deployment and service manifests, but kubectl run provides a simple way to get the basic app up and running. We add the image-pull-policy flag to make sure that Kubernetes doesn’t first try to pull the image remotely from Docker Hub.</p>
            <pre><code>$ kubectl run token --image myrepo/secret_token --expose --port 8000 --image-pull-policy=IfNotPresent --replicas=3</code></pre>
            <p></p><p>We now have a Kubernetes deployment running with our 3 replicas of containers built from our image, and a service associated with it that exposes port <code>8000</code>. Save the two manifests locally into files:</p>
            <pre><code>kubectl get deployment token --export -o yaml &gt; deployment.yaml  
kubectl get svc token --export -o yaml &gt; service.yaml</code></pre>
            <p></p><p>We’ll be able to edit these files to make changes to our cluster configuration.</p><p>For local testing, let's change that service so that it exposes a NodePort -- this will proxy the service to a port on the Minikube VM. Replace the spec in the service.yaml file with:</p>
            <pre><code>spec:  
	ports:  
	-	nodePort:  32080  
		port:  8000  
		protocol:  TCP  
		targetPort:  8000  
	selector:  
		run:  token  
	sessionAffinity:  None  
	type:  NodePort</code></pre>
            <p></p><p>And apply the change to our cluster:</p>
            <pre><code>$ kubectl apply -f service.yaml</code></pre>
            <p></p><p>Now, we can test locally with <code>curl</code>, reaching the service via the NodePort on the Minikube VM:</p>
            <pre><code>$ minikube start
$ export MINIKUBE_IP=$(minikube ip)
$ curl http://$MINIKUBE_IP:32080/api/v1/token/b01323031589</code></pre>
            
    <div>
      <h3>Using Cloudflare’s Argo Tunnel</h3>
      <a href="#using-cloudflares-argo-tunnel">
        
      </a>
    </div>
    <p>The NodePort setup is fine for testing the application locally, but if we want to share this service with others or better simulate how it will work in the real world, we need to expose it to the internet. In most cases, this means running in a cloud environment and dealing with load balancer configuration or setting up an NGINX ingress controller and dealing with network rules and routing. The Cloudflare Argo Tunnel Ingress Controller allows us to route almost anything to a Cloudflare domain, including services running inside of Minikube.</p><p>In the Kubernetes cluster, an <code>ingress</code> is an object that describes how we want our service exposed on the internet and an <code>ingress-controller</code> is the process that actually exposes it. To install the Cloudflare Ingress Controller, you’ll need to have a Cloudflare domain and an Argo Tunnel certificate, configured with the <code>cloudflared</code> application.</p><p><code>kubectl run</code> was fine for quickly installing the test application, but for more complex installations, <a href="https://helm.sh/">helm</a> is a great tool, and is used to package the Cloudflare agent. Once you have the helm client installed, a simple <code>helm init</code> will configure Minikube to work with it. The chart for the ingress controller is found at the <a href="https://github.com/StackPointCloud/trusted-charts/tree/master/stable/cloudflare-warp-ingress&amp;sa=D&amp;ust=1529959945974000">trusted-charts public repository</a> and can be installed directly from there.</p>
    <div>
      <h4>Cloudflared Configuration</h4>
      <a href="#cloudflared-configuration">
        
      </a>
    </div>
    <p><code>Cloudflared</code> is the end of the tunnel that runs on your machine and proxies traffic to and from your origin server through the tunnel. If you don't have it installed already, the <code>cloudflared</code> application complete quickstart instructions can be found at <a href="https://www.google.com/url?q=https://developers.cloudflare.com/argo-tunnel/quickstart/quickstart/&amp;sa=D&amp;ust=1529959945975000">https://developers.cloudflare.com/argo-tunnel/quickstart/quickstart/</a></p>
    <div>
      <h4>Installing the Controller with Helm</h4>
      <a href="#installing-the-controller-with-helm">
        
      </a>
    </div>
    <p>Now we will run some commands that define the repository that holds our chart and override a few default values:</p>
            <pre><code>$ minikube start
$ export MINIKUBE_IP=$(minikube ip)
$ curl http://$MINIKUBE_IP:32080/api/v1/token/b01323031589</code></pre>
            <p>This installation configures two cloudflare-warp-ingress controller replicas so that any service we expose will get two separate tunnels to the Cloudflare edge, paired together in a single pool.</p>
    <div>
      <h4>Exposing Our Application with an Ingress</h4>
      <a href="#exposing-our-application-with-an-ingress">
        
      </a>
    </div>
    <p>We'll need to write an ingress definition. Create a file called <code>warp-controller.yaml</code>:</p>
            <pre><code>apiVersion:  extensions/v1beta1  
kind:  Ingress  
metadata:  
	annotations:  
		kubernetes.io/ingress.class:  argo-tunnel  
	name:  token  
	namespace:  default  
spec:  
	rules:  
	-	host:  token.anthopleura.net  
	http:  
	paths:  
	-	backend:  
			serviceName:  token  
			servicePort:  8000</code></pre>
            <p>And apply the definition:</p>
            <pre><code>$ kubectl apply -f service.yaml</code></pre>
            
    <div>
      <h4>Examining the deployment</h4>
      <a href="#examining-the-deployment">
        
      </a>
    </div>
    
            <pre><code>$ kubectl get pod</code></pre>
            <p>Should print:</p>
            <pre><code>NAME									READY 	STATUS	RESTARTS 	AGE

cloudflare-argo-ingress-6b886994b-52fsl 1/1 	Running 	   0 	34s

token-766cd8dd4c-bmksw 					1/1 	Running 	   0 	 2m

token-766cd8dd4c-l8gkw 					1/1 	Running 	   0 	 2m

token-766cd8dd4c-p2phg 					1/1 	Running 	   0 	 2m</code></pre>
            <p>The output shows the three <code>token</code> pods and the <code>cloudflare-warp-ingress</code> pod. Examine the logs from the argo pod to see the activity of the ingress controller:</p>
            <pre><code>$ kubectl logs cloudflare-argo-ingress-6b886994b-52fsl</code></pre>
            <p>The controller watches the cluster for creation of ingresses, services and pods.</p><p>The endpoint is live at <a href="https://token.anthopleura.net/api/v1/token/e8990ab9be26">https://token.anthopleura.net/api/v1/token/e8990ab9be26</a> returning</p>
            <pre><code>{  
	"key": "e8990ab9be26",  
	"token": "3OX9+p39QLIvE6+x/YK4DxWWCFi/D+c7g99c14oNB8g="  
}</code></pre>
            <p>Now this small piece of an api is available publicly on the internet for testing. Obviously you don’t want to serve public traffic into a minikube instance, but it’s certainly handy for sharing preliminary work across development teams.</p><p>The Cloudflare <a href="https://blog.cloudflare.com/minikube-cloudflare/%5Bhttps://dash.cloudflare.com/%5D(https://dash.cloudflare.com/&amp;sa=D&amp;ust=1529959945990000)">dashboard</a> under the analytics tab will show some general statistics about the requests to your zone.</p>
    <div>
      <h3>Routing and relationships</h3>
      <a href="#routing-and-relationships">
        
      </a>
    </div>
    <p>A quick sketch of the routing in the Kubernetes cluster and from the Cloudflare network:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1ZiftW67CC2PZM3UfQ25kn/f23446ae0782e79e316e667164505d0b/Screenshot_2024-08-06_at_5.33.57_PM.png" />
          </figure><p>The warp controller pods provide a way for Argo Tunnels to connect the pods containing your application to the internet through Cloudflare's edge.</p>
    <div>
      <h3>Going farther with Cloudflare Load Balancers</h3>
      <a href="#going-farther-with-cloudflare-load-balancers">
        
      </a>
    </div>
    <p>This demo exposes a service through a single Argo Tunnel. If your Cloudflare account is enabled with load balancing, you can route traffic through a load balancer and pool of tunnels instead, by adding the annotation<code>argo.cloudflare.com/lb-pool=token</code> to the ingress. For details of load balancer routing and weighting please refer to the Cloudflare <a href="https://developers.cloudflare.com/">docs</a>.</p><p>If you do use load balancing, then it is possible to run multiple instances of the ingress controller. When installing from the helm chart, set the value <code>replicaCount</code> to two or more and get multiple instances of the controller in the minikube cluster. The configuration will be useful for high availability in a single cluster. Load balancing can also be used to spread traffic across multiple clusters with different argo ingress controllers connecting to the same load balancing pool.</p><p>With two ingress controllers, the <a href="https://www.cloudflare.com/a/traffic/stackpoint.io&amp;sa=D&amp;ust=1529959945991000">Cloudflare UI</a> will show a pool named token.anthopleura.net with two origins with tunnel ids as origin addresses:</p> ]]></content:encoded>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Load Balancing]]></category>
            <category><![CDATA[API]]></category>
            <category><![CDATA[Serverless]]></category>
            <category><![CDATA[Cloudflare Tunnel]]></category>
            <guid isPermaLink="false">5dKfmN3YfXAbTEsYyLgdJG</guid>
            <dc:creator>Guest Author</dc:creator>
        </item>
        <item>
            <title><![CDATA[Copenhagen & London developers, join us for five events this May]]></title>
            <link>https://blog.cloudflare.com/copenhagen-london-developers/</link>
            <pubDate>Thu, 26 Apr 2018 05:32:00 GMT</pubDate>
            <description><![CDATA[ Are you based in Copenhagen or London? Drop by some talks we're hosting about the use of Go, Kubernetes, and Cloudflare’s Mobile SDK. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Photo by <a href="https://unsplash.com/@nickkarvounis?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Nick Karvounis</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></p><p>Are you based in Copenhagen or London? Drop by one or all of these five events.</p><p><a href="https://twitter.com/0xRLG">Ross Guarino</a> and <a href="https://twitter.com/terinjokes">Terin Stock</a>, both Systems Engineers at Cloudflare are traveling to Europe to lead Go and Kubernetes talks in Copenhagen. They'll then join <a href="https://twitter.com/IcyApril">Junade Ali</a> and lead talks on their use of Go, Kubernetes, and Cloudflare’s Mobile SDK at Cloudflare's London office.</p><p>My Developer Relations teammates and I are visiting these cities over the next two weeks to produce these events with Ross, Terin, and Junade. We’d love to meet you and invite you along.</p><p>Our trip will begin with two meetups and a conference talk in Copenhagen.</p>
    <div>
      <h3>Event #1 (Copenhagen): 6 Cloud Native Talks, 1 Evening: Special KubeCon + CloudNativeCon EU Meetup</h3>
      <a href="#event-1-copenhagen-6-cloud-native-talks-1-evening-special-kubecon-cloudnativecon-eu-meetup">
        
      </a>
    </div>
    
            <figure>
            <a href="https://www.meetup.com/GOTO-Nights-CPH/events/249895973/">
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3PkIfrgFfKT97ZlXTCkM8N/b29595b25228a344e0c1b66c880d004f/GOTO.jpeg.jpeg" />
            </a>
            </figure><p><b>Tuesday, 1 May</b>: 17:00-21:00</p><p><b>Location</b>: <a href="https://trifork.com/">Trifork Copenhagen</a> - <a href="https://www.google.com/maps/place/Borgergade+24b,+1300+K%C3%B8benhavn,+Denmark/@55.684785,12.5840548,17z/data=!3m1!4b1!4m5!3m4!1s0x46525318dfb3b89d:0x855a7fb57181604f!8m2!3d55.684785!4d12.5862435">Borgergade 24B, 1300 København K</a></p><p>How to extend your Kubernetes cluster</p><p>A brief introduction to controllers, webhooks and CRDs. Ross and Terin will talk about how Cloudflare’s internal platform builds on Kubernetes.</p><p><b>Speakers</b>: Ross Guarino and Terin Stock</p><p><a href="https://www.meetup.com/GOTO-Nights-CPH/events/249895973/">View Event Details &amp; Register Here »</a></p>
    <div>
      <h3>Event #2 (Copenhagen): Gopher Meetup At Falcon.io: Building Go With Bazel &amp; Internationalization in Go</h3>
      <a href="#event-2-copenhagen-gopher-meetup-at-falcon-io-building-go-with-bazel-internationalization-in-go">
        
      </a>
    </div>
    
            <figure>
            <a href="https://www.meetup.com/Go-Cph/events/249830850/">
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1YCo27Er0ncIR677a3eKFL/ae8b09422245f7c111d27f23607e16ff/Viking-Gopher.png" />
            </a>
            </figure><p><b>Wednesday, 2 May</b>: 18:00-21:00</p><p><b>Location</b>: <a href="https://www.falcon.io/">Falcon.io</a> - <a href="https://www.google.com/maps/place/H.+C.+Andersens+Blvd.+27,+1553+K%C3%B8benhavn,+Denmark/@55.674143,12.5629923,15z/data=!4m5!3m4!1s0x4652531251d1c86d:0xd1f236f0ffef562e!8m2!3d55.674143!4d12.571747">H.C. Andersen Blvd. 27, København</a></p><p>Talk 1: Building Go with Bazel</p><p>Fast and Reproducible go builds with Bazel. Learn how to remove Makefiles from your repositories.</p><p><b>Speaker</b>: Ross Guarino</p><p>Talk 2: Internationalization in Go</p><p>Explore making effective use of Go’s internationalization and localization packages and easily making your applications world-friendly.</p><p><b>Speaker</b>: Terin Stock</p><p><a href="https://www.meetup.com/Go-Cph/events/249830850/">View Event Details &amp; Register Here »</a></p>
    <div>
      <h3>Event #3 (Copenhagen): Controllers: Lambda Functions for Extending your Infrastructure at KubeCon + CloudNativeCon 2018</h3>
      <a href="#event-3-copenhagen-controllers-lambda-functions-for-extending-your-infrastructure-at-kubecon-cloudnativecon-2018">
        
      </a>
    </div>
    
            <figure>
            <a href="http://sched.co/DqwM">
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6DfqqwLIbsg89kEglVpmNr/5ee34593d6192c6cdb9bb3bac21da2e9/Screen-Shot-2018-04-25-at-2.41.41-PM.png" />
            </a>
            </figure><p><b>Friday, 4 May</b>: 14:45-15:20</p><p><b>Location</b>: <a href="https://events.linuxfoundation.org/events/kubecon-cloudnativecon-europe-2018/">KubeCon + CloudNativeCon 2018</a> - <a href="https://www.google.com/maps/place/Bella+Center/@55.6385357,12.5433961,13z/data=!3m1!5s0x465254a4eeec0777:0x55f95a7fe9ed3f83!4m13!1m5!2m4!1sBella+Center,+Center+Blvd.+5,+2300+K%C3%B8benhavn!5m2!5m1!1s2018-04-27!3m6!1s0x465254a363269c3d:0x61db300fc92fb898!5m1!1s2018-04-27!8m2!3d55.6375044!4d12.5785932">Bella Center, Center Blvd. 5, 2300 København</a></p><p>If you happen to be attending <a href="https://events.linuxfoundation.org/events/kubecon-cloudnativecon-europe-2018/">KubeCon + CloudNativeCon 2018</a>, check out Terin and Ross’s conference talk as well.</p><p>This session demonstrates how to leverage Kubernetes Controllers and Initializers as a framework for building transparent extensions of your Kubernetes cluster. Using a live coding exercise and demo, this presentation will showcase the possibilities of the basic programming paradigms the Kubernetes API server makes easy.</p><p><b>Speakers</b>: Ross Guarino and Terin Stock</p><p><a href="http://sched.co/DqwM">View Event Details &amp; Register Here »</a></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5NWrQuZUZgMIy7uJZv0OWr/4fe475faee0e83937f10542f86e6bb4c/photo-1508808402998-ec38e4bf0fd0" />
            
            </figure><p>Photo by <a href="https://unsplash.com/@photobuffs?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Paul Buffington</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></p><p>When <a href="https://events.linuxfoundation.org/events/kubecon-cloudnativecon-europe-2018/">KubeCon + CloudNativeCon 2018</a> concludes, we're all heading to the Cloudflare London office where we are hosting two more meetups.</p>
    <div>
      <h3>Event #4 (London): Kubernetes Controllers: Lambda Functions for Extending your Infrastructure</h3>
      <a href="#event-4-london-kubernetes-controllers-lambda-functions-for-extending-your-infrastructure">
        
      </a>
    </div>
    
            <figure>
            <a href="https://kubernetes-controlers.eventbrite.com">
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4RvxFZQ2Thh4N7J4zBOcSu/4c02fcfd43a2fd108b19fac3c0e5aab8/Cloudflare-London.jpg" />
            </a>
            </figure><p><b>Wednesday, 9 May</b>: 18:00-20:00</p><p><b>Location</b>: Cloudflare London - <a href="https://www.google.com/maps/place/25+Lavington+St,+London+SE1+0NZ,+UK/@51.5047963,-0.1024043,17z/data=!3m1!4b1!4m5!3m4!1s0x487604a8a2b9c4f1:0x1126c5560c56cc41!8m2!3d51.5047963!4d-0.1002156">25 Lavington St, Second floor | SE1 0NZ London</a></p><p>This session demonstrates how to leverage Kubernetes Controllers and Initializers as a framework for building transparent extensions of your Kubernetes cluster. Using a live coding exercise and demo, this presentation will showcase the possibilities of the basic programming paradigms the Kubernetes API server makes easy. As an SRE, learn to build custom integrations directly into the Kubernetes API that transparently enhance the developer experience.</p><p><b>Speakers</b>: Ross Guarino and Terin Stock</p><p><a href="https://kubernetes-controlers.eventbrite.com">View Event Details &amp; Register Here »</a></p>
    <div>
      <h3>Event #5 (London): Architecture for Network Failure, Developing for Mobile Performance</h3>
      <a href="#event-5-london-architecture-for-network-failure-developing-for-mobile-performance">
        
      </a>
    </div>
    
            <figure>
            <a href="https://mobilearchitectureandperformance.eventbrite.com">
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/atA3RsFZPPZbbHcZKMhZW/1c5d56415e2513375ef5e6db50344910/Screen-Shot-2018-04-25-at-8.59.10-AM.png" />
            </a>
            </figure><p><b>Thursday, 10 May</b>: 18:00-20:00</p><p><b>Location</b>: Cloudflare London - <a href="https://www.google.com/maps/place/25+Lavington+St,+London+SE1+0NZ,+UK/@51.5047963,-0.1024043,17z/data=!3m1!4b1!4m5!3m4!1s0x487604a8a2b9c4f1:0x1126c5560c56cc41!8m2!3d51.5047963!4d-0.1002156">25 Lavington St, Second floor | SE1 0NZ London</a></p><p>Whether you're building an <a href="https://www.cloudflare.com/ecommerce/">e-commerce app</a> or a new mobile game, chances are you'll be needing some network functionality at some point when building a mobile app. Network performance can vary dramatically between carriers, networks, and APIs, but far too often mobile apps are tested inconsistent conditions with the same decent network performance. Fortunately we can iterate on our apps by collecting real-life performance measurements from your users; however, unfortunately existing mobile app analytics platforms only provide visibility into in-app performance but have no knowledge about outgoing network call.</p><p>This talk will cover how you can easily collect vital performance data from your users at no cost and then use this data to improve your apps' reliability and experience, discussing the tips and tricks needed to <a href="https://www.cloudflare.com/solutions/ecommerce/optimization/">boost app performance</a>.</p><p><b>Speaker</b>: Junade Ali</p><p><a href="https://mobilearchitectureandperformance.eventbrite.com">View Event Details &amp; Register Here »</a></p>
    <div>
      <h3>More About the Speakers</h3>
      <a href="#more-about-the-speakers">
        
      </a>
    </div>
    <p><a href="https://twitter.com/0xRLG">Ross Guarino</a> is a Systems Engineer at Cloudflare in charge of the technical direction of the internal platform. He’s determined to improve the lives of developers building and maintaining everything from a simple function to complex globally distributed systems.</p><p><a href="https://twitter.com/terinjokes">Terin Stock</a> is a long-time engineer at Cloudflare, currently working on building an internal Kubernetes cluster. By night, he hacks on building new hardware projects. Terin is also a member of <a href="https://gulpjs.com/">gulp.js</a> core team and the author of the <a href="https://github.com/terinjokes/StickersStandard">Sticker Standard</a>.</p><p><a href="https://twitter.com/IcyApril">Junade Ali</a> is a software engineer who is specialised in computer security and software architecture. Currently, Junade works at Cloudflare as a polymath, and helps make the Internet more secure and faster; prior to this, he was a technical lead at some of the UK's leading digital agencies before moving into architecting software for mission-critical road-safety systems.</p><p>We'll hope to meet you soon!</p> ]]></content:encoded>
            <category><![CDATA[Events]]></category>
            <category><![CDATA[Community]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[United Kingdom]]></category>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[Go]]></category>
            <category><![CDATA[Mobile SDK]]></category>
            <category><![CDATA[Cloudflare Meetups]]></category>
            <category><![CDATA[MeetUp]]></category>
            <guid isPermaLink="false">4KmQqQHsaL4hmb1fHLo2VX</guid>
            <dc:creator>Andrew Fitch</dc:creator>
        </item>
        <item>
            <title><![CDATA[Creating a single pane of glass for your multi-cloud Kubernetes workloads with Cloudflare]]></title>
            <link>https://blog.cloudflare.com/creating-a-single-pane-of-glass-for-your-multi-cloud-kubernetes-workloads-with-cloudflare/</link>
            <pubDate>Fri, 23 Feb 2018 17:00:00 GMT</pubDate>
            <description><![CDATA[ One of the great things about container technology is that it delivers the same experience and functionality across different platforms. This frees you as a developer from having to rewrite or update your application to deploy it on a new cloud provider. ]]></description>
            <content:encoded><![CDATA[ <p><i>(This is a crosspost of a blog post </i><a href="https://cloudplatform.googleblog.com/2018/02/creating-a-single-pane-of-glass-for-your-multi-cloud-Kubernetes-workloads-with-Cloudflare.html"><i>originally published</i></a><i> on Google Cloud blog)</i></p><p>One of the great things about container technology is that it delivers the same experience and functionality across different platforms. This frees you as a developer from having to rewrite or update your application to deploy it on a new cloud provider—or lets you run it across multiple cloud providers. With a containerized application running on multiple clouds, you can avoid lock-in, run your application on the cloud for which it’s best suited, and lower your overall costs.</p><p>If you’re using Kubernetes, you probably manage traffic to clusters and services across multiple nodes using internal load-balancing services, which is the most common and practical approach. But if you’re running an application on multiple clouds, it can be hard to distribute traffic intelligently among them. In this blog post, we show you how to use Cloudflare Load Balancer in conjunction with Kubernetes so you can start to achieve the benefits of a multi-cloud configuration.</p><p>To continue reading follow the Google Cloud blog <a href="https://cloudplatform.googleblog.com/2018/02/creating-a-single-pane-of-glass-for-your-multi-cloud-Kubernetes-workloads-with-Cloudflare.html">here</a> or if you are ready to get started we created a <a href="https://support.cloudflare.com/hc/en-us/articles/115003384591-Using-Kubernetes-on-GKE-and-AWS-with-Cloudflare-Load-Balancer">guide</a> on how to deploy an application using Kubernetes on GCP and AWS along with our Cloudflare Load Balancer.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1BCYwEZkuZnTcf06JBjegX/176554a91047c6c57c4bd83b815dc08f/Single_Pane_ofglass_Cloudflare.png" />
            
            </figure> ]]></content:encoded>
            <category><![CDATA[Google Cloud]]></category>
            <category><![CDATA[Google]]></category>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Serverless]]></category>
            <guid isPermaLink="false">4ZQwt7DyISJPGuH5oeauP7</guid>
            <dc:creator>Kamilla Amirova</dc:creator>
        </item>
        <item>
            <title><![CDATA[Introducing the Cloudflare Warp Ingress Controller for Kubernetes]]></title>
            <link>https://blog.cloudflare.com/cloudflare-ingress-controller/</link>
            <pubDate>Tue, 05 Dec 2017 14:00:00 GMT</pubDate>
            <description><![CDATA[ It’s ironic that the one thing most programmers would really rather not have to spend time dealing with is... a computer.  ]]></description>
            <content:encoded><![CDATA[ <p><i>NOTE: Prior to launch, this product was renamed Argo Tunnel. Read more in the </i><a href="/argo-tunnel/"><i>launch announcement</i></a><i>.</i></p><p>It’s ironic that the one thing most programmers would really rather not have to spend time dealing with is... a computer. When you write code it’s written in your head, transferred to a screen with your fingers and then it has to be run. On. A. Computer. Ugh.</p><p>Of course, code has to be run and typed on a computer so programmers spend hours configuring and optimizing shells, window managers, editors, build systems, IDEs, compilation times and more so they can minimize the friction all those things introduce. Optimizing your editor’s macros, fonts or colors is a battle to find the most efficient path to go from idea to running code.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/313EyDPITzKIECDYuj2qzq/a5eec59921a13d8aadfe7ed0e6ccc305/4532962327_c5a219d992_b.jpg" />
            
            </figure><p><a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a> <a href="https://www.flickr.com/photos/ivyfield/4532962327/in/photolist-7UyBLT-87ERdK-cCZVyC-8E715f-dbdKCZ-nGKair-cQay4s-ebwmMy-nAuTVv-jw9hxd-nqxc9h-nH1hJw-cp4c1Q-8B3PLE-PUxit-6gY6pQ-4P2q52-cCZVWL-6eRJAH-kNHY-nj1peY-nqxyHa-iNw9jP-5boJ6P-J3KVad-nj1hAZ-7yYuBu-8PCwt2-aJptFP-b4WLoM-nysiQJ-b8kxAV-BtcWbK-7yKiEj-cABXZ1-b8RR72-9LbLum-a6n7fX-X3SERX-br1nSQ-qdLBYQ-4XJsbd-5zXtUQ-dWePHa-qAi9Jt-awuoCM-cACicL-cA43Y1-nGQWPs-dotR4Y">image</a> by <a href="https://www.flickr.com/photos/ivyfield/">Yutaka Tsutano</a></p><p>Once the developer is managing their own universe they can write code at the speed of their mind. But when it comes to putting their code into production (which necessarily requires running their programs on machines that they don’t control) things inevitably go wrong. Production machines are never the same as developer machines.</p><p>If you’re not a developer, here’s an analogy. Imagine carefully writing an essay on a subject dear to your heart and then publishing it only to be told “unfortunately, the word ‘the’ is not available in the version of English the publisher uses and so your essay is unreadable”. That’s the sort of problem developers face when putting their code into production.</p><p>Over time different technologies have tried to deal with this problem: dual booting, different sorts of isolation (e.g. virtualenv, chroot), totally static binaries, virtual machines running on a developer desktop, elastic computing resources in clouds, and more recently <a href="https://en.wikipedia.org/wiki/Operating-system-level_virtualization">containers</a>.</p><p>Ultimately, using containers is all about a developer being able to say “it ran on my machine” and be sure that it’ll run in production, because fighting incompatibilities between operating systems, libraries and runtimes that differ from development to production is a waste of time (in particular developer brain time).</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3w1Pjk0GrYgcVwz3LiliHI/9c14d99ad7b4366a4db6d961f6a5e472/14403331148_bf25864944_k-1.jpg" />
            
            </figure><p><a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a> <a href="https://www.flickr.com/photos/jumilla/14403331148/in/photolist-nWLQxE-3eHezK-a85qp6-iEoK39-iYjss-9at6Z7-iYjsr-9gYg6a-bW3NeL-6AMx91-6rJaN3-28rg3L-TvqJmB-GjMFdK-2UrVHv-fs2WCj-4LGK2-2UrYz4-2Us2tK-Eeqeo-85HX8j-rF6SGG-o9rBXe-fWrkwA-dcGZAo-aoHuTF-SGpPT3-boaQy8-u8Bei-62JAWa-s9fFGo-61fNWq-fJYrjR-axYxm-2h42pU-2h42w7-rRNyES-fKfUKQ-6YXYGU-VjiSN1-4Xcg61-7YmaKY-WyF1oq-bE83qB-dvQoQw-CRQx6-82fwLo-fvJhXq-gmkcM-U3mP5E">image</a> by <a href="https://www.flickr.com/photos/jumilla/">Jumilla</a></p><p>In parallel, the rise of microservices is also a push to optimize developer brain time. The reality is that we all have limited brain power and ability to comprehend the complex systems that we build in their entirety and so we break them down into small parts that we can understand and test: functions, modules and services.</p><p>A microservice with a well-defined API and related tests running in a container is the ultimate developer fantasy. An entire program, known to operate correctly, that runs on their machine and in production.</p><p>Of course, no silver lining is without its cloud and containers beget a coordination problem: how do all these little programs find each other, scale, handle failure, log messages, communicate and remain secure. The answer, of course, is a coordination system like <a href="https://kubernetes.io/">Kubernetes</a>.</p><p>Kubernetes completes the developer fantasy by allowing them to write and deploy a service and have it take part in a whole.</p><p>Sadly, these little programs have one last hurdle before they turn into useful Internet services: they have to be connected to the brutish outside world. Services must be safely and scalably exposed to the Internet.</p><p>Recently, Cloudflare introduced a new service that can be used to connect a web server to Cloudflare without needing to have a public IP address for it. That service, <a href="https://warp.cloudflare.com">Cloudflare Warp</a>, maintains a connection from the server into the Cloudflare network. The server is then only exposed to the Internet through Cloudflare with no way for attackers to reach the server directly.</p><p>That means that any connection to it is protected and accelerated by Cloudflare’s service.</p>
    <div>
      <h3>Cloudflare Warp Ingress Controller and StackPointCloud</h3>
      <a href="#cloudflare-warp-ingress-controller-and-stackpointcloud">
        
      </a>
    </div>
    <p>Today, we are extending Warp’s reach by announcing the Cloudflare Warp <a href="https://kubernetes.io/docs/concepts/services-networking/ingress/">Ingress Controller</a> for Kubernetes (it’s an open source project and can be found <a href="https://github.com/cloudflare/cloudflare-warp-ingress">here</a>). We worked closely with the team at <a href="https://stackpoint.io/">StackPointCloud</a> to integrate Warp, Kubernetes and their universal control plane for Kubernetes.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/P1ACOSWhcrBdZU7WlNcHi/798b6421302ef4c4c070607c2df97fb8/Screen-Shot-2017-12-04-at-5.28.18-PM-2.png" />
            
            </figure><p>Within Kubernetes creating an ingress with annotation <code>kubernetes.io/ingress.class: cloudflare-warp</code> will automatically create secure Warp tunnels to Cloudflare for any service using that ingress. The entire lifecycle of tunnels is transparently managed by the ingress controller making exposing Kubernetes-managed services securely via Cloudflare Warp trivially easy.</p><p>The Warp Ingress Controller is responsible for finding Warp-enabled services and registering them with Cloudflare using the hostname(s) specified in the Ingress resource. It is added to a Kubernetes cluster by creating a file called warp-controller.yaml with the content below:</p>
            <pre><code>apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  creationTimestamp: null
  generation: 1
  labels:
    run: warp-controller
  name: warp-controller
spec:
  replicas: 1
  selector:
    matchLabels:
      run: warp-controller
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: warp-controller
    spec:
      containers:
      - command:
        - /warp-controller
        - -v=6
        image: quay.io/stackpoint/warp-controller:beta
        imagePullPolicy: Always
        name: warp-controller
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - name: cloudflare-warp-cert
          mountPath: /etc/cloudflare-warp
          readOnly: true
      volumes:
        - name: cloudflare-warp-cert
          secret:
            secretName: cloudflare-warp-cert
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30</code></pre>
            <p>The full documentation is <a href="https://developers.cloudflare.com/argo-tunnel/reference/kubernetes/">here</a> and shows how to get up and running with Kubernetes and Cloudflare Warp on StackPointCloud, Google GKE, Amazon EKS or even <a href="https://kubernetes.io/docs/getting-started-guides/minikube/">minikube</a>.</p>
    <div>
      <h3>One Click with StackPointCloud</h3>
      <a href="#one-click-with-stackpointcloud">
        
      </a>
    </div>
    <p>Within StackPointCloud adding the Cloudflare Warp Ingress Controller requires just <i>a single click</i>. And one more click and you've deployed a Kubernetes cluster.</p><p>The connection between the Kubernetes cluster and Cloudflare is made using a TLS tunnel ensuring that all communication between the cluster and the outside world is secure.</p><p>Once connected the cluster and its services then benefit from Cloudflare's DDoS protection, WAF, global load balancing and health checks and huge global network.</p><p>The combination of Kubernetes and Cloudflare makes managing, scaling, accelerating and protecting Internet facing services simple and fast.</p> ]]></content:encoded>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[Argo Smart Routing]]></category>
            <category><![CDATA[Optimization]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Cloudflare Tunnel]]></category>
            <guid isPermaLink="false">3JzDlyg9g51wqTsZdMuHW3</guid>
            <dc:creator>John Graham-Cumming</dc:creator>
        </item>
        <item>
            <title><![CDATA[Living In A Multi-Cloud World]]></title>
            <link>https://blog.cloudflare.com/living-in-a-multi-cloud-world/</link>
            <pubDate>Tue, 21 Nov 2017 16:30:00 GMT</pubDate>
            <description><![CDATA[ A few months ago at Cloudflare’s Internet Summit, we hosted a discussion on A Cloud Without Handcuffs with Joe Beda, one of the creators of Kubernetes, and Brandon Phillips, the co-founder of CoreOS. ]]></description>
            <content:encoded><![CDATA[ <p>A few months ago at Cloudflare’s Internet Summit, we hosted a discussion on <a href="/a-cloud-without-handcuffs/">A Cloud Without Handcuffs</a> with Joe Beda, one of the creators of Kubernetes, and Brandon Phillips, the co-founder of CoreOS. The conversation touched on multiple areas, but it’s clear that more and more companies are recognizing the need to have some strategy around hosting their applications on multiple cloud providers.</p><p>Earlier this year, Mary Meeker published her annual <a href="http://www.kpcb.com/internet-trends">Internet Trends</a> report which revealed that 22% of respondents viewed Cloud Vendor Lock-In as a top 3 concern, up from just 7% in 2012. This is in contrast to previous top concerns, Data Security and Cost &amp; Savings, both of which dropped amongst those surveyed.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3lCKoQPPU77zT1kPMwe2Mb/11e6f100efab86138150f3d911fa39ad/Mary-Meeker-Internet-Trends-2017.png" />
            
            </figure><p>At Cloudflare, our mission is to help build a better internet. To fulfill this mission, our customers need to have consistent access to the best technology and services, over time. This is especially the case with respect to storage and compute providers. This means not becoming locked-in to any single provider and taking advantage of multiple cloud computing vendors (such as Amazon Web Services or Google Cloud Platform) for the same end user services.</p>
    <div>
      <h3>The Benefits of Having Multiple Cloud Vendors</h3>
      <a href="#the-benefits-of-having-multiple-cloud-vendors">
        
      </a>
    </div>
    <p>There are a number of potential challenges when selecting a single cloud provider. Though there may be scenarios where it makes sense to consolidate on a single vendor, our belief is that it is important that customers are aware of their choice and downsides of being potentially locked-in to that particular vendor. In short, know what trade offs you are making should you decide to continue to consolidate parts of your network, compute, and storage with a single cloud provider. While not comprehensive, here are a few trade-offs you may be making if you are locked-in to one cloud.</p>
    <div>
      <h4>Cost Efficiences</h4>
      <a href="#cost-efficiences">
        
      </a>
    </div>
    <p>For some companies, there may be a cost savings involved in spreading traffic across multiple vendors. Some can take advantage of free or reduced cost tiers at lower volumes. Vendors may provide reduced costs for certain times of day that are lower utilized on their infrastructure. Applications can have varying compute requirements amongst layers of the application: some may require faster, immediate processing while others may benefit from delayed processing at a lower cost.</p>
    <div>
      <h4>Negotiation Strength</h4>
      <a href="#negotiation-strength">
        
      </a>
    </div>
    <p>One of the most important reasons to consider deploying in multiple cloud providers is to minimize your reliance on a single vendor’s technology for your critical business processes. As you become more vertically integrated with any vendor, your negotiation posture for pricing or favorable contract terms becomes diminished. Having production ready code available on multiple providers allows you to have less technical debt should you need to change. If you go a step further and are already sending traffic to multiple providers, you have minimized the technical debt required to switch and can negotiate from a position of strength.</p>
    <div>
      <h4>Business Continuity or High Availability</h4>
      <a href="#business-continuity-or-high-availability">
        
      </a>
    </div>
    <p>While the major cloud providers are generally reliable, there have been a few notable outages in recent years. The most significant in recent memory being Amazon’s <a href="https://aws.amazon.com/message/41926/">US-EAST S3</a> outage in February. Some organizations may have a policy specifying multiple providers for high availability while others should consider it where necessary and feasible as a best practice. A multi-cloud strategy can lower operational risk from a single vendor’s mistakes causing a significant outage for a mission critical application.</p>
    <div>
      <h4>Experimentation</h4>
      <a href="#experimentation">
        
      </a>
    </div>
    <p>One of the exciting things about having competition in the space is the level of innovation and feature velocity of each provider. Every year there are major announcements of new products or features that may have a significant impact on improving your organization's competitive advantage. Having test and production environments in multiple providers gives your engineers the ability to understand and experiment with a new capability in the context of your technology stack and data. You may even try these features for a portion of your traffic and get real world data on any benefits realized.</p>
    <div>
      <h3>Cloudflare’s Role</h3>
      <a href="#cloudflares-role">
        
      </a>
    </div>
    <p>Cloudflare is an independent third party in your multi-cloud strategy. Our goal is to minimize the layers of lock-in between you and a provider and lower the effort of change. In particular, one area where we can help right away is to minimize the operational changes necessary at the network, similar to what Kubernetes can do at the storage and compute level. As a benefit of our network, you can also have a centralized point for security and operational control.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/63ggEFEj8gBOjaS6OJ8HKJ/849234502f57a31cfe7a84244831e930/Cloudflare_Multi_Cloud.png" />
            
            </figure><p>Cloudflare’s Load Balancing can easily be configured to act as your global application traffic aggregator and distribute your traffic amongst origins at as many clouds as you choose to utilize. Active layer 7 health checks continually probe your origins and can automatically move traffic in the case of network or application failure. All consolidated web traffic can be inspected and acted upon by Cloudflare’s best of breed <a href="https://www.cloudflare.com/security/">Security</a> services, providing a single control point and visibility across all application traffic, regardless of which cloud the origin may be on. You also have the benefit of Cloudflare’s <a href="https://www.cloudflare.com/network/">Global Anycast Network</a>, providing for better speed and higher availability regardless of which clouds your origins are hosted on.</p>
    <div>
      <h3>Billforward: Using Cloudflare to Implement Multi-Cloud</h3>
      <a href="#billforward-using-cloudflare-to-implement-multi-cloud">
        
      </a>
    </div>
    <p>Billforward is a San Francisco and London based startup that is focused and mission driven on changing the way people bill and charge their customers, providing a solution to the complexities of Quote-to-Cash. Their platform is built on a number of Rest APIs that other developers call to bill and generate revenue for their own companies.</p><p>Billforward is using Cloudflare for its core customer facing application to failover traffic between Google Compute Engine and Amazon Web Services. Acting as a reverse proxy, Cloudflare receives all requests for and decides which of Billforward’s two configured cloud origins to use based upon the availability of that origin in near real-time. This allows Billforward to completely manage the connections to and from two disparate cloud providers using Cloudflare’s UI or API. Billforward is in the process of migrating all of their customer facing domains to a similar setup.</p>
    <div>
      <h4>Configuration</h4>
      <a href="#configuration">
        
      </a>
    </div>
    <p>Billforward has a single load balanced hostname with two available Pools. They’ve named the two Pools with “gce” and “aws” labels and each Pool has one Origin associated with it. All of the Pools are enabled and the entire LB/hostname is proxied through Cloudflare (as indicated by the orange cloud).</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/28B3npbU2VtpVbeHeQApBz/7914a5ca5d0d9b9019e77a669aa81fa7/Billforward_Config_UI.png" />
            
            </figure><p>Cloudflare probes Billforward’s Origins once every minute from all of Cloudflare’s data centers around the world (a feature available to all Load Balancing Enterprise customers). If Billforward’s GCE Origin goes down, Cloudflare will quickly and automatically failover to the AWS Origin with no actions required from Billforward’s team.</p><p>Google Compute Engine was chosen as the primary provider for this application by virtue of cost. Martin Lee, Site Reliability Engineer at Billforward says, “Essentially, GCE is cheaper for our general purpose computing needs but we're more experienced with deployments in AWS. This strategy allows us to switch back and forth at will and avoid being tied in to either platform.” It is likely that Billforward will change the priority as pricing models evolve.</p><blockquote><p>“It's a fairly fast moving world and features released by cloud providers can have a meaningful impact on performance and cost on a week by week basis - it helps to stay flexible,” says Martin. “We may also change priority based on features.”</p></blockquote><p>For orchestration of the compute and storage layers, Billforward uses <a href="https://www.docker.com/">Docker</a> containers managed through <a href="http://www.rancher.com/">Rancher</a>. They use distinct environments between cloud providers but are considering bridging an environment across cloud providers and using VPNs between them, which will enable them to move load between providers even more easily. “Our system is loosely coupled through a message queue,” adds Martin. “Having a container system across clouds means we can really take advantage of this - we can very easily move workloads across clouds without any danger of dropping tasks or ending up in an inconsistent state.”</p>
    <div>
      <h4>Benefits</h4>
      <a href="#benefits">
        
      </a>
    </div>
    <p>Billforward manages these connections at Cloudflare’s edge. Through this interface (or via the Cloudflare APIs), they can also manually move traffic from GCE to AWS by just disabling the GCE pool or by rearranging the Pool priority and make AWS the primary. These changes are near instant on the Cloudflare network and require no downtime to Billforward’s customer facing application. This allows them to act on potential advantageous pricing changes between the two cloud providers or move traffic to hit pricing tiers.</p><p>In addition, Billforward is now not “locked-in” to either provider’s network; being able to move traffic and without any downtime means they can make traffic changes independent of Amazon or Google. They can also integrate additional cloud providers any time they deem fit: adding Microsoft Azure, for example, as a third Origin would be as simple as creating a new Pool and adding it to the Load Balancer.</p><p>Billforward is a good example of a forward thinking company that is taking advantage of technologies from multiple providers to best serve their business and customers, while not being reliant on a single vendor. For further detail on their setup using Cloudflare, please check their <a href="https://www.billforward.net/blog/being-multi-cloud-with-cloudflare/">blog</a>.</p> ]]></content:encoded>
            <category><![CDATA[Google Cloud]]></category>
            <category><![CDATA[Internet Summit]]></category>
            <category><![CDATA[Serverless]]></category>
            <category><![CDATA[Kubernetes]]></category>
            <guid isPermaLink="false">VwEr9XiNrvDVfzTQliPa4</guid>
            <dc:creator>Sergi Isasi</dc:creator>
        </item>
        <item>
            <title><![CDATA[A Cloud Without Handcuffs]]></title>
            <link>https://blog.cloudflare.com/a-cloud-without-handcuffs/</link>
            <pubDate>Thu, 14 Sep 2017 19:01:55 GMT</pubDate>
            <description><![CDATA[ Brandon Philips, Co-Founder & CTO, CoreOS, and Joe Beda, CTO, Heptio, & Co-Founder, Kubernetes

 ]]></description>
            <content:encoded><![CDATA[ <p><a href="https://twitter.com/brandonphilips">Brandon Philips</a>, Co-Founder &amp; CTO, CoreOS, and <a href="https://twitter.com/jbeda">Joe Beda</a>, CTO, Heptio, &amp; Co-Founder, Kubernetes</p><p>Moderator: Alex Dyner, Head of Special Projects, Cloudflar</p><p>We’re exploring increasing risk of few companies locking in customers gaining more power over time.</p><p>AD: I want to hear your stories about how you got into what you do.</p><p>JB: Kubernetes faced problem of either having googlers use rbs or bring X to rest of world. We wanted to have Googlers and outside people using something similar. We chose to do it as open source because you play a different game when you’re the underdog. Through open source we could garner interest. We wanted to provide applicational mobility.</p><p>AD: Brandon, talk about your mission and why you started company.</p><p>BP: We started CoreOS four years ago; We spent a lot of time thinking about this problem and containers were natural choice. They are necessary for achieving our mission. We wanted to allow people to have mobility around their applications. We wanted to enable new security model through containers. So we started building a product portfolio</p><p>AD: There are tradeoffs between using a container or an open source tech; how do you think about those tradeoffs?</p><p>BP: First Kubernetes is providing application-centric view. The abstraction is: how do we create a platform? Also, how to build useful integrations?</p><p>The project tries to build useful integrations. It’s really about that initial abstraction.</p><p>JB: One useful comparison is Kubernetes for is a kernel for system. There is a feeling that we want to keep Kubernetes as flexible kernel, while recognizing that you have to build integrations &amp; user mode on top of it.</p><p>AD: How do you talk about different levels (developer, operational)?</p><p>JB: The advice i give is that lock-in is unavoidable. The question is: What is the risk of that lock-in? You have to weigh that risk against the benefits. If you’re a startup, you’re not worried about the risk of moving away from a public cloud network. Vs. very large company. There are certain types of lock-in that present problem for operations vs. development teams. Kubernetes makes it an operational problem versus a developmental problem.</p><p>BP: Operational: by using Kubernetes, people can bring up dev environments and test on internal infrastructure in our office. This is already providing value.</p><p>On the app side, risk comes in when cloud providers build databases where data is tied to the data center. Abstraction allows developers to be free from data center.</p><p>AD: How does that work over time?</p><p>BP: For many organizations it comes down to cost benefit analysis. They look at their application code, figure out how long they’re locked-in. Leverage only comes when you can call a bluff.Basically a business decision.</p><p>JB: It’s a new type of technical debt.There is no one answer.</p><p>AD: As less people can do this, salaries of mainframe programmers are going up now; what do you think about that?</p><p>JP: There is an analogy between the big public clouds and the legacy mainframe</p><p>Legacy mainframe vs. public cloud. Even if no longer preferred choice, it will have a long future. It’s here to stay, even if world moves on.</p><p>BP: The larger companies will be competing against the major tech companies that run clouds. We don’t have a term. Is it “cloud debt”? Cloud technical debt? It’s a nascent topic but becoming important.A new challenge .</p><p>JB: Data gravity.</p><p>AD: A lot of this is about Amazon---are other large vendors approaching this because of their market position?</p><p>JB: Amazon is the big elephant for sure. But this goes beyond Amazon. When you look at Kubernetes in containers, it provides a model that did not exist before Amazon. Amazon has been struggling to find balance between infrastructure and ease of use.</p><p>So what is making this layer of infrastructure so interesting is not just multi-cloud strategy, but a different way of thinking about programming and automating applications.</p><p>The interesting stuff is how we utilize this new tool set.</p><p>BP: It’s about making and ensuring the tech works across the board. When Kubernetes started the tech wasn’t there yet for it to run on Amazon. One of our first challenges was to make it possible to get Kubernetes on Amazon. It’s an ongoing technological battle to figure out abstractions and making cloud providers innovators themselves in data and network storage, etc.</p><p>AD: What’s the counter to, yes, CoreOS will help me not get locked into Amazon?</p><p>BP: Customers are getting APIs. We’re giving customers an API that we don’t modify and they get upstream Kubernetes. We take open source software and integrate it; they can put that integration into their own apps.It’s about taking pieces and providing an adhesive experience.</p><p>Not just infrastructure but application monitoringA lot of value of the cloud is that it automates operations.</p><p>We provide you with open source software that is automated.</p><p>Software venders have to start providing value proposition of resecuring infrastructure when a vulnerability appears in the cloud. “Zero-toil automation”</p><p><b>Q&amp;A:</b></p><p>Q: Customers with critical applications usually use multiple networks; is this one value proposition of the cloud lock-in argument?</p><p>BP: we have seen both; it Depends on their internal risk assessment. You can have beautiful architecture about how your business will survive but if you don’t have applications around it, it’s all pointless.</p><p>JB: Geography is important. Having a substrate to write app against is important.</p><p>BP: It will be interesting as we see global distribution of compute network and storage, the different cost-benefit analyses that will be available. A lot of competition will arise outside of the US in terms of building data centers.</p><p>All our sessions will be streamed live! If you can't make it to Summit, here's the link: <a href="http://www.cloudflare.com/summit17">cloudflare.com/summit17</a></p> ]]></content:encoded>
            <category><![CDATA[Internet Summit]]></category>
            <category><![CDATA[Kubernetes]]></category>
            <guid isPermaLink="false">2HCFrLTeQdXcRz8imsUbtc</guid>
            <dc:creator>Internet Summit Team</dc:creator>
        </item>
    </channel>
</rss>