<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Analogous Dev Blog</title>
        <link>https://www.analogous.dev</link>
        <description>The analogous developer. Technology topics for newcomers and experts alike.</description>
        <lastBuildDate>Sat, 22 Feb 2025 13:09:51 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/nuxt-community/feed-module</generator>
        <language>en</language>
        <image>
            <title>Analogous Dev Blog</title>
            <url>analogous-dev.png</url>
            <link>https://www.analogous.dev</link>
        </image>
        <copyright>Cole Arendt 2022</copyright>
        <item>
            <title><![CDATA[Analogies Help Us Learn!]]></title>
            <link>/blog/analogies-help-us-learn</link>
            <guid>analogies-help-us-learn</guid>
            <pubDate>Mon, 26 Oct 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[
I love analogies. Perhaps that is easy to guess based on the name of the
website. Why? Well, I'll tell you. What is an analogy? Perhaps you recall something like this from primary or secondary school: egg:chicken::acorn:? If that use of analogies brings back painful memories  ...]]></description>
            <content:encoded><![CDATA[
I love analogies. Perhaps that is easy to guess based on the name of the
website. Why? Well, I'll tell you.

### What is an analogy?

Perhaps you recall something like this from primary or secondary school:

```
egg:chicken::acorn:?
```

If that use of analogies brings back painful memories (like the SAT), then rest
assured, that is not how I like to think of analogies. The definition of an
analogy is:

> **analogy:** _noun_. a comparison between two things. typically for the
> purpose of explanation or clarification

Note that some definitions explicitly note that analogies are a comparison of
two "unalike" things, thereby highlighting their similarities in a certain sense.

### Why analogies are great

Learning is defined as "the acquisition of knowledge or skills through
experience, study, or by being taught." We have all learned _many_ things
in our lives, and they are typically in areas that we have spent the most
time or have gained the most hands-on experience.

However, learning is sometimes hard to "transfer" into new areas. _Related_
areas come easily (i.e. good soccer players tend to be good at futsal, because
the two are very closely related), but _unrelated_ areas, not so much. Being good
at basketball does not necessarily transfer well to rocket science, for instance.

Enter analogies! Analogies help us take experience or learning from one _unrelated_
area to another, and thereby allow us to jump from basketball to rocket science!
We cannot expect to become experts purely by way of analogies, but it is a great
way to soften learning curves, simplify topics, and transfer our experience!!

Plus, they're fun!

### An example

I love math and technology, and am a bit of a nerd. Many of the people I care
about are either not nerds or are not nerds about math and technology. As a
result, I use analogies to help explain my world to them without requiring them
to become math/programming nerds.

For instance, I might explain that "Calculus is like Driving a Car." This is a
useful analogy I used when I was teaching calculus. To help give newcomers an
intuition about calculus, I would draw upon their experience driving cars! Need
help understanding derivatives? Think about the spedometer! Need help
understanding integrals? Think about the odometer!

None of us is capable of _really_ understanding things for which we have no
context, background, or logical foundation for. Analogies help us bridge that
gap and provide an easier entry-point based on similarities of otherwise unlike
things.

### How we use them here

On this site, we love to use analogies to explain tech topics and things that
are otherwise complex or tricky to understand. As a result, when sharing an
analogy, we will generally shoot for a few highlights:

- The analogy
- The high points
- The breakdown

#### The Analogy

We begin with an analogy! Most analogies will require a bit of
elaboration and qualification. At first glance, perhaps it seems strange to
relate calculus to driving a car.

#### The High Points

If the analogy is good, there are some very clear points of similarity that will
help explain the "less accessible" topic. The high points of an analogy are its
focus and primary value, so this is where you should spend the bulk of your time
when learning.

#### The Breakdown

Every analogy "breaks down" somewhere. We should always acknowledge the limits
of our tools, and every analogy has its limits. Generally, it is best to focus
on the high points of an analogy, lest we confuse newcomers with our
qualifications. However, in this context we will briefly mention our
shortcomings as a highlight for where you can dig deeper. Further, our
shortcomings can help us understand the desired topic by establishing
guard-rails.

To take our example, while driving a car gives you an _entry point_ into
calculus, it will not make you an expert. At some point, you _do_ have to master
the definitions and mechanics of derivatives and integrals. Also, I can't think of
any way relate an oil change to calculus, so let's not get too literal.

### Closing

It is worth noting that this site is named for analogies. Coincidentally, I
will often focus on them. However, I'm not very creative. As a result, many
posts on the site will not be about analogies at all. However, my aim is that
the theme of explaining topics in an accessible way will remain.

Next time you are trying to explain something complex, do some thinking to see
if an analogy will help you out! A well-thought-out analogy can be very useful
and a lot of fun! Of course, others fall completely flat, but that's fun in its
own way.

Finally, if analogies are ever actively hindering your understanding or just making
you more confused, _please_ drop them. If an analogy is relating to something you have
no context for, it may be better to try another or to just ignore the analogy altogether.
After all, the focus is learning!
]]></content:encoded>
            <category>Analogy</category>
            <category>Meta</category>
            <category>General</category>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[DevOps is like Building Sand Castles]]></title>
            <link>/blog/devops-is-like-building-sand-castles</link>
            <guid>devops-is-like-building-sand-castles</guid>
            <pubDate>Thu, 05 Nov 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[
How can less-technical people understand what DevOps (short for Development
Operations) people do? Welcome to my childish attempt to explain. The Analogy Doing DevOps work is like building sand castles! First, I'm not a real DevOps person, but bear with me. When you build sand castles
at  ...]]></description>
            <content:encoded><![CDATA[
How can less-technical people understand what DevOps (short for Development
Operations) people do? Welcome to my childish attempt to explain.

### The Analogy

> Doing DevOps work is like building sand castles!

First, I'm not a _real_ DevOps person, but bear with me. When you build sand castles
at the beach, you are aiming to:

1. build something architecturally sound
1. build it on a decently realistic timeline (or else the tides will eat it for lunch)
1. build something that is close to destructive forces (waves). Or for extra
   points, involves them (i.e. fill an epic moat)
1. do something productive (after all, you could just lie down in the sun instead)
1. defend yourself from an imaginary invader

DevOps is similar to these things! Of course, the primary component in DevOps
is enabling others (Developers) and making their work more productive.
_That_ part of the job is more like holding a ladder (more on that another day).
I am concerned here with mostly what a DevOps person _does_ or _builds_.

### The High Points

1. DevOps involves building and maintaining digital architecture (i.e. sand castles). Whether using
   virtual machines, containers, or "infrastructure-as-code", the end goal is a castle.
1. DevOps engineers often work hard and work quickly, as their work is in high
   demand by the teams that depend on them. Timeliness is important
1. Although a system without any users would be easier to maintain, the destructive
   force that is "users" will assault the DevOps engineers' well-architected fortress.
1. Not any sand castle will do. DevOps engineers often care about building
   things correctly. Like mathematicians, they also do not want to do the same 
   thing over and over again ("toil").
1. Often times DevOps engineers are concerned about _actual_ invaders... but to
   prepare, they will load test their architecture with imaginary invaders (to be
   sure it will work) or get their coworkers to test it out.

Some of these connections are very helpful. For instance, a load test becomes
"simultaneously building a sand-castle and trying to throw waves at it, to make
sure it is strong." Or deploying an update to a cluster is "automatically
reconfiguring 40 sand-castles and hoping all of them keep standing." Perhaps in
a moment of deep thought, "I can't figure out why my sand-castles keep crashing.
The waves aren't even that big today, and they did just fine yesterday."

A Docker container is a sand castle, let's say, and a docker _image_ is a
template that allows me to build new sand castles super fast when I need them!
The explanatory power is wonderful!

### The Breakdown

The most dangerous thing about this analogy is that it is quaint and accessible,
and could therefore easily be used to over-simplify or demean the work of DevOps
engineers. However, these engineers are building incredibly intricate
architectures that run some of the most lucrative businesses on the face of the
planet. It is work that should not be taken lightly! Just because sand-castles
rarely have a lucrative or serious purpose does not mean that DevOps work shares
that trait.

A related breakdown: the imaginative childishness of sandcastles coupled with
"digital" architecture that cannot be objectively seen can lead down a similar
path suggesting that DevOps work is not productive. It is worth remembering that
these engineers are actively building, maintaining, and improving the foundation
that the _entirety_ of most technology careers are standing upon. The
twenty-first century would not look the same without these contributors!

As such, I think this analogy is most productive when an engineer describes their
_own_ work this way. An observer proposing the analogy could come across as demeaning.

Perhaps a better way to think of it as an external observer:

> Wow. This person is building and maintaining multi-dimensional _real_ castles
  in another universe that I cannot see with my eyes, or even understand, but
  which I am dependent on for my twenty-first century lifestyle. Thank you

### Closing

I have tremendous respect for the Operations team that I work with. They do
amazing work and really keep the rest of the teams rolling smoothly! As someone
who occasionally dabbles in their area of expertise, I have seen that it can be
hard to explain the topic to others. This is my feeble attempt at explaining the
idea to those outside the field.

I have to say, it has been a recurring analogy in discussions with my wife, who
does an amazing job being interested in my digital playground. If you need me,
I'll be building sandcastles with the kiddos.
]]></content:encoded>
            <category>DevOps</category>
            <category>Analogy</category>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[Debugging Kerberos is like Hades]]></title>
            <link>/blog/debugging-kerberos-is-like-hades</link>
            <guid>debugging-kerberos-is-like-hades</guid>
            <pubDate>Sat, 19 Dec 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[ We've all been there. You start debugging kerberos, and it starts getting warmer
in the room. Ok, maybe not all of us. Debugging Kerberos certainly can feel an
awful lot like Hades, though. The Analogy Debugging Kerberos is like Hades Hades is the Greek God of  ...]]></description>
            <content:encoded><![CDATA[  
We've all been there. You start debugging kerberos, and it starts getting warmer
in the room. Ok, maybe not all of us. Debugging Kerberos certainly can feel an
awful lot like Hades, though.

### The Analogy

> Debugging Kerberos is like Hades

Hades is the Greek God of the underworld or sometimes a reference for the place
of the dead, generally. I promise, this analogy is not _completely_ out of left
field.

Kerberos is a computer protocal developed by MIT and used in many enterprise
settings for authentication and authorization. However, it gets its name from
Cerberus - the guard dog of Hades. As a result, feeling close to Hades when you are close
to Kerberos is reasonable at some level.

Although there are many aspects of Hades we could highlight for the purposes of
our analogy, we will focus on a few: heat, torment, eternal, inexplicable,
aimless, and generally terrible.

### The High Points

Why is debugging Kerberos so awful? A couple of reasons below. Particularly,
reasons that can encourage us how _not_ to make software:

- **No documentation** - Ok there _is_ documentation, but much of it is just too
  theoretical to be useful. Further, much of programming in the 21st century is
  search-engine-foo. Unfortunately, searching Kerberos issues generally leads to
  Microsoft documentation from Windows XP. Despair usually ensues. If you have been
  there, I promise you are not alone.
- **No logging** - The next best problem for software: no logging. Kerberos has
  a knack for giving useless error messages or error messages that need to be run
  through a universal translation engine (which does not exist). Good luck finding
  out what a `Generic preauthentication failure` is.
    - _TIP_: Set the environment variable `KRB5_TRACE=/tmp/somefile.log` when
      debugging a Kerberos client. The
      [client-spec](https://web.mit.edu/kerberos/krb5-devel/doc/admin/troubleshoot.html)
      sends client debug logs to that location!
- **Little direction** - This goes alongside the previous items. If you have
  theoretical docs and useless logging, the usual sources of direction and "next
  steps" are lost. Internet tutorials and a general sense for what I want to
  accomplish are about the only places I have found direction for Kerberos issues.
  Keep at it! Perseverance _usually_ wins out in the end.
  - _TIP_: As with most debugging, try to simplify your case as much as possible 
    first. Then slowly add pieces until you get to a working state.
- **Cryptic configuration** - To properly configure Kerberos, you typically need
  to master a bunch of unusual terminology (realm, kdc, ticket, cache, keytab),
  wade through a myriad of brackets, and pay particular attention to case. Not to
  mention the importance of tricky UDP networking rules and the [directionless
  failure](https://social.technet.microsoft.com/Forums/windowsserver/en-US/7fbece1a-9e72-4ed1-b8d6-1a08f633f0bd/trouble-joining-linux-server-to-ad-domain-in-aws-failed-to-find-dc-for-domain?forum=winserverDS)
  you get if you misstep. Tread carefully! A slew of confounding error messages
  are watching your every move.
- **Little tolerance for humans** - What is the difference between
  "analogous.dev" and "ANALOGOUS.DEV"? As far as your browser is concerned,
  nothing. As far as humans are generally concerned, maybe one reads more like
  shouting? As far as Kerberos is concerned, `Realm not local to KDC while getting
  initial credentials`. We humans need help doing software. Please help us,
  Kerberos. Make your logs better.
    - _TIP_: Yes, case matters in domain names to Kerberos. It feels like
      flailing, but in some cases it can actually help!

Put all of this together, and what do you get? It may not be Hades, but it is
pretty close to eternal torment.

### The Breakdown

Now that I have completed that rant, Kerberos has some really nice things about
it. It is also worth noting that it has achieved success by being a pretty
brilliant piece of software.

- Pass-through authentication to backend services
- Granular access controls
- Minimal password usage
- Integration into Windows
- Integration with security keys
- Quickly expiring sessions

This makes the protocol highly desirable for security, particularly in the enterprise. However,
with the internet age upon us, browser-based paradigms like OAuth2 and JWT (JSON web tokens)
are shaping up to replace Kerberos for most of these purposes.

In many large enterprises and backend systems (like databases), Kerberos still rules the day.

### Closing

Although Kerberos is a necessary fixture in some peoples' lives (like mine) and
nonexistent in others, I am hopeful that it can teach us important lessons about
what makes good software and the interesting interplay of obligation versus
enjoyment.

At very least, community, empathy, and a bit of laughter can get us through our
struggles with this beast.
]]></content:encoded>
            <category>DevOps</category>
            <category>Analogy</category>
            <category>Tech</category>
            <category>Kerberos</category>
            <category>Auth</category>
            <category>Tricks</category>
        </item>
        <item>
            <title><![CDATA[Using the Kubernetes Python Client with AWS]]></title>
            <link>/blog/using-the-kubernetes-python-client-with-aws</link>
            <guid>using-the-kubernetes-python-client-with-aws</guid>
            <pubDate>Wed, 27 Jan 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[
As someone who normally just uses `kubectl` and `helm` to talk to my Kubernetes
clusters, the idea of scripting modifications to my Kubernetes cluster was
exciting!! I cracked open the
`kubernetes-python` client and
started playing. TL;DR; We use the `boto3`, `eks-token`, and `kubernetes` python packages to
talk to an EKS  ...]]></description>
            <content:encoded><![CDATA[
As someone who normally just uses `kubectl` and `helm` to talk to my Kubernetes
clusters, the idea of scripting modifications to my Kubernetes cluster was
exciting!! I cracked open the
[`kubernetes-python`](https://github.com/kubernetes-client/python) client and
started playing.

## TL;DR;

We use the `boto3`, `eks-token`, and `kubernetes` python packages to
talk to an EKS cluster without depending on `kubeconfig`.

## Why

For those of us interactively building and maintaining kubernetes resources, 
`helm` or `kubectl` become our bread and butter. They provide very nice CLI
interfaces and have all the bells and whistles one could ask for!

Moreover, when it comes to AWS (Amazon Web Services) and their EKS (Elastic
Kubernetes Service) clusters, they handle authentication smoothly and easily via
a handy CLI command and the `kubeconfig` file (usually stored at `~/.kube/config`:

```
aws eks update-kubeconfig --alias mycluster
```

(Tip: Don't forget the `--alias` flag!)

My assume-role settings and environment variables integrate nicely from my shell: everything is dandy!

However, in this case, I want to build an _app_ / software program that _itself_
accesses, modifies, and maintains resources on my Kubernetes cluster. Of course,
one can script a solution around these CLIs, but that is not very portable (requires
the CLIs installed) and is prone to failure because `kubeconfig` is user-defined, 
user-maintained, and hinges entirely on `contexts` which are arbitrary text strings.

So I headed for the `kubernetes-python` API client which simplifies making API requests
against a Kubernetes cluster. With any luck, I will finish the day with a Python program
that creates my Kubernetes resources.

**NOTE**: Before we get started, it is worth noting that I am presuming you have
a way to authenticate to AWS. In my case, I have already provided environment
variables, an instance profile, etc. that gives this program access to the AWS
API as the necessary IAM role.

## The Story is Auth

As with many of my stories, this story will become one largely consisting of
authentication. Unraveling the "magic" of the `kubeconfig` file and AWS's IAM
authentication was not an easy task - as I soon learned, the `kubernetes-python`
API client _also_ depends heavily on `kubeconfig` by default.

Most of [the
examples](https://github.com/kubernetes-client/python/tree/master/examples) look
something like this, and if you want to use `kubeconfig`, this works very well.

```python
from kubernetes import config

config.load_kube_config()
core_v1 = core_v1_api.CoreV1Api()
```

(Although there is also a cool example of [in-cluster config](https://github.com/kubernetes-client/python/blob/master/examples/in_cluster_config.py))

However, for portability, I want to bypass `kubeconfig` but will be running outside of a cluster. So let's see how this
class works.

```python
config.kube_config.Configuration
```

After a bit of flailing, fighting, and digging, I learned that you can
initialize an API connection with the API endpoint and a bearer token.
Basically, you initialize a configuration object directly and then use that to
initialize an API client.

<!-- url: https://github.com/colearendt/example-python-kubernetes/raw/master/breakout/1_api_client.py -->
```python
import kubernetes


def k8s_api_client(endpoint: str, token: str, cafile: str) -> kubernetes.client.CoreV1Api:
    kconfig = kubernetes.config.kube_config.Configuration(
        host=endpoint,
        api_key={'authorization': 'Bearer ' + token}
    )
    kconfig.ssl_ca_cert = cafile
    kclient = kubernetes.client.ApiClient(configuration=kconfig)
    return kubernetes.client.CoreV1Api(api_client=kclient)
```
<!-- url: end -->

There are other useful options in `kconfig`, like `kconfig.proxy` and
`kconfig.verify_ssl`. Have a look at the class for more details!

### Authenticate to AWS / EKS

Now we need to figure out how to get a bearer token to talk to EKS. I cannot use
the magic in `kubeconfig`, which led me to the [AWS CLI's `aws eks get-token`
command](https://docs.aws.amazon.com/cli/latest/reference/eks/get-token.html). I
chose to opt for a pure python solution in the
[`eks-token`](https://pypi.org/project/eks-token/) package. You can evaluate the
source for yourself [here](https://github.com/peak-ai/eks-token) and [more
specifically,
here](https://github.com/peak-ai/eks-token/blob/master/eks_token/logics.py).


With this module, we can acquire an EKS token easily:

<!-- url: https://github.com/colearendt/example-python-kubernetes/raw/master/breakout/2_token.py -->
```python
import eks_token

cluster_name = 'my-eks-cluster'
my_token = eks_token.get_token(cluster_name)
```
<!-- url: end -->

**NOTE:** It is worth checking whether the `boto3` or `aws` libraries provide
access to this functionality directly from Python as things improve!

### Now TLS

Unfortunately, the `kubernetes-python` client does not allow for inlining
the TLS CA Certificate. As a result, we have to write it to a temp file
(which by inspection is exactly what the `kubeconfig` approach is doing)

<!-- url: https://github.com/colearendt/example-python-kubernetes/raw/master/breakout/3_cafile.py -->
```python

   
import boto3
import tempfile
import base64


def _write_cafile(data: str) -> tempfile.NamedTemporaryFile:
    # protect yourself from automatic deletion
    cafile = tempfile.NamedTemporaryFile(delete=False)
    cadata_b64 = data
    cadata = base64.b64decode(cadata_b64)
    cafile.write(cadata)
    cafile.flush()
    return cafile


bclient = boto3.client('eks')
cluster_data = bclient.describe_cluster(name=cluster_name)['cluster']
my_cafile = _write_cafile(cluster_data['certificateAuthority']['data'])
```

### Put it all together

Armed with a bearer token and transport layer security (TLS), we now have the
tools we need to succeed!!

<!-- url: https://github.com/colearendt/example-python-kubernetes/raw/master/breakout/4_main.py -->
```python
api_client = k8s_api_client(
    endpoint=cluster_data['endpoint'],
    token=my_token['status']['token'],
    cafile=my_cafile.name
)

api_client.list_namespace()
```

## Go wild

The first time `list_namespace()` returned data, I was ecstatic.
Authentication successful! Now the world is your oyster.

All that's left is perusing the [API client
docs](https://github.com/kubernetes-client/python/blob/master/kubernetes/README.md)
and refreshing AWS creds every so often!

For example, creating a configmap:

<!-- url: https://github.com/colearendt/example-python-kubernetes/raw/master/breakout/5_configmap.py -->
```python
my_configmap = kubernetes.client.V1ConfigMap(
    api_version='v1',
    metadata={'name': 'my-configmap'},
    kind='ConfigMap',
    data={'my_file.txt': 'mycontent'}
)

api_client.create_namespaced_config_map(namespace='default', body=my_configmap)

# NOTE: to update a configmap, you need to
# use k8s_client.replace_namespaced_config_map
#
# If it already exists, create will give a 409 conflict
```

Have fun!!

[The source for this article is available here (along with a few extra
examples)](https://github.com/colearendt/example-python-kubernetes)

## Outtakes

Yes, there were some outtakes:

- Standing up [`mitmproxy`](https://mitmproxy.org/) in a docker container and
  setting `kconfig.proxy='http://localhost:8080` and `kconfig.verify_ssl=False`
  before initializing `api_client` so I could see the requests being made to the
  Kubernetes cluster and verify whether authentication was being sent
- Flailing on temp files (today, I learned that python disposes of temp files
  rather quickly by default)
- Trolling the internet and finding an unfortunate absence of docs on this
  topic. "Is this obvious to everyone else? Am I doing something wrong?" Classic
  questions of a lonely explorer.
- Accidentally installing the `aws` package into my `pyenv` and breaking my
  terminal's ability to assume roles when in certain directories.

Programming can be hard! It is so nice to know we are not alone!
]]></content:encoded>
            <category>DevOps</category>
            <category>Python</category>
            <category>Programming</category>
            <category>HowTo</category>
            <category>AWS</category>
            <category>Kubernetes</category>
        </item>
        <item>
            <title><![CDATA[Linux Capabilities are like Superpowers]]></title>
            <link>/blog/linux-capabilities-are-like-superpowers</link>
            <guid>linux-capabilities-are-like-superpowers</guid>
            <pubDate>Mon, 15 Mar 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[
If you were a superhero, what would your superpower be? That is such a great
discussion starter! The Analogy Linux Capabilities are like Superpowers The number of superpowers out there in the imaginations of content creators is
simply astounding. Just check out the X-Men
universe, if you don't  ...]]></description>
            <content:encoded><![CDATA[
If you were a superhero, what would your superpower be? That is such a great
discussion starter!

### The Analogy

> Linux Capabilities are like Superpowers

The number of superpowers out there in the imaginations of content creators is
simply astounding. Just check out the [X-Men
universe](https://x-men.fandom.com/wiki/Category:Powers), if you don't believe
me! However, most people tend towards the common ones (flight, super strength,
invisibility, teleportation, etc.). There are also _real life_ super powers like
empathy, perseverance, forgiveness, and love... I digress.

**Linux Capabilities**, on the other hand, are a notion of providing "advanced
access" to certain programs on a linux computer. If you have ever heard of
"sudo" (short for "super-user do"), you are on the right track! The basic idea
is that most programs only get access to do certain things (walk, talk, sleep,
etc.), but _others_ can be given access to do _more_ (like restart the
computer).

If you are not familiar with "linux" just think about a normal computer. Linux
is a computer "Operating System," which means it is the foundation for all of a
computer's computering. Other common Operating Systems are Mac and Windows. 

Linux is a lovely computer operating system with amazing support for using a
terminal (i.e. no point and click, no mouse, only typing). Believe it or not,
that turns out to be an _amazing_ thing for programmers!

The Windows analog to linux capabilities would be "Administrator" and some
notion of privileges (sorry, I am not a Windows expert). Where Windows is used
for most desktops, Linux basically runs most of the world's [computer
infrastructure (digital sand castles)](../devops-is-like-building-sand-castles)
for hosting websites, databases, etc.

### The High Points

- Super powers are special, which means they are not given often. (If they were,
  they would not be "super"). In an ideal world, not many services would require /
  have access to these privileged capabilities, so that your digital world is more
  secure!
    - Say a crime requires super strength, and the crime occurs. Who did it!? Well if only one
      person has super strength, it is much easier to point the finger than if everyone does!
    - To maintain the analogy, if nobody has super strength, the crime _cannot_ happen!
    
- Part of the value of containers (mini-sand-castles) in the modern world is
  _precisely_ because of the isolation that they provide, often in terms of access
  and *capabilities*. You can give [linux capabilities to containers
  too](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities),
  but most admins would rather not (see the previous point)!

- Super powers are widely varied. So are linux capabilities! One will let you
  change ownership on random files (like being the title office for the computer -
  `CAP_CHOWN`), while another will let you define the time (who wouldn't love this
  in real life? - `CAP_SYS_TIME`), and another will allow you to awake the
  computer at any time (`CAP_WAKE_ALARM`). Hopefully it makes sense why these
  should be closely guarded. Just like you, the computer does not want
  everyone to be able to wake them in the middle of the night for no reason!

### The Breakdown

I love the idea of super powers just as much as the next person. Ok, you're
right, my 5 year old probably likes super powers more. In any case, "real life"
super powers are generally more practical and useful than linux capabilities for
things like fighting crime, capturing criminals, and saving the world.

Moreover, they are definitely going to stick in your mind a lot more vividly
than `CAP_SYS_TTY_CONFIG`. And who knows what a TTY is?
([TeleTypeWriter](https://www.howtogeek.com/428174/what-is-a-tty-on-linux-and-how-to-use-the-tty-command/) - a place to type
commands - now you know!)

This is why software people _love_
[documentation](https://man7.org/linux/man-pages/man7/capabilities.7.html), even
if it is not pretty, because there is basically zero chance of my remembering
stuff like that.

### Closing

Linux capabilites may be less magical, mystifying, and memorable than super
powers, but for people (like me) who play in digital sand castles for a living,
they are definitely the next best thing.

After all, I may not be fighting crime and saving the world, but keeping the
hackers from having super powers just may prevent the need for crime fighting in
the first place!
]]></content:encoded>
            <category>Analogy</category>
            <category>DevOps</category>
            <category>General</category>
            <category>Linux</category>
            <category>Tech</category>
            <category>Security</category>
        </item>
        <item>
            <title><![CDATA[Using sssd in a Playground Without TLS]]></title>
            <link>/blog/sssd-without-tls</link>
            <guid>sssd-without-tls</guid>
            <pubDate>Sat, 01 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[
`sssd` has established itself as the most common way to provision system
accounts via LDAP or Active Directory on linux servers across all linux
distributions. However, working with it can be tricky! TL;DR; We show an example of using `sssd` to contact an LDAP server that is
listening  ...]]></description>
            <content:encoded><![CDATA[
[`sssd`](https://sssd.io/) has established itself as the most common way to provision system
accounts via LDAP or Active Directory on linux servers across all linux
distributions. However, working with it can be tricky!

## TL;DR;

We show an example of using `sssd` to contact an LDAP server that is
listening on port 389 (in plaintext / no TLS). This is _NOT_ a good
idea in any production environment. However, it can be important
and helpful in playgrounds, learning, or other experiments. The magic
configuration is `ldap_auth_disable_tls_never_use_in_production = true`.

## Why

It is quite straightforward to stand up an LDAP server listening in plaintext. My
favorite mechanism is using the [`openldap`
container](https://github.com/osixia/docker-openldap)
, [although there are other options](https://github.com/nitnelave/lldap).

```bash
docker run -it --rm -p 389:389 osixia/openldap:latest
```

However, if you have a toy linux container running `sssd`, this is unfortunately not an obvious option! Why, you ask?
This is all just a dev playground!? Right. Well the `sssd` maintainers want to be very careful about not creating
security vulnerabilities or letting their users get hacked. This means you have to work hard to open yourself up to this
type of vulnerability in your playground.

Specifically, we will use the `ldap_auth_disable_tls_never_use_in_production` setting.

> NOTE: Do not use this setting in any "real" environment with "real" users, passwords, sensitive data, etc.

## Give it a Shot

### Create Users

First, we need to create and populate our LDAP server. Let's go ahead and do that. It is easiest if we create a file
with users first. For a more advanced LDIF file, check
out [the repository associated with this post](https://github.com/colearendt/container-playground):

_users.ldif_
```ldif
version: 1

## Entry 1: dc=angl,dc=dev
#dn: dc=angl,dc=dev
#dc: angl
#o: Angl Dev
#objectclass: top
#objectclass: dcObject
#objectclass: organization
#
## Entry 2: cn=admin,dc=angl,dc=dev
#dn: cn=admin,dc=angl,dc=dev
#cn: admin
#description: LDAP administrator
#objectclass: simpleSecurityObject
#objectclass: organizationalRole
#userpassword: {SSHA}+FquX8RcwTtBPo7mu2pgSvjaQYX9HpCL
#
#
# Entry 3: cn=engineering_group,dc=angl,dc=dev
dn: cn=engineering_group,dc=angl,dc=dev
cn: engineering_group
gidnumber: 500
memberuid: joe
memberuid: julie
objectclass: posixGroup
objectclass: top

# Entry 4: dc=engineering,dc=angl,dc=dev
dn: dc=engineering,dc=angl,dc=dev
dc: engineering
description: The Engineering Department
o: Engineering
objectclass: dcObject
objectclass: organization
objectclass: top


# Entry 5: cn=joe,dc=engineering,dc=angl,dc=dev
dn: cn=joe,dc=engineering,dc=angl,dc=dev
cn: joe
gidnumber: 500
givenname: Joe
homedirectory: /home/joe
loginshell: /bin/sh
mail: joe@angl.dev
objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: top
sn: Golly
uid: joe
uidnumber: 1000
userpassword: {MD5}j/MkifkvM0FmlL6P3C1MIg==

# Entry 9: cn=julie,dc=engineering,dc=angl,dc=dev
dn: cn=julie,dc=engineering,dc=angl,dc=dev
cn: julie
gidnumber: 500
givenname: Julie
homedirectory: /home/julie
loginshell: /bin/sh
mail: julie@angl.dev
objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: top
sn: Jolly
uid: julie
uidnumber: 1001
userpassword: {MD5}FvEvXoN54ivpleUF6/wbhA==
```

You will notice that the first two entries are commented out. They are included to represent a _complete_ LDIF file.
However, the `osixia/docker-openldap` container help us by provisioning these automatically.

Further, you will notice that passwords are included. This makes things easier for our playground, but is _definitely_ a
bad idea in real life / production applications.

### Create LDAP Server

Now let's create the server itself!

```bash
docker network create playground-network
docker run \
  -d --name openldap --rm \
  -p 389:389 \
  --network playground-network \
  -v $(pwd)/users.ldif:/container/service/slapd/assets/config/bootstrap/ldif/50-bootstrap.ldif \
  -e LDAP_TLS=false \
  -e LDAP_DOMAIN="angl.dev" \
  -e LDAP_ADMIN_PASSWORD="admin" \
  osixia/openldap:1.5.0 \
  --copy-service --loglevel debug
```

And check that it is working

```bash
docker exec -it openldap ldapsearch -D cn=admin,dc=angl,dc=dev -b dc=angl,dc=dev -w admin cn
docker exec -it openldap ldapsearch -D cn=admin,dc=angl,dc=dev -b dc=angl,dc=dev -w admin cn=julie \*
```

If you look carefully, you will notice that:

1. We created a persistent network for our containers to share
2. We provisioned users from our `ldif` file
3. We disabled TLS on the service
4. We bumped up the logging verbosity for debugging purposes

These are all useful tidbits to dig into if you are not familiar!

### Configure sssd Server

It is possible to run `sssd` in a fairly vanilla `ubuntu:jammy` container.

```bash
docker run -it --name sssd --rm --network playground-network ubuntu:jammy bash

apt update && apt install -y sssd ldap-utils vim
```

Then you need to create your `sssd.conf` file. Notice our magic
option `ldap_auth_disable_tls_never_use_in_production=true`. This will be the magic that makes things work for us!
```bash
cat << EOF > /etc/sssd/sssd.conf
[sssd]
config_file_version = 2
services = nss, pam
domains = LDAP

[nss]
filter_users = root,named,avahi,haldaemon,dbus,radiusd,news,nscd
filter_groups =

[pam]

[domain/LDAP]
id_provider = ldap
auth_provider = ldap
chpass_provider = ldap
sudo_provider = ldap
enumerate = true
# ignore_group_members = true
cache_credentials = false
ldap_schema = rfc2307
ldap_uri = ldap://openldap:389
ldap_search_base = dc=angl,dc=dev
ldap_user_search_base = dc=angl,dc=dev
ldap_user_object_class = posixAccount
ldap_user_name = uid

ldap_group_search_base = dc=angl,dc=dev
ldap_group_object_class = posixGroup
ldap_group_name = cn
ldap_id_use_start_tls = false
ldap_tls_reqcert = never
ldap_tls_cacert = /etc/ssl/certs/ca-certificates.crt
ldap_default_bind_dn = cn=admin,dc=angl,dc=dev
ldap_default_authtok = admin
access_provider = ldap
ldap_access_filter = (objectClass=posixAccount)
min_id = 1
max_id = 0
ldap_user_uuid = entryUUID
ldap_user_shell = loginShell
ldap_user_home_directory = homeDirectory
ldap_user_uid_number = uidNumber
ldap_user_gid_number = gidNumber
ldap_group_gid_number = gidNumber
ldap_group_uuid = entryUUID
ldap_group_member = memberUid
ldap_auth_disable_tls_never_use_in_production = true
use_fully_qualified_names = false
ldap_access_order = filter
debug_level=6
EOF
chmod 600 /etc/sssd/sssd.conf
```

Now let's start the `sssd` service
```bash
sssd -i
# should see some log messages that suggest things are happening!
```

### Be sure it works!

Now let's make sure that this works by starting another shell in our `jammy` container.

```bash
docker exec -it sssd bash

id joe
# uid=1000(joe) gid=500(engineering_group) groups=500(engineering_group)
id julie
# uid=1001(julie) gid=500(engineering_group) groups=500(engineering_group)
```

## Using `docker-compose`

For playground environments like this, `docker-compose` makes this setup much easier to architect and reuse. You can
use [my example compose setup](https://github.com/colearendt/container-playground) if you prefer.

```bash
cd compose/
docker network create playground-network
NETWORK=playground-network docker-compose -f ldap.yml -f sssd.yml -f network.yml up -d
docker exec -it compose_sssd_1 bash

sssd -i >/tmp/sssd.log 2>&1 &
id joe
```

## Review

Well done! You have successfully started your own `sssd` container. Although this is very much a toy, it is a
great "jumping off point" to learn and understand how `sssd` works in more detail!

Any time you need a toy LDAP server for `sssd`, just remember: `ldap_auth_disable_tls_never_use_in_production = true`.
]]></content:encoded>
            <category>Docker</category>
            <category>Container</category>
            <category>LDAP</category>
            <category>sssd</category>
            <category>DevOps</category>
            <category>SysAdmin</category>
            <category>HowTo</category>
            <category>openldap</category>
        </item>
        <item>
            <title><![CDATA[Helm Intro and Helm Cheatsheet]]></title>
            <link>/blog/helm-cheatsheet</link>
            <guid>helm-cheatsheet</guid>
            <pubDate>Mon, 10 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[
Below is an introduction to Helm! If you want to skip to the
cheatsheet, you can download it
here. What is Helm According to its own docs, Helm is "the" package
manager for Kubernetes. What does this mean? It's a way of keeping track of all your Kubernetes  ...]]></description>
            <content:encoded><![CDATA[
Below is an introduction to Helm! If you want to [skip to the
cheatsheet](#cheat-sheet), you can [download it
here](https://www.analogous.dev/download/cheatsheet/helm.pdf).

## What is Helm

According to [its own docs](https://helm.sh/docs/), Helm is "the" package
manager for Kubernetes. What does this mean?

It's a way of keeping track of all your Kubernetes stuff!

Helm as I describe it is a mechanism for packaging and parameterizing standard
Kubernetes YAML files. It uses [Go
Templating](https://blog.gopheracademy.com/advent-2017/using-go-templates/) for
most of this mechanism, and adds a layer of version / metadata tracking as
well. All of this packaged up into tarballs used by a client-side-only (as of
`helm` v3) CLI.

So basically: Helm = YAML + Go Templating + Versioning + Tar balls.

## Why use it?

Why use it? There are lots of alternatives out there, and many purported "Helm replacements,"
but Helm has yet to give up its throne, and I have not found anything better
for my own use cases... yet. So what are Helm's strengths?

I will do my best not to wax poetic. I am biased and a big fan of Helm. As a layer of
abstraction between an application and Kubernetes, I think it is a fantastic asset.

In particular, I think this is because:

- No runtime dependency
- Client-side only utility
- Data stored server side for collaboration
- output represents native Kubernetes objects (i.e. interoperable with other tools)
- `helm template` gives rapid feedback on iterating and testing
- plain text file output / diffs is very easy to parse

As a system administrator, it is nice because it offers:

- Version pinning for reproducibility
- Everything is open source tarballs, so dependencies are easy to track and introspect
- application vendors will ideally maintain their own chart and good NEWS files

## When to use it?

So that's _what_ it is, and _why_ it is desirable. But _when_ is it useful?

I find that helm particularly shines in a handful of situations:

- Managing an array of applications deployed on Kubernetes
- Packaging your own application for use by customers
- Encoding complex knowledge about "how to run an application" (to an extent,
  then you get to
  [operators](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/))
- To set up easy "roll-back" policies for applications that support the
  behavior

Occasionally a wrapper like [ArgoCD](https://argoproj.github.io/cd/), [Flux](https://fluxcd.io/)
, [helmfile](https://github.com/helmfile/helmfile), or [pulumi](https://www.pulumi.com/docs/get-started/kubernetes/)
will be useful to manage your helm deployments too, so
that you don't have to keep track of a bunch of CLI commands.

## When not to use it?

Helm can definitely be overkill in some "hello world" or very simple deployment
situations. Unfortunately, it also **does not have a great answer for
[CRDs](https://helm.sh/docs/topics/charts/#custom-resource-definitions-crds)
yet**. Moreover, it is **only useful for Kubernetes**, so if you are unfamiliar
with Kubernetes, it will have limited utility for you.

The other case where it may not be useful is in some **internal applications**.
Maintaining a helm chart for an application can end up being a sizable amount
of work, and they do not allow arbitrary inputs, so if you miss some key (i.e.
"imagePullSecrets,") you can end up spending a lot of time key-chasing across
your charts. I have heard of folks using [Kustomize](https://kustomize.io/) in
such a situation, although another option is to use a meta chart (one chart for
many apps) or Functions-as-a-Service (FaaS) framework like
[Serverless](https://www.serverless.com/),
[OpenFaas](https://www.openfaas.com/), [Knative](https://knative.dev/docs/),
etc.

Also, helm charts do have a **complexity ceiling**. Go Templating provides
lots of flexibility, but being DRY is hard, and there are many parts of the
process that are not optimal from a software development point of view. As
charts become more complex, an
[operator](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/)
becomes increasingly beneficial as a mechanism to provide better software
semantics to the application management process. However, the learning curve
for operators can also be a bit steep.

Finally, helm charts unfortunately **do not have hard-and-fast standards about how values are used** across the
ecosystem. As a result, you will often encounter wild variations in chart quality, value naming, and value behavior.

## Hello World

Let's get started on a hello world example! First, you need to [install
kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl), [install
helm](https://helm.sh/docs/intro/install/), and have a kubernetes cluster
available.  Once those things are taken care of, a hello world example of a
helm deployment is pretty straightforward!

For this example, we will use my [generic
chart](https://github.com/colearendt/helm/tree/main/charts/generic), useful for
deploying simple services with standard configuration or helm needs.

We are also going to use [this hello-world container](https://hub.docker.com/r/paulbouwer/hello-kubernetes).

First, add the repository that houses our example chart:
```
helm repo add colearendt https://colearendt.github.io/helm/
```

You can look at the values available for the chart:
```
helm show values colearendt/generic

# I like to pipe it to a pager for search and such
helm show values colearendt/generic | less
```

Then create a YAML file called _my-values.yaml_ to hold values:

_my-values.yaml_
```
image:
  repository: paulbouwer/hello-kubernetes
  tag: "1.10"
pod:
  port: 8080
```

Then template the output:
```
helm template hello-world colearendt/generic -f my-values.yaml
```

And install it into the Kubernetes cluster!

```
helm upgrade --install hello-world colearendt/generic -f my-values.yaml
```

Then you should be able to see the app deployed:
```
helm list
kubectl get pods
```

And view the service in your web browser at http://localhost:8080:
```
kubectl port-forward svc/hello-world-generic 8080:80
```

### Clean Up

If you want to clean up after yourself:

```bash
# delete the helm release
helm delete hello-world

# delete the repository reference
helm repo remove colearendt
```

Unfortunately, I have not taken much time to dive into troubleshooting here! If you are hitting issues,
please [shoot me an email](mailto:info@analogous.dev) - I would love to have feedback on what to improve! Maybe
someday I will take the time to set up comments 😅

## Best Practices

So now you have a "Hello World" deployment under your belt. However, it also helps to keep in mind some best practices
as you keep improving. Below is a handful of helm chart conventions that may be unfamiliar if you are new to the
community:

- Make sure to pin helm chart versions with the `--version` flag
- Maintain a `NEWS.md` file (or read the `NEWS.md` file) to keep track of
  changes between versions
- Keep an eye out for "upgrading directions" in the `README.md` or elsewhere
- Use `helm show values` to see the default values (and comment strings
  associated). Ideally these are presented or discussed in a `README` as well.
- Avoid [`sub-charts`](https://helm.sh/docs/chart_template_guide/subcharts_and_globals/) if you can. It is tempting as a
  DRY software principle, but turns out to be a pretty advanced topic with lots of tricky edge cases. In particular,
  namespaces can be painful.

## Cheat Sheet

I took the time to arrange a "cheat sheet" of my favorite helm commands and the
contexts in which they are useful. It was inspired by [RStudio's array of excellent
cheat sheets for the R community](https://www.rstudio.com/resources/cheatsheets/).

A hit-list of some of the most useful commands:

- `helm show values chartrepo/chartname`
- `helm template releasename chartrepo/chartname`
- `helm upgrade --install releasename chartrepo/chartname`
- `helm repo add https://repourl`
- `helm repo list`
- `helm search repo`
- `helm info`
- `helm list`

And the cheat-sheet itself can be downloaded [here](https://www.analogous.dev/download/cheatsheet/helm.pdf).

<div style="position: relative; text-align: center">
  <div>
    <iframe src="https://dl.angl.dev/cheatsheet/helm.pdf" style="height: 230px; width: auto">
    </iframe>
  </div>
  <a href="https://analogous.dev/download/cheatsheet/helm.pdf" target="_blank" style="position:absolute; top:0; 
      left:0; display:inline-block; width:100%; height:100%; z-index:5;">
  </a>
</div>

## What's Next?

The [helm project](https://helm.sh/) is an open source project. There is much
that could be improved, and many applications that need helm charts or need
improved helm charts. You can make a difference! If you are interested in
learning more, check out the [helm tag](/?tag=Helm) on this blog to see other writing on
the topic, and start poking around on [ArtifactHub](https://artifacthub.io/),
where lots of charts are centralized for easier searching!
]]></content:encoded>
            <category>HowTo</category>
            <category>Helm</category>
            <category>DevOps</category>
            <category>Tech</category>
            <category>Kubernetes</category>
            <category>Container</category>
        </item>
        <item>
            <title><![CDATA[Websockets and Cloudflare Workers]]></title>
            <link>/blog/websockets-and-cloudflare-workers</link>
            <guid>websockets-and-cloudflare-workers</guid>
            <pubDate>Sat, 22 Feb 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[
Websockets and Workers! A match made in heaven! Or not. Stateful, persistent communication? With ephemeral, floaty web workers that have almost no state? How in the world can those interoperate!? Leave it to the talented folks at Cloudflare to build a fantastic integration. This is  ...]]></description>
            <content:encoded><![CDATA[
Websockets and Workers! A match made in heaven!

Or not.

Stateful, persistent communication? With ephemeral, floaty web workers that have almost no state?

How in the world can those interoperate!?

Leave it to the talented folks at Cloudflare to build a fantastic integration. This is way easier than you might expect!

## Getting Started

To get set up, you want to create a standard boilerplate web worker. I love starting with the `Application Starter`:

```
wrangler init
# give it a name...
# choose "Application Starter"
# then "API starter (OpenAPI compliant)"
```

(If you don't have `wrangler` installed yet, then you need to go take care of that!)

Now we're set up! Let's get this thing rolling!

## Get it working locally

As with most workers (and other projects) - the first step is to get things working locally. Thankfully, wrangler has a pretty killer setup for this. So let's get things started right out of the gate.

Navigate to the directory you just created and:
```
wrangler dev
```

### Add websocket

Now we're going to add a simple websocket server to the `endpoints` directory

_endpoints/websocket.ts_
```
import { OpenAPIRoute } from "chanfana";
import { Context } from "hono";

export class WebSocket extends OpenAPIRoute {
	async handle(c: Context) {
		// optional - if you want to be strict
		const upgradeHeader = c.req.header("Upgrade");
		if (!upgradeHeader || upgradeHeader !== "websocket") {
			return new Response("Expected Upgrade: websocket", { status: 426 });
		}

		const webSocketPair = new WebSocketPair();
		const [client, server] = Object.values(webSocketPair);

		server.accept();
		server.addEventListener("message", (event) => {
			console.log(event);
			server.send("Hello from the other side!");
		});

		server.addEventListener("close", () => {
			console.log("WebSocket closed");
		});

		return new Response(null, {
			status: 101,
			webSocket: client,
		});
	}
}
```

And integrate into the `index.ts` file:

_index.ts_

```
import { WebSocket } from "./endpoints/websocket";

...

openapi.all("/api/ws", WebSocket);
```

### Test it!

Now how do you test websockets!? Well there are some cool tools out there on the web. 

But I just wanted to test locally, so I made a little python script to test with:

_wstest.py_
```
import argparse
from websocket import create_connection

def main():
    parser = argparse.ArgumentParser(description='WebSocket test client')
    parser.add_argument('--domain', '-d', nargs='?', default='localhost:8787', help='The hostname to connect to (e.g. localhost:8787)')
    parser.add_argument('--tls', '-t', action='store_true', help='Use TLS')
    parser.add_argument('--path', '-p', nargs='?', default='/api/ws', help='The path to connect to (e.g. /api/ws)')
    args = parser.parse_args()

    if args.tls:
        protocol = "wss"
    else:
        protocol = "ws"
    
    # ensure that args.domain does not have a protocol prefix
    # parse domain as a url
    domain = args.domain.split('://')[1]
    path = args.path

    # ensure that there is only one slash between domain and path
    if path.startswith('/'):
        path = path[1:]
    if domain.endswith('/'):
        domain = domain[:-1]

    ws_url = f"{protocol}://{domain}/{path}"
    ws = create_connection(ws_url)
    print(f"--> Connected to {ws_url}")
    print("--> Sending 'Hello, World'...")
    ws.send("Hello, World")
    result = ws.recv()
    print(f"<-- Received '{result}'")

    try:
        while True:
            msg = input('--> ')
            ws.send(msg)
            result = ws.recv()
            print(f"<-- Received '{result}'")
    except KeyboardInterrupt:
        print("\nClosing connection...")
    finally:
        ws.close()

if __name__ == '__main__':
    main()
```

You will need one little library:
```
pip install websocket-client
```

And then you should be good to test!

```
# use the port that your websocket worker is running on from `wrangler dev`
python3 ./wstest.py -d localhost:59462
```

## Deploy to Cloudflare

This should be pretty straightforward! Deploy your worker!

```
wrangler deploy
```

Then redirect the websocket test script and see what happens! (Make sure to include the TLS flag!):

```
python3 ./wstest.py -d my-deployed-domain.workers.dev -t
```

## More debugging

There are lots of snags you can hit with websockets, especially if you start using the browser.

- Beware of CORS
- Be mindful of `ws://` vs. `wss://` (for TLS connections) protocol issues
- Be careful of client code, which needs to handle reconnections and can cause problems disconnecting unintentionally
- Firewalls, enterprise security restrictions, and reverse proxies can cause all sorts of weird issues with websockets!
- It is probably worth using socket.js or having some fallback mechanisms handled... but more on that another time.

## Successful Websocketing!

Congratulations! You have hopefully deployed a websocket server successfully to Cloudflare! 

I would love to hear how it went, if you ran into issues, how this article can be improved, or what you ended up building! [Let me know!](mailto:info@analogous.dev)

Thanks!
]]></content:encoded>
            <category>HowTo</category>
            <category>Websockets</category>
            <category>DevOps</category>
            <category>Tech</category>
            <category>Cloudflare</category>
            <category>Web Worker</category>
        </item>
    </channel>
</rss>