<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mya</title>
    <description>The latest articles on DEV Community by Mya (@msmyajaye).</description>
    <link>https://dev.to/msmyajaye</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F328362%2Fd76215af-a357-460b-8436-6a6119103b44.png</url>
      <title>DEV Community: Mya</title>
      <link>https://dev.to/msmyajaye</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/msmyajaye"/>
    <language>en</language>
    <item>
      <title>Extending my Security System with a Raspberry Pi Network Bridge</title>
      <dc:creator>Mya</dc:creator>
      <pubDate>Wed, 16 Jun 2021 15:37:55 +0000</pubDate>
      <link>https://dev.to/msmyajaye/extending-my-security-system-with-a-raspberry-pi-network-bridge-1h7m</link>
      <guid>https://dev.to/msmyajaye/extending-my-security-system-with-a-raspberry-pi-network-bridge-1h7m</guid>
      <description>&lt;p&gt;It doesn't happen a lot, but every so often I come across a device that isn't wi-fi supported. This latest case was my security system. On one hand, I like that my cameras aren't taking up bandwidth on my home network and that the system is largely a closed loop. On the other, not having access to my security system without having it tethered into the router is a bit of a pain. For one, my home networking setup isn't that elegant (yet). Second, the last thing I want to do is have more stuff out in the open, co-located with my router. So I decided to get a little creative. Sure, I could've bought a wi-fi adapter, but where's the fun in that. On top of that, I had some other reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I didn't want to spend money on an adapter for this system. Even though they aren't that expensive, I would likely need to wait for one to come in which would probably happen &lt;em&gt;after&lt;/em&gt; I left for my trip.&lt;/li&gt;
&lt;li&gt;Eventually, I want to do some real-time video processing and having a device closer to the box is promising. This would let me process data straight from the box rather than needing to transmit all that information over wi-fi to another machine.&lt;/li&gt;
&lt;li&gt;Finally, I already have more than a dozen pis around. For the first version of this, I wound up using a 3b+. I would likely upgrade this later on when I add the real-time processing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Today, I show how I set up and configured a Raspberry Pi to act as a WAN client for a connected device. There are a handful of similar guides out there, but finding one for this specific direction / configuration was difficult. Here are some similar guides I used as reference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.raspberrypi.org/documentation/configuration/wireless/access-point-bridged.md" rel="noopener noreferrer"&gt;Setting up a Raspberry Pi as a bridged wireless access point&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.raspberrypi.org/documentation/configuration/wireless/access-point-routed.md" rel="noopener noreferrer"&gt;Setting up a Raspberry Pi as a routed wireless access point&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raspberrypi.stackexchange.com/questions/88214/setting-up-a-raspberry-pi-as-an-access-point-the-easy-way" rel="noopener noreferrer"&gt;Setting up a Raspberry Pi as an access point&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://willhaley.com/blog/raspberry-pi-wifi-ethernet-bridge/" rel="noopener noreferrer"&gt;Raspberry Pi 4 Model B WiFi Ethernet Bridge&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of these guides, "Raspberry Pi 4 Model B WiFi Ethernet Bridge" is the closest. Unlike many of these guides, I run an Ubuntu arm64 image instead of Raspbian OS. The process is largely the same, but some tooling is different. For example, Ubuntu works with netplan by default. In addition to that, we have a slightly higher resource footprint, but it's not the biggest concern for what this little one is doing. To help provide a little context as to what's going on here, I've put together this diagram (and alt-text):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxk1ll6zkuv3vx77q6m35.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxk1ll6zkuv3vx77q6m35.png" alt="Alt-text below." width="577" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alt-text for diagram:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At the top, there's a wi-fi router that's connected to the public internet who's &lt;code&gt;wlan0&lt;/code&gt; interface holds the &lt;code&gt;192.168.4.1&lt;/code&gt; IP address.&lt;/li&gt;
&lt;li&gt;Connected to the router over wi-fi are two machines.

&lt;ul&gt;
&lt;li&gt;First is Mya's laptop who's &lt;code&gt;wlan0&lt;/code&gt; interface holds the &lt;code&gt;192.168.4.10&lt;/code&gt; IP address.&lt;/li&gt;
&lt;li&gt;Second is a Raspberry Pi board who's &lt;code&gt;wlan0&lt;/code&gt; interface holds the &lt;code&gt;192.168.4.30&lt;/code&gt; IP address
and who's &lt;code&gt;eth0&lt;/code&gt; interface holds the &lt;code&gt;192.168.10.1&lt;/code&gt; IP address.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The Security hub connects to the Raspberry Pi's using an ethernet cable.&lt;/li&gt;

&lt;li&gt;Some number of cameras connect to the security hub using a cable.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Flash the Image
&lt;/h2&gt;

&lt;p&gt;For simplicity, I used my &lt;code&gt;cloud-init&lt;/code&gt; base from my &lt;a href="https://github.com/mjpitz/rpi-cloud-init" rel="noopener noreferrer"&gt;rpi-cloud-init&lt;/a&gt; repository to flash my Raspberry Pi (w/ wi-fi access). This gives it a similar look and feel to many of the other microcomputers I have around the house. In addition to that, I get some reasonable default security configurations with that setup (i.e. private key access, no root, custom user, etc). To get a machine up and running, follow Steps 1 - 4 on the projects &lt;a href="https://github.com/mjpitz/rpi-cloud-init/blob/main/README.md" rel="noopener noreferrer"&gt;README.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once the board is running, we'll need to make some additional modifications to the networking setup. The &lt;code&gt;network-config&lt;/code&gt; provided in the &lt;code&gt;cloud-init&lt;/code&gt; setup generates &lt;code&gt;/etc/netplan/50-cloud-init.yaml&lt;/code&gt;. We will be creating a new &lt;code&gt;/etc/netplan/60-static-eth0.yaml&lt;/code&gt; file with the following contents.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;ethernets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;eth0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;dhcp4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;dhcp6&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;192.168.10.1/24&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;gateway4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;192.168.10.1&lt;/span&gt;
      &lt;span class="na"&gt;nameservers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;192.168.10.1&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configures the devices &lt;code&gt;eth0&lt;/code&gt; interface to use the static IP &lt;code&gt;192.168.10.1&lt;/code&gt;. With the new file, we need to generate and apply the changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo netplan generate
$ sudo netplan apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can verify the changes by inspecting the &lt;code&gt;ifconfig&lt;/code&gt; (you may need to &lt;code&gt;apt-get install net-tools&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ifconfig
mjpitz@ip-192-168-4-30:~$ ifconfig
eth0: flags=4163&amp;lt;UP,BROADCAST,RUNNING,MULTICAST&amp;gt;  mtu 1500
        inet 192.168.10.1  netmask 255.255.255.0  broadcast 192.168.10.255    ###### expected change

lo: flags=73&amp;lt;UP,LOOPBACK,RUNNING&amp;gt;  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0

wlan0: flags=4163&amp;lt;UP,BROADCAST,RUNNING,MULTICAST&amp;gt;  mtu 1500
        inet 192.168.4.30  netmask 255.255.255.0  broadcast 192.168.4.255

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Software
&lt;/h2&gt;

&lt;p&gt;In order to get this to work, I needed to install some basic software components.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;isc-dhcp-server&lt;/code&gt; is responsible for providing configuration to machines on the underlying network.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dnsmasq&lt;/code&gt; intermediates DNS queries between connected devices and the upstream router.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;netfilter-persistent&lt;/code&gt; and &lt;code&gt;iptables-persistent&lt;/code&gt; preserve firewall and iptables rules on reboot (respectively). We will be using iptables to act as our core routing layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can install these components using &lt;code&gt;apt-get&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo apt-get update -y &amp;amp;&amp;amp; sudo apt-get install -y isc-dhcp-server dnsmasq
$ sudo DEBIAN_FRONTEND=noninteractive apt install -y netfilter-persistent iptables-persistent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, we'll need to configure each system. Note, dnsmasq may be having trouble starting up. This is OK, we'll fix it.&lt;/p&gt;

&lt;h3&gt;
  
  
  dhcp
&lt;/h3&gt;

&lt;p&gt;First up, let's configure dhcp. dhcp delivers network configuration to devices attempting to connect to a given router. For some reason, I couldn't get the system to work without this installed. I know dnsmasq supports dhcp, but without this process I was getting an error and couldn't obtain a network address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dnsmasq-dhcp[3685]: DHCP packet received on eth0 which has no address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we'll want to modify &lt;code&gt;/etc/default/isc-dhcp-server&lt;/code&gt; to point &lt;code&gt;INTERFACESv4="eth0"&lt;/code&gt;. This will ensure that the dhcp server responds to dhcp requests (similar to how your wi-fi router works). Next we need to configure the dhcp server to know about the subnet that we're allocating to it. To do this, we'll edit &lt;code&gt;/etc/dhcp/dhcpd.conf&lt;/code&gt; to contain the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# communicates that the Raspberry Pi will act as a router for requests.
host router {
  hardware ethernet "mac of eth0, obtained from ifconfig.eth0.ether attribute";
  fixed-address 192.168.10.1;
}

# communicates how to manage the associated subnet(s)
subnet 192.168.10.0 netmask 255.255.255.0 {
  range 192.168.10.2 192.168.10.254;
  option routers 192.168.10.1;
  option dns-name-servers 192.168.10.1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once dhcp has been configured, we'll need to restart the service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo service isc-dhcp-server restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the dhcp server has been restarted, it should be operating with the new configuration. We can verify that it's running properly using &lt;code&gt;sudo service isc-dhcp-server status&lt;/code&gt; or by tailing logs with &lt;code&gt;sudo journalctl -u isc-dhcp-server&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  dnsmasq
&lt;/h3&gt;

&lt;p&gt;Next we're going to configure dnsmasq. dnsmasq provides discovery logic for requests via a DNS interface. In the default configuration, it conflicts with systemd-resolved (which is why the service likely couldn't start). We'll be modifying its configuration to bind dnsmasq to &lt;code&gt;eth0&lt;/code&gt; allowing it to respond to requests there instead of conflicting on &lt;code&gt;wlan0&lt;/code&gt;. To do so, we'll backup the existing configuration and create a new file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo mv /etc/dnsmasq.conf /etc/dnsmasq.conf.orig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we'll create a new &lt;code&gt;/etc/dnsmasq.conf&lt;/code&gt; with the following content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface=eth0                              # what interface to bind to.
listen-address=192.168.10.1                 # what addresses to listen on.
bind-interfaces                             # binds to all interfaces, even if we're only listening on some.
server=192.168.4.1                          # sets the upstream router as a dns server for delegation.
dhcp-range=192.168.10.2,192.168.10.254,12h  # configures the lease time for dhcp requests.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we configure dnsmasq, we'll need to restart it for our changes to take effect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo service dnsmasq restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once restarted, dnsmasq should come up without an issue. We can check its status using &lt;code&gt;sudo service dnsmasq status&lt;/code&gt; or by tailing logs with &lt;code&gt;sudo journalctl -u dnsmasq&lt;/code&gt;. Before requests can successfully pass through the Raspberry Pi, we need to configure some lower level networking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux networking
&lt;/h3&gt;

&lt;p&gt;The last part of this configuration requires modifying the Linux networking components.  To do this, we'll first want to configure the kernel to do packet forwarding for IPv4. Open &lt;code&gt;/etc/sysctl.conf&lt;/code&gt;, uncomment the line containing &lt;code&gt;net.ipv4.ip_forward=1&lt;/code&gt;, and save. To reload the configuration without a full system reboot, run the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo sysctl --system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we'll instruct IP tables to allow masquerading over the &lt;code&gt;wlan0&lt;/code&gt; interface. This allows requests to pass through the network interfaces with very little software in between. Once modified, we'll need to persist changes using &lt;code&gt;netfilter-persistent&lt;/code&gt; so changes persist between reboots.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE
$ sudo netfilter-persistent save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verifying client
&lt;/h3&gt;

&lt;p&gt;Once all of this is done, traffic should flow from the connected device through to other devices on the network or internet. We can verify this by connecting a device to the Raspberry Pi. It should successfully negotiate an IP address from the dhcp server (likely &lt;code&gt;192.168.10.2&lt;/code&gt;). In addition to that, we should be able to make requests from the client (i.e. the security system) to other devices on the network or internet.&lt;/p&gt;

&lt;p&gt;Once I connected my system, I saw that it successfully obtain an IP from the server using the UI they provide. After, I sent a test email to make sure the request would successfully go through. While doing this, I brought up a terminal on the Raspberry Pi to watch the traffic flow (&lt;code&gt;sudo tcpdump -X -i eth0&lt;/code&gt;). At this point, I'm leagues ahead of where I was before. When I went to set up cloud backups, I learned that needed to be done through their mobile app. As a result, I needed to expose the client application ports through the Raspberry Pi in order to connect from my devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exposing ports
&lt;/h3&gt;

&lt;p&gt;The system I bought exposed two ports of interest to me. &lt;code&gt;9000&lt;/code&gt; provides a media application that can interface with my security systems mobile application. And &lt;code&gt;554&lt;/code&gt;, which exposes a real-time streaming protocol (RTSP) that allows for remote observation of cameras. To quickly proxy these ports, we can use our good ole friend &lt;code&gt;iptables&lt;/code&gt; again. By executing the following commands, we can successfully route requests from a port on the Raspberry Pi to a port on the client. Note, &lt;code&gt;${CLIENT_IP}&lt;/code&gt; should be the IP your client obtained. &lt;code&gt;${PORT}&lt;/code&gt; would be the port you want to expose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo iptables -A PREROUTING -t nat -i wlan0 -p tcp --dport ${PORT} -j DNAT --to ${CLIENT_IP}:${PORT}
$ sudo iptables -A FORWARD -p tcp -d ${CLIENT_IP} --dport ${PORT} -j ACCEPT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that in my case, the RTSP port needed to also be exposed over &lt;code&gt;udp&lt;/code&gt; for better streaming support. If you're exposing ports through a Raspberry Pi, you'll need to verify what protocols to expose them over. Also, don't forget to save your updated IP table rules, otherwise changes will be lost on reboot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo netfilter-persistent save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;That was it. Once everything was setup, I was able to connect to the real-time stream from my devices and monitor my whole system. The mobile application also connected to the security system fine and was able to let me configure cloud backups. While this is where I stop today, I have some more plans for this in the future. As I prepare for some upcoming trips, I want to make sure I can access this system outside of my home network safely and securely. While I don't expect this to become a regular pattern I'll deploy, it was extremely useful to set up in this case.&lt;/p&gt;

&lt;p&gt;If you plan on doing this on your own network, be sure to use the proper values for your router and network topology. For example, my wireless system uses &lt;code&gt;192.168.4.1&lt;/code&gt;. Yours might be something different. When it comes to configuring the network for the Raspberry Pi's ethernet port, you can pick any unused range on your network.&lt;/p&gt;

&lt;p&gt;~ Thanks for stopping by and I hope you enjoyed the read!&lt;/p&gt;

</description>
      <category>iot</category>
      <category>operations</category>
      <category>homelab</category>
    </item>
    <item>
      <title>Exploring Redis High Availability</title>
      <dc:creator>Mya</dc:creator>
      <pubDate>Thu, 22 Apr 2021 14:50:02 +0000</pubDate>
      <link>https://dev.to/msmyajaye/exploring-redis-high-availability-3f1k</link>
      <guid>https://dev.to/msmyajaye/exploring-redis-high-availability-3f1k</guid>
      <description>&lt;p&gt;Recently, I've found myself using &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; for more of the projects that I work on. Redis can be used in a variety of ways. It provides functionality for queueing, set operations, bitmaps, streams, and so much more. Yet, most of my experience with Redis has been as a best-effort cache. Since it's become a staple in my development, I figured it would be good to brush up on its operations.&lt;/p&gt;

&lt;p&gt;In this post, I dive into the variety of ways Redis can be deployed. I'll cover the benefits, tradeoffs, and even some uses for each deployment. Finally, I'll describe the deployment that I recently put together.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Redis?
&lt;/h2&gt;

&lt;p&gt;Redis is an open-source, in-memory database licensed under the BSD license. It has built-in replication, common eviction strategies, and even different levels of on-disk persistence. Redis also supports &lt;a href="https://redis.io/topics/modules-intro" rel="noopener noreferrer"&gt;modules&lt;/a&gt;, which makes it easy to plug in new functionality and interact with native data types. For example, the &lt;a href="http://redisgraph.io/" rel="noopener noreferrer"&gt;RedisGraph&lt;/a&gt; module extends Redis by providing &lt;a href="https://en.wikipedia.org/wiki/Graph_database" rel="noopener noreferrer"&gt;graph database&lt;/a&gt; capabilities. Due to the variety of data structures and its flexibility, it's no wonder Redis has become as &lt;a href="https://www.cncf.io/blog/2020/11/18/cncf-end-user-technology-radar-database-storage-november-2020/" rel="noopener noreferrer"&gt;popular&lt;/a&gt; as it has. &lt;/p&gt;

&lt;h2&gt;
  
  
  How can Redis be deployed?
&lt;/h2&gt;

&lt;p&gt;One of the big benefits to Redis is that it can be deployed in a variety of ways. We won't dive into every deployment, but we will focus on the common patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replicated
&lt;/h3&gt;

&lt;p&gt;The easiest deployment of Redis is a replicated architecture. In this deployment, Redis has a single, primary write point called a "master". Any data written to this primary propagates to replicas asynchronously. The diagram below depicts how an application may interact with this deployment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbocfbobr6lp7h0hvrodp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbocfbobr6lp7h0hvrodp.jpg" alt="replicated diagram" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While the deployment is rather straightforward, there are a few things that we need to keep in mind. First, applications need to be able to track read-only and read-write connections. They could use a single connection, but that likely means that the primary handles every request. By using a single connection, your application makes no use of the replicas that you have in place. Second, this deployment has a single point of failure, the primary node. Should the primary node fail, applications can no longer write data to Redis.&lt;/p&gt;

&lt;p&gt;This deployment works well for offline population. For example, maybe you have a background task that's responsible for populating the cache. Meanwhile, your web service knows about the replicas and actively reads from them. Should the primary crash, your background process can simply wait for it to return.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Sentinel
&lt;/h3&gt;

&lt;p&gt;To help alleviate the single primary node in the previous deployment, Redis provides Sentinel. Sentinel is a separate process that determines when a given primary node is no longer available. It does this by using a quorum to vote on member availability. When a member becomes unavailable, Sentinel is able to elect a new primary for the cluster. The diagram below depicts how an application may interact with this deployment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnkrf2hk2j8en50h1lk4w.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnkrf2hk2j8en50h1lk4w.jpg" alt="sentinel diagram" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similar to the last deployment, this approach has some downsides. When a client wants to write data to a Sentinel cluster, they first need to consult the Sentinel process.&lt;br&gt;
Sentinel reports to callers who the current primary in the cluster is. Then, clients will call the primary to write the data. As a result, clients need to be aware they are talking to a Sentinel cluster. This often requires additional configuration that few projects support.&lt;/p&gt;

&lt;p&gt;Another risk to this deployment is a potential split-brain state during upgrades or node failures. A split-brain state happens when two nodes cannot come to an agreement. This case is less likely if you manage Sentinel deployments separately from Redis. If you run them in the same pod or on the same host, the likelihood of this scenario increases. To illustrate let us consider the above example.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Suppose we upgrade our cluster and &lt;code&gt;redis-0&lt;/code&gt; is taken down.&lt;/li&gt;
&lt;li&gt;Sentinels detect that the pod is no longer available and start figuring out a new primary.&lt;/li&gt;
&lt;li&gt;The sentinel for &lt;code&gt;redis-1&lt;/code&gt; votes that &lt;code&gt;redis-2&lt;/code&gt; should be the new primary.&lt;/li&gt;
&lt;li&gt;The sentinel for &lt;code&gt;redis-2&lt;/code&gt; votes that &lt;code&gt;redis-1&lt;/code&gt; should be the new primary.&lt;/li&gt;
&lt;li&gt;With a quorum of &lt;code&gt;2&lt;/code&gt;, the instances are unable to come to consensus.&lt;/li&gt;
&lt;li&gt;Steps 2 through 5 repeat until the instances reach a quorum.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unfortunately, this is how &lt;a href="https://github.com/bitnami/charts" rel="noopener noreferrer"&gt;Bitnami&lt;/a&gt; configures the deployment of Sentinel in their &lt;code&gt;redis&lt;/code&gt; Helm chart. While split-brains &lt;em&gt;can&lt;/em&gt; occur, they may not be as common as you may think. Still, it's something you need to account for in your deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redis Cluster
&lt;/h3&gt;

&lt;p&gt;Redis Cluster is popular amongst those using Redis for a large amount of data. It shards data across the instances in the cluster, thus reducing the resource requirements of each individual node. Redis does this by hashing the provided key into a slot. Slots are distributed across the primaries in the cluster, often in chunks. The diagram below illustrates how an application might interact with members of a Redis Cluster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3hm9xluf8cv9toh0pkr7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3hm9xluf8cv9toh0pkr7.jpg" alt="cluster diagram" width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similar to the Sentinel deployment, applications need to be aware they are speaking to a Redis Cluster. This is due to how Redis Cluster handles its sharding. Clients are responsible for hashing keys to determine which slot they write to. Then, clients write to the instance responsible for the associated slot. This will allow clients to perform a subset of operations, even in the event of a partial cluster outage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clustering with Envoy
&lt;/h3&gt;

&lt;p&gt;An alternative to using Redis Cluster would be to cluster using Envoy. &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/other_protocols/redis" rel="noopener noreferrer"&gt;Envoy&lt;/a&gt; is a well-known, well-supported, network proxy open-sourced by Lyft. It supports many protocols including HTTP, gRPC, MongoDB, Redis, and more. When using Envoy to cluster instances, envoy handles partitioning the keyspace on behalf of Redis. This means the underlying Redis instances &lt;em&gt;shouldn't&lt;/em&gt; have clustering enabled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe062h6pglpti767k9h4i.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe062h6pglpti767k9h4i.jpg" alt="envoy diagram" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this deployment, Envoy favors availability and partition tolerance over consistency. This makes it ideal for a best-effort cache, but not great for more stateful systems. You might consider setting an LRU eviction policy for Redis to ensure stale, unused data is pruned first. One nice thing about this deployment is that clients can speak to any &lt;code&gt;envoy&lt;/code&gt; instance in the cluster. This drastically simplifies the logic on the callers' end when compared to Redis Cluster. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;2022-03-21 Edit&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've written up a &lt;a href="https://dev.to/charts/"&gt;Redis Helm Chart&lt;/a&gt; that makes it easy to deploy a Redis cluster using this pattern. Instead of running Envoy as a sidecar to the Redis server, client applications must embed it into their own application. My helm chart provides a simple function for doing so. For a reference on how to do this, see the &lt;a href="https://github.com/mjpitz/mjpitz/blob/main/charts/registry/templates/deployment.yaml#L84-L86" rel="noopener noreferrer"&gt;Registry Helm Chart&lt;/a&gt; as an example.&lt;/p&gt;

&lt;h2&gt;
  
  
  My deployment
&lt;/h2&gt;

&lt;p&gt;While these approaches can work great, I often found myself wanting something a little different. Ideally, I wanted a deployment that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improves the availability of the replicated deployment&lt;/li&gt;
&lt;li&gt;Improves the stability of the sentinel deployment&lt;/li&gt;
&lt;li&gt;Maintains the simplicity of the replicated deployment&lt;/li&gt;
&lt;li&gt;Maintains the consistency of the Redis deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Such a deployment would improve task and queue-based systems like &lt;a href="https://docs.celeryproject.org/en/stable/index.html" rel="noopener noreferrer"&gt;Celery&lt;/a&gt; in python or &lt;a href="https://github.com/RichardKnop/machinery/" rel="noopener noreferrer"&gt;Machinery&lt;/a&gt; in go. Payloads in these systems tend to be small, relatively short-lived, and/or backed up elsewhere. This means we rarely need to shard data across nodes.&lt;/p&gt;

&lt;p&gt;As I contemplated how to do this, I kept my runtime environment in mind. Kubernetes provides many capabilities to engineers. For example, adding leader election to a system is relatively easy to do in Go. The &lt;code&gt;client-go&lt;/code&gt; library provides a pre-built &lt;a href="https://github.com/kubernetes/client-go/tree/master/tools/leaderelection" rel="noopener noreferrer"&gt;&lt;code&gt;leaderelection&lt;/code&gt;&lt;/a&gt; package. This can automate watching a &lt;code&gt;Lease&lt;/code&gt; for changes and periodically attempting to claim leadership. This had given me an idea for a sidecar.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The sidecar would use the Lease API to determine leadership in the cluster.&lt;/li&gt;
&lt;li&gt;When an election occurs, the sidecar is responsible for updating the topology of the Redis cluster.

&lt;ol&gt;
&lt;li&gt;Each sidecar acts independently, removing the need for coordination beyond the initial election.&lt;/li&gt;
&lt;li&gt;Each sidecar takes appropriate steps to handle the change in leadership gracefully.

&lt;ul&gt;
&lt;li&gt;The current leader pauses writes and waits for the newly elected leader to catch up.&lt;/li&gt;
&lt;li&gt;The newly elected leader waits for syncs to complete before attempting to promote itself to leader.&lt;/li&gt;
&lt;li&gt;All other followers swap over to replicating from the new leader.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Once the topology of the cluster has been updated, the sidecar of the newly elected updates a Kubernetes &lt;code&gt;Endpoint&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;This Endpoint can be used by clients to guarantee requests get sent to the leader.&lt;/li&gt;
&lt;li&gt;A separate service can be used to allow reads to any node in the cluster.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;Such a system can be seen below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctxueiwlyikoyei43ha0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctxueiwlyikoyei43ha0.jpg" alt="mya's diagram" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A nice thing about this deployment is that the mitigation techniques are similar to that of the sentinel deployment. While we aren't using the sentinel to perform the election process, we are doing more or less the same thing. A benefit to this deployment over sentinel is that we remove the call to discover the current leader. Instead, clients get a stable address they can dial and always reach a leader, even during rolling upgrades.&lt;/p&gt;

&lt;p&gt;In the few tests I ran, I had seen failover times of 6 to 10 seconds. Definitely still working out some tuning, but all in all, I'm satisfied with how it came out. For now, I'm going to keep the project closed source. If you're interested in chatting through this a bit more feel free to reach out.&lt;/p&gt;

&lt;p&gt;Anyway, thanks for checking in. I know it's been a while. I hope you learned something while you were here. Thanks for reading!&lt;/p&gt;

</description>
      <category>redis</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Repository impressions and conversions</title>
      <dc:creator>Mya</dc:creator>
      <pubDate>Mon, 03 Aug 2020 14:23:27 +0000</pubDate>
      <link>https://dev.to/msmyajaye/repository-impressions-and-conversions-2heg</link>
      <guid>https://dev.to/msmyajaye/repository-impressions-and-conversions-2heg</guid>
      <description>&lt;p&gt;As someone who works on open source a lot, I often have questions about the reach a given project has. Understanding user behavior around a given product helps companies improve their product and experience. Why should open source repositories be any different?&lt;/p&gt;

&lt;p&gt;In this series, I walk through setting up Google Analytics to track metrics around a repository hosted on GitHub. This approach follows a similar technique that's applied to tracking email open rates.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mjpitz.com/blog/2020/07/17/repo-impression-tracking/" rel="noopener noreferrer"&gt;Part 1 - Set up&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mjpitz.com/blog/2020/07/27/repo-impressions-2/" rel="noopener noreferrer"&gt;Part 2 - Follow up on metrics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mjpitz.com/blog/2020/08/02/repo-impressions-3/" rel="noopener noreferrer"&gt;Part 3 - Follow up on updates&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>github</category>
    </item>
  </channel>
</rss>
