[{"content":"As a photography enphusiast, all-around nerd, and a \u0026ldquo;moon dork\u0026rdquo; as once lovingly described by my wife, you can bet I was excited for this year\u0026rsquo;s total solar eclipse. I had originally settled for only seeing it partially from North Carolina, but several months ago my wife and I impromptu decided to make an adventure out of it. Looking at the map and convenient flights, we settled on visiting Toronto and seeing the eclipse in the Niagara Falls area.\nIt was an absolutely amazing experience that I\u0026rsquo;d highly recommend to anyone. While my main goal was for my family to be able to see it in person, I wanted to try my hand at photographing the eclipse as well since I had made it this far. This post serves as a way to capture my process and share what I\u0026rsquo;d do differently next time around.\nPlanning While we had decided Toronto as our home base for the weekend, I was still a little unsure as to where in Ontario we\u0026rsquo;d actually watch the eclipse. Due to the path of the eclipse being mostly over Lake Ontario and Lake Erie, it limited our spots on land. We had originally planned for Niagara Falls, but so did an anticipated million others. I settled for Brock University in St. Catharines, Ontario. They had an event planned and it seemed like a great way to keep my young kids engaged as well.\nSince capturing photos was a goal, I also needed to acquire a bit of extra gear. I picked up a solar filter for camera rig (think eclipse glasses for your camera) and rather last minute decided to pick up an extender to give my 100-400mm zoom lens twice the reach.\nThe Pivot I\u0026rsquo;d been checking the weather forecast religiously all week, but Sunday evening was when I really became concerned with all the clouds in the forecast. We had just explored Niagara earlier in the day with crystal clear skies, but a front was on its way through bringing overcast skies. While there was still some hope the system would move quickly to the east, I needed a backup plan. Looking at the Canadian Government\u0026rsquo;s cloud cover forecast and the eclipse map next to each other, I settled on the Port Dover area which had nearly as much time in totality as St. Cathatrines, but much less cloud cover.\n A map showing our potential observation spots with totality shaded in the blue lines  Late Monday morning after we finished our last Toronto attraction, I checked the forecast again after the latest model runs: St. Catharines was covered by heavy clouds and Port Dover was looking clear. That settled it. After frantically looking for spots on Google Maps, I settled on a park in Simcoe, Ontario.\n The orange circle highlights the Niagara area, while the green circle highlights our ultimate location of Simcoe  3 hours later with bits of heavy traffic, we made it to Grant Anderson Park around 2:45pm. The park was full of people donned with glasses observing the partial eclipse that had started earlier in the hour.\nThe Process I realize this post is turning into one of those recipes where you have to scroll for miles past the author\u0026rsquo;s life story, but nevertheless you\u0026rsquo;ve arrived.\nGear Here\u0026rsquo;s what I carried across the border to capture the eclipse. I had a shorter zoom and some short prime lenses, but didn\u0026rsquo;t use them for this part of my trip.\nCamera Body: Canon EOS RP\nLens: Canon RF 100-400mm f/5.6-8 IS USM with Canon RF 2x extender\nSolar Filter: Thousand Oaks Optical\nAccessories: tripod and wired remote\n Yours truly nerding out with his gear  Partial Eclipse Due to traffic, I had less than 30 minutes to setup my gear before totality, so I was a bit rushed out of the gate. My largest aperture was already limited to f/16 due to using the extender, so I locked my shutter speed down to ~1 second and let my camera\u0026rsquo;s auto exposure calculate the rest. It worked pretty well, though I wish I had underexposed a bit. Autofocus worked okay at the beginning despite the small aperture. I bumped it a few times and had to re-adjust. I didn\u0026rsquo;t have any tracking equipment, so I reframed the sun as it moved every few minutes.\n The partial eclipse as it approaches totality  I didn\u0026rsquo;t take a ton of pictures during the partial eclipse as I was balancing time behind the camera and with my family. It was super cool observing the sunlight darkening, the air cooling, and our shadows changing as we neared totality.\nTotality Now the glasses (and solar filters) come off. I lack words to describe how amazing totality of the eclipse was. Between hearing the cheering of hundreds of people sharing this experience nearby, seeing how excited my kids were, and gazing at the now-visible corona of the sun, it\u0026rsquo;s truly something I won\u0026rsquo;t forget.\nI went into manual exposure for this phase. I kept my aperture at its widest f/16, then found a good balance at ISO 400 and 1/160 second shutter speed. I wouldn\u0026rsquo;t need to adjust until the end of totality, so this seemed like a good spot to lock it in.\n The eclipse during totality with only the corona visible  I only took about 20 shots over the 3 minutes of totality, making sure I had time to take it in with my own eyes as well.\n A quick shot from my phone showing the fake \u0026#34;sunset\u0026#34; during totality with the sun\u0026#39;s corona lighting the sky  A few light clouds started to roll in as totality ended, making for some pretty cool texture as the diamond ring emerged. This was one of my last shots before putting the solar filter (and my glasses) back on.\n The diamond ring emerges as totality ended. Note that the blue was just some lens flare and not something observable to the eye  At this point, I took a few more shots but mostly just breathed a sigh of relief: I got the \u0026ldquo;money shot\u0026rdquo; already and my family got to experience this celestial phenomenon with barely any clouds in the sky.\nWhat I\u0026rsquo;d Do Differently I\u0026rsquo;m perfectly happy with how things turned out: I had amazing weather, my kids were amazed, and I had a blast capturing some photos along the way. But I\u0026rsquo;d be remiss to say things couldn\u0026rsquo;t be improved. Here are a few things I\u0026rsquo;m keeping in mind for next time I do this.\n  More time!: Traffic was a wildcard, so I really should have boxed out some more time to reach my destination. I think the initial rush prevented me from experimenting more during the partial eclipse phase.\n  Exposure bracketing: The corona had the most dynamic range and likely could have benefited from some exposure bracketing to capture more detail like the red flare-like prominences peeking out in the corona. Plus as conditions change rapidly, having more exposures to choose from would have been useful.\n  More wide shots: I didn\u0026rsquo;t exactly have a scenic view, but I only got one \u0026lt;800mm shot on my phone. I\u0026rsquo;ll seriously consider renting a second camera body to take some consistent wide shots. I\u0026rsquo;ve seen plenty of cool layered shots tracking the sun across the sky through the different phases of the eclipse, which would be doable with a second rig.\n  Less exposure is more: I\u0026rsquo;m so used to shooting in low light with high ISO that I tend to try and get exposure right on-camera. In this case, I wish I had lowered my exposure compensation some in an attempt to better see sunspots during the partial eclipse. My ISO remained fairly low, so I would have had plenty of room to brighten things up while editing. Yet another reason to bracket exposure.\n  Practice makes perfect: There\u0026rsquo;s nothing preventing eclipse glasses and solar filters from working the rest of the year. I regret not taking an hour or so to just practice exposure with the full sun at home when I wasn\u0026rsquo;t against the clock.\n  All in all, I had an amazing experience and can\u0026rsquo;t wait to attempt this again one day.\n","permalink":"https://patrick.easte.rs/post/2024/solar-eclipse-post-retrospective/","summary":"As a photography enphusiast, all-around nerd, and a \u0026ldquo;moon dork\u0026rdquo; as once lovingly described by my wife, you can bet I was excited for this year\u0026rsquo;s total solar eclipse. I had originally settled for only seeing it partially from North Carolina, but several months ago my wife and I impromptu decided to make an adventure out of it. Looking at the map and convenient flights, we settled on visiting Toronto and seeing the eclipse in the Niagara Falls area.","title":"A total solar eclipse retrospective"},{"content":"As someone who\u0026rsquo;s been playing with a lot of Kubernetes on bare metal lately, I\u0026rsquo;ve come to appreciate MetalLB (a load balancer implementation for bare metal). Nothing is worse than blindly pasting YAML into your terminal, then seeing Pending next to all your newly created services expecting cloud load balancers.\n   MetalLB was the last thing I needed to make my tiny home lab cluster feel like a real cloud. When I was first configuring it, the hardest thing to wrap my head around was how traffic flowed in the different modes and traffic policies. I spent a lot of time reading docs and experimenting, so hopefully this post will help you understand the different modes and how they work with service traffic policies.\nChoosing an announcement mode MetalLB discovers services needing load balancers, allocates IP addresses for them, and advertises them outside of the cluster. For the latter piece of the puzzle, there are 2 primary modes for announcing load balancers: Layer 2 and Layer 3 (BGP), both of which may sound familiar if you\u0026rsquo;ve ever seen the OSI model. Each mode has its pros and cons, so it\u0026rsquo;s a real balancing act as you decide which one to use.\nLayer 2 Mode Layer 2 mode is the easiest to get started with and will work in any environment—no fancy routers needed. One Kubernetes node will assume the role of advertising your service to the rest of your network, behaving as if a secondary IP was added to its network interface. But underneath, MetalLB is responding to ARP (or NDP in the case of IPv6) requests on its own. Should the node advertising your service fail, another node will be elected and use gratuitous ARP/NDP to alert the rest of your network that your service has a new home.\nIf you\u0026rsquo;re thinking that sounds a lot like failover instead of load balancing, you\u0026rsquo;re not wrong. Because an IP address can only map to one physical (MAC) address at time, incoming traffic for the service will all come through one node. This can potentially become a bottleneck for some services, but is likely not a deal breaker most of the time.\nBGP Mode At the expense of a more involved initial setup, BGP mode can avoid the single-node bottleneck that comes with Layer 2. Despite BGP being hard sometimes, don\u0026rsquo;t let it dissuade you from trying it out.\nIn this mode, each Kubernetes node essentially becomes a router and advertises routes to your services to its peers. As long as your upstream routers support BGP multi-path, traffic will be evenly-ish distributed to each node that advertises your service.\nThe main gotcha with BGP mode stems from how the protocol\u0026rsquo;s stateless load balancing works. Instead of tracking connections, BGP hashes headers from the incoming packet (like IPs and ports) to determine which of the available next hops to use. When nodes are added or removed, existing connections will be randomly rebalanced based upon the hash. This can lead to clients receiving a one-time connection reset error when nodes come and go. MetalLB\u0026rsquo;s docs include some strategies to mitigate this disruption if this becomes an issue for you.\nUnderstanding how traffic flows MetalLB is really only concerned with getting traffic to your nodes. Once it reaches the node\u0026rsquo;s networking stack, it\u0026rsquo;s up to kube-proxy to direct traffic to your service endpoints based on your external traffic policy. Each combination of announcement mode and traffic policy has its tradeoffs, so there is not necessarily one correct option.\n   Layer 2 Mode BGP Mode   Global Traffic Policy Traffic enters one node and is distributed to all pods matching the service selector Traffic enters all nodes and is distributed to all pods matching the service selector   Local Traffic Policy Traffic enters one node and is only distributed to pods on the same node. Traffic is distributed to all nodes with running pods and is then distributed to pods on the same node.   A quick note about Source IP preservation Source IP preservation is a big consideration when choosing an external traffic policy. With a Global policy, the source IP of incoming packets is always translated (even if the destination pod is on the same node). This means the application running behind your service won\u0026rsquo;t be able to determine the client IP of incoming requests based on the network connection. Unless you have something like a CDN or other reverse proxy in front of your cluster that can inject that as an HTTP header or use the PROXY protocol, losing the true source IP is a big limitation.\nOn the other hand, the Local policy preserves the source IP of incoming packets. The only disadvantage is that the packet can only be sent to a pod/endpoint on the same node.\nLayer 2 Global With layer 2 mode and the Global external traffic policy, MetalLB advertises one node\u0026rsquo;s MAC address and kube-proxy evenly distributes traffic to all service endpoints in the cluster. This results in a pretty even spread of traffic at the expense of no source IP preservation.\nLayer 2 Local On the other hand, Layer 2 mode with the Local external traffic policy results in traffic entering one node and only reaching service endpoints on the same node. MetalLB is smart enough to ensure that nodes without endpoints are ineligible to advertise a service, meaning you won\u0026rsquo;t be stuck in a scenario where a single pod runs on node A but is advertised from node B.\nIf you want to use Layer 2 mode with a singleton application (one pod/endpoint), the Local policy is a great fit. For services with multiple endpoints, it\u0026rsquo;s important to remember that inbound traffic will only ever reach pods on a single node at a time.\nBGP Global The real fun starts with BGP. Combined with the Global external traffic policy, traffic will enter all nodes and then be distributed across all service endpoints in the cluster. If a node becomes unreachable, its BGP session will drop and the upstream routers will rebalance traffic amongst the other nodes. Unlike layer 2 mode, all nodes will accept incoming traffic, which can be handy when you have high throughput services and want to avoid overwhelming a single node.\nLike the story with layer 2 mode, the Global policy will potentially achieve better load balancing at the expense of no source IP preservation.\nBGP Local Lastly we have my personal favorite (is that nerdy to say?): BGP mode with the Local external traffic policy. In this configuration, each node containing at least one service endpoint will advertise itself and then distribute traffic to endpoints on the same node. I like this for at least several reasons: it minimizes extra hops and source IP preservation.\nOne thing to watch out for when using this combination is your traffic distribution. Because traffic is equally balanced between nodes, then yet again balanced between pods on the node, the spread can become uneven if some nodes have more pods than others. One potential mitigation is using pod anti-affinity policies to help spread pods across nodes. If this is a pain point for you, it may be worth following a related issue that proposes some BGP magic to equal out the distribution based upon endpoint count on each node.\nWrapping up Hopefully by now you\u0026rsquo;ve learned a little more about how MetalLB works and some of the tradeoffs to consider. Reach out if you have any questions or feedback. Happy load balancing!\nThanks to u/brontide on Reddit for correcting some bad assumptions I made regarding Source IP Preservation.\n","permalink":"https://patrick.easte.rs/post/2022/forging-optimal-metallb-config/","summary":"\u003cp\u003eAs someone who\u0026rsquo;s been playing with a lot of Kubernetes on bare metal lately, I\u0026rsquo;ve come to appreciate \u003ca href=\"https://metallb.io/\"\u003eMetalLB\u003c/a\u003e (a load balancer implementation for bare metal). Nothing is worse than blindly pasting YAML into your terminal, then seeing \u003ccode\u003ePending\u003c/code\u003e next to all your newly created services expecting cloud load balancers.\u003c/p\u003e\n\u003cfigure\u003e\n\t\u003cimg src=\"/post/2022/forging-optimal-metallb-config/img/waiting.jpg\" /\u003e\n\t\u003cfigcaption\u003e\u003c/figcaption\u003e\n\u003c/figure\u003e\n\u003cp\u003eMetalLB was the last thing I needed to make my tiny home lab cluster feel like a real cloud. When I was first configuring it, the hardest thing to wrap my head around was how traffic flowed in the different modes and traffic policies. I spent a lot of time reading docs and experimenting, so hopefully this post will help you understand the different modes and how they work with service traffic policies.\u003c/p\u003e","title":"Forging an optimal MetalLB configuration"},{"content":"After many years of being on-call under my belt, I never thought I\u0026rsquo;d say I have a favorite alerting method. But that changed after watching one of Justin Garrison\u0026rsquo;s videos which had an excellent depiction of how Linux\u0026rsquo;s Out-of-Memory Killer works. I was no stranger to the OOM Killer visiting my Kubernetes clusters, so this gave me a dumb idea for a (perhaps) fun alerting mechanism: the OOM Bonker.\n How It Works This project mostly consisted of hardware I had laying around, the Prometheus monitoring stack for Kubernetes, and a tiny bit of code to tie it all together. At a high level, I have a Raspberry Pi running a webserver waiting for alerts from Prometheus\u0026ndash;at which point it commences bonking a poor container.\nSoftware We\u0026rsquo;ll configure a Prometheus alert to detect our out-of-memory conditions and then fire a webhook to a Raspberry Pi when containers are killed.\nPrometheus Configuration To get started, we\u0026rsquo;ll need a Prometheus instance configured to scrape kube-state-metrics, which is where we\u0026rsquo;ll get metrics for container status. I use the Prometheus Operator, but you can adapt the config to your environment as needed.\nWe\u0026rsquo;ll be using the kube_pod_container_status_last_terminated_reason{reason=\u0026quot;OOMKilled\u0026quot;} query to find containers that were last terminated due to being OOM Killed. One caveat to this search is that it may not capture containers with child processes that were killed. I found a good post that gives a few alternatives using cadvisor metrics. I had difficulty getting those to work in my OpenShift cluster, so I fell back to using the query above from kube-state-metrics.\nSince software evolves over time, I\u0026rsquo;ll link you to the patrickeasters/oom-bonker GitHub repo which contains the steps needed to configure Prometheus.\nAlert Webhook With the Prometheus configuration out of the way, we need something to receive alerts. Prometheus includes some default integrations like Slack and PagerDuty, but webhooks are still the king of compatibility. All it takes is a web app that can accept a POST request with some JSON.\nThe Prometheus docs give a full example of what a webhook POST body looks like, but I\u0026rsquo;ve included a snippet below of what a webhook looks like for the alert we just configured.\n{ \u0026#34;receiver\u0026#34;: \u0026#34;oom-bonker\u0026#34;, \u0026#34;status\u0026#34;: \u0026#34;firing\u0026#34;, \u0026#34;alerts\u0026#34;: [{ \u0026#34;status\u0026#34;: \u0026#34;firing\u0026#34;, \u0026#34;labels\u0026#34;: { \u0026#34;alertname\u0026#34;: \u0026#34;OOMKilled\u0026#34;, \u0026#34;container\u0026#34;: \u0026#34;eater\u0026#34;, \u0026#34;endpoint\u0026#34;: \u0026#34;https-main\u0026#34;, \u0026#34;job\u0026#34;: \u0026#34;kube-state-metrics\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;bonk\u0026#34;, \u0026#34;pod\u0026#34;: \u0026#34;memory-eater\u0026#34;, \u0026#34;prometheus\u0026#34;: \u0026#34;openshift-monitoring/k8s\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;OOMKilled\u0026#34;, \u0026#34;service\u0026#34;: \u0026#34;kube-state-metrics\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;49cc552f-3c00-4275-babb-e9598c7fec61\u0026#34; }, \u0026#34;annotations\u0026#34;: {}, \u0026#34;startsAt\u0026#34;: \u0026#34;2022-04-26T13:59:56.33Z\u0026#34;, \u0026#34;endsAt\u0026#34;: \u0026#34;0001-01-01T00:00:00Z\u0026#34;, \u0026#34;fingerprint\u0026#34;: \u0026#34;6e4ca3f5c5f99172\u0026#34; }] ... } I wrote a super simple Flask app that accepts the webhook requests and uses the gpiozero Python library to control a servo connected to the Raspberry Pi.\nThe GitHub repo contains the steps for copying the webhook app and configuring systemd to run it as a service.\nHardware As I mentioned before, most of the hardware was picked from things I had laying around. We all have that drawer with old electronics in our house, right?\nElectronic Components I used the following components that I had on hand:\n Raspberry Pi 1B (yes, it\u0026rsquo;s old\u0026ndash;but it\u0026rsquo;s hard to find a new Pi amidst our supply chain crisis) Generic servo (purchased from SparkFun)  A wiring diagram is included below, though it may likely vary if you\u0026rsquo;re using a newer Raspberry Pi which has 40 pins instead of 26.\n3D Printed Enclosure I didn\u0026rsquo;t have to 3D print an enclosure for this, but I wanted an excuse to exercise my CAD skills. I won\u0026rsquo;t divulge how many Fusion 360 tutorials I had to lookup on YouTube, but I was able to fumble my way through it and only printed 2 prototypes.\nI used self-tapping M2.3x6 screws for the Raspberry Pi and M3x6 screws for the servo.\n   Both the CAD model and ready-to-print STL file are are available on Printables\nOther components In the spirit of grabbing what I had around my house, the remaining components we need are:\n Tux penguin stress reliever (thanks for the swag, Microsoft!) A toothpick (close enough in scale to the 4x4 Justin used) One of an assortment of springs from Home Depot A 3D printed shipping container scaled to about 40mm long  Testing it out Once it\u0026rsquo;s wired up, there\u0026rsquo;s no better way to test it out than by summoning the OOM Killer. While my first idea for quickly eating up memory was launching Chrome, I turned to Stack Overflow and found a cool one-liner to quickly consume an infinite amount of memory: tail /dev/zero. In this project\u0026rsquo;s Git repo, I provided a spec for a pod that crashes 10 seconds after startup. Assuming it\u0026rsquo;s all wired up correctly, poor Tux should be bonked momentarily.\nDespite being a niche project, hopefully you were able to glean something from it. Let me know if you make something inspired from this—I\u0026rsquo;d love to see it!\n","permalink":"https://patrick.easte.rs/post/2022/oom-bonker/","summary":"\u003cp\u003eAfter many years of being on-call under my belt, I never thought I\u0026rsquo;d say I have a favorite alerting method. But that changed after watching one of Justin Garrison\u0026rsquo;s videos which had \u003ca href=\"https://www.youtube.com/watch?v=KNexvhb_DuY\u0026amp;list=PLehXSATXjcQHGYufa__n1y9WIUZjyNMEw\u0026amp;t=43s\"\u003ean excellent depiction of how Linux\u0026rsquo;s Out-of-Memory Killer works\u003c/a\u003e. I was no stranger to the \u003ca href=\"https://docs.rackspace.com/support/how-to/linux-out-of-memory-killer/\"\u003eOOM Killer\u003c/a\u003e visiting my Kubernetes clusters, so this gave me a dumb idea for a (perhaps) fun alerting mechanism: the \u003ca href=\"https://github.com/patrickeasters/oom-bonker\"\u003eOOM Bonker\u003c/a\u003e.\u003c/p\u003e\n\u003cvideo width=\"600\" playsinline autoplay loop muted\u003e\n  \u003csource src=\"/post/2022/oom-bonker/img/bonker.webm\" type=\"video/webm\" /\u003e\n\u003c/video\u003e","title":"Taking a whack at custom Prometheus alerting"},{"content":"I live near a major airport, and I frequently hear aircraft flying over my house. I also have a curious preschooler, and I find myself answering questions like, \u0026ldquo;What\u0026rsquo;s that?\u0026rdquo; and \u0026ldquo;Where\u0026rsquo;s that plane going?\u0026rdquo; often. While a quick internet search could answer these questions, I wanted to see if I could answer them myself.\nWith a Raspberry Pi, an inexpensive radio, and open source software, I can track aircraft as far as 200 miles from my house. Whether you\u0026rsquo;re answering relentless questions from your kids or are just curious about what\u0026rsquo;s in the sky above you, this is something you can try, too.\nThe protocol behind it all ADS-B is a technology that aircraft use worldwide to broadcast their location. Aircraft use position data gathered from GPS and periodically broadcast it along with speed and other telemetry so that other aircraft and ground stations can track their position.\nSince this protocol is well-known and unencrypted, there are many solutions to receive and parse it, including many that are open source.\nGathering the hardware Pretty much any Raspberry Pi will work for this project. I\u0026rsquo;ve used an older Pi 1 Model B, but I\u0026rsquo;d recommend a Pi 3 or newer to ensure you can keep up with the stream of decoded ADS-B messages.\nTo receive the ADS-B signals, you need a software-defined radio. Thanks to ultra-cheap radio chips designed for TV tuners, there are quite a few cheap USB receivers to choose from. I use FlightAware\u0026rsquo;s ProStick Plus because it has a built-in filter to weaken signals outside the 1090MHz band used for ADS-B. Filtering is important since strong signals, such as broadcast FM radio and television, can desensitize the receiver. Any receiver based on RTL-SDR should work.\nYou will also need an antenna for the receiver. The options are limitless here, ranging from the more adventurous DIY options to purchasing a ready-made 1090MHz antenna. Whichever route you choose, antenna placement matters most. ADS-B reception is line-of-sight, so you\u0026rsquo;ll want your antenna to be as high as possible to extend your range. I have mine in my attic, but I got decent results from my house\u0026rsquo;s upper floor.\nVisualizing your data with software Now that your Pi is equipped to receive ADS-B signals, the real magic happens in the software. Two of the most commonly used open source software projects for ADS-B are readsb for decoding ADS-B messages and tar1090 for visualization. Combining both provides an interactive map showing all the aircraft your Pi is tracking.\nBoth projects provide setup instructions, but using a prebuilt image like the ADSBx Custom Pi Image is the fastest way to get going. The ADSBx image even configures a Prometheus instance with custom metrics like aircraft count.\nKeep experimenting If the novelty of tracking airplanes with your Raspberry Pi wears off, there are plenty of ways to keep experimenting. Try different antenna designs or find the best antenna placement to maximize the number of aircraft you see.\nThese are just a few of the ways to track aircraft with your Pi, and hopefully, this inspires you to try it out and learn a bit about the world of radio. Happy tracking!\n This article was originally published on Opensource.com and is licensed under Creative Commons SA-BY 4.0.\n","permalink":"https://patrick.easte.rs/post/2021/track-aircraft-pi/","summary":"\u003cp\u003eI live near a major airport, and I frequently hear aircraft flying over my house. I also have a curious preschooler, and I find myself answering questions like, \u0026ldquo;What\u0026rsquo;s that?\u0026rdquo; and \u0026ldquo;Where\u0026rsquo;s that plane going?\u0026rdquo; often. While a quick internet search could answer these questions, I wanted to see if I could answer them myself.\u003c/p\u003e\n\u003cp\u003eWith a Raspberry Pi, an inexpensive radio, and open source software, I can track aircraft as far as 200 miles from my house. Whether you\u0026rsquo;re answering relentless questions from your kids or are just curious about what\u0026rsquo;s in the sky above you, this is something you can try, too.\u003c/p\u003e","title":"Track aircraft with a Raspberry Pi"},{"content":"It\u0026rsquo;s a few days in to the holiday shutdown at work, so I\u0026rsquo;ve been enjoying some downtime with my family at home. There\u0026rsquo;s been plenty of last-minute shopping, gift wrapping, baking, and, evidently, building operators with the Operator SDK.\nFor the unaquainted, the Operator Framework is a toolkit that makes it easy to manage complex applications on top of Kubernetes. While I\u0026rsquo;ve had the chance to use the Go SDK for a few projects, I\u0026rsquo;ve recently been reading more about Ansible operators. Ansible operators allow you use Ansible roles to configure an application and respond to any changes to its Kubernetes resources. An Ansible operator allows you to handle complex scenarios just like the Go SDK, but lets you use the familiar Ansible syntax (no Go code required) and take advantage of the large Ansible module ecosystem.\nWhile operators are designed to manage resources inside Kubernetes, they also do a great job at managing resources outside of the cluster such as TLS certificates or even external monitoring checks. As I was thinking of a good first Ansible operator, I looked up from my couch and saw my Christmas tree. I already had many of my Christmas lights integrated with Home Assistant, so why not take advantage of the easy-to-use REST API and automate my Christmas lights?\nThis post will walk you through the process of how I created my first (admittedly not very useful) Ansible operator.\nCreating the operator If you haven\u0026rsquo;t already, go ahead and install the operator-sdk CLI tool. We\u0026rsquo;ll be using this tool to create the initial structure and boilerplate for our operator.\nIn the commands below, I named it christmas-decoration-operator. The API group is decorations.easte.rs with version v1alpha1. You can choose whatever group name you\u0026rsquo;d like, though it should be some domain name you own to ensure it does not overlap with another resource. We also set the name of our custom resource to Light via the --kind parameter.\noperator-sdk new christmas-decoration-operator --api-version=decorations.easte.rs/v1alpha1 --kind=Light --type=ansible cd christmas-decoration-operator Defining the custom resource Now that we have our Light custom resource, we need to determine its specification. In this case, we simply need 2 parameters to manage a light in Home Assistant: its entity ID and its state (on/off). We\u0026rsquo;ll add these to the example spec in deploy/crds/decorations.easte.rs_v1alpha1_light_cr.yaml:\napiVersion:decorations.easte.rs/v1alpha1kind:Lightmetadata:name:example-lightspec:# Add fields hereentity:\u0026#34;light.example\u0026#34;state:\u0026#34;on\u0026#34;Configuring the operator While the code generated by the Operator SDK has very sane defaults, we\u0026rsquo;re going to make a few tweaks and add some custom config.\nWatch Configuration Operators work by watching Kubernetes resources and reacting to changes. When a resource changes, a reconciliation is triggered. In the case of an Ansible operator, reconciliation means a role or playbook is applied to the resource. We\u0026rsquo;ll be reducing the reconciliation period to 30 seconds in watches.yaml, meaning that the role will be applied every 30 seconds even without changes to the Light resource. This is especially useful for external resources like ours where the state can change in Home Assistant without the operator knowing.\n- version:v1alpha1group:decorations.easte.rskind:Lightrole:/opt/ansible/roles/lightreconcilePeriod:30sHome Assistant Connection Secret Our operator needs to connect to Home Assistant to interact with its REST API. Since this operator is namespaced (only manages resources in the same namespace), it will only need to talk to one instance of Home Assistant. We\u0026rsquo;ll use a secret to store the connection informating, which will be injected into the operator via environment variables. An example secret is available at deploy/secret-example.yaml.\napiVersion:v1kind:Secretmetadata:name:christmas-decoration-operator-hassstringData:baseURL:https://demo.home-assistant.ioaccessToken:hunter2We\u0026rsquo;ll update the deployment of our Operator to use this secret as well in deploy/operator.yaml:\napiVersion:apps/v1kind:Deploymentmetadata:name:christmas-decoration-operatorspec:template:spec:containers:- name:operatorenv:- name:HASS_BASE_URLvalueFrom:secretKeyRef:name:christmas-decoration-operator-hasskey:baseURL- name:HASS_ACCESS_TOKENvalueFrom:secretKeyRef:name:christmas-decoration-operator-hasskey:accessTokenUpdate image and pull policy There are a few other variables we need to update in the deployment generated by the Operator SDK: the location of the operator\u0026rsquo;s container image and the image pull policy.\nsed -i \u0026#39;s|{{ REPLACE_IMAGE }}|quay.io/patrickeasters/christmas-decoration-operator:v0.0.1|g\u0026#39; deploy/operator.yaml sed -i \u0026#39;s|{{ pull_policy\\|default(\u0026#39;\\\u0026#39;\u0026#39;Always\u0026#39;\\\u0026#39;\u0026#39;) }}|Always|g\u0026#39; deploy/operator.yaml ## MacOS users will need to use these alternate commands sed -i \u0026#34;\u0026#34; \u0026#39;s|{{ REPLACE_IMAGE }}|quay.io/patrickeasters/christmas-decoration-operator:v0.0.1|g\u0026#39; deploy/operator.yaml sed -i \u0026#34;\u0026#34; \u0026#39;s|{{ pull_policy\\|default(\u0026#39;\\\u0026#39;\u0026#39;Always\u0026#39;\\\u0026#39;\u0026#39;) }}|Always|g\u0026#39; deploy/operator.yaml Creating the role At last, we need to develop the actual fun part of our operator. The SDK generated a skeleton role named light, which will be run every time a Light resource is being reconciled. Normally, your role would heavily use the k8s Ansible module to manage resources in the cluster, but in our very simple case we\u0026rsquo;ll just be using the uri module to interact with Home Assistant. I added the following tasks in roles/light/tasks/main.yml:\n- name:Validate state paramfail:msg:\u0026#34;state must be \u0026#39;on\u0026#39; or \u0026#39;off\u0026#39;\u0026#34;when:state is not defined or (state != \u0026#34;on\u0026#34; and state != \u0026#34;off\u0026#34;)- name:Get current stateuri:url:\u0026#34;{{ hass_base_url }}/api/states/{{ entity }}\u0026#34;method:GETheaders:authorization:\u0026#34;Bearer {{ hass_access_token }}\u0026#34;register:current_state- name:Set stateuri:url:\u0026#34;{{ hass_base_url }}/api/services/homeassistant/turn_{{ state }}\u0026#34;method:POSTheaders:authorization:\u0026#34;Bearer {{ hass_access_token }}\u0026#34;body:entity_id:\u0026#34;{{ entity }}\u0026#34;body_format:jsonchanged_when:truewhen:current_state.json.state != stateWe also need to access those environment variables we created earlier with the Home Assistant connection info, so we\u0026rsquo;ll add these as role variables in roles/light/vars/main.yml:\nhass_base_url:\u0026#34;{{ lookup(\u0026#39;env\u0026#39;,\u0026#39;HASS_BASE_URL\u0026#39;) }}\u0026#34;hass_access_token:\u0026#34;{{ lookup(\u0026#39;env\u0026#39;,\u0026#39;HASS_ACCESS_TOKEN\u0026#39;) }}\u0026#34;Deploying the operator At last, we have a role and all the other necessary bits needed to run our operator. First we\u0026rsquo;ll build and push the operator image to a container registry.\noperator-sdk build quay.io/patrickeasters/christmas-decoration-operator:v0.0.1 docker push quay.io/patrickeasters/christmas-decoration-operator:v0.0.1 Now we will create all the resources needed to run our operator. We\u0026rsquo;ll create the custom resource definition (CRD), RBAC, and finally the deployment for the operator.\nkubectl create -f deploy/crds/decorations.easte.rs_lights_crd.yaml kubectl create -f deploy/service_account.yaml kubectl create -f deploy/role.yaml kubectl create -f deploy/role_binding.yaml kubectl create -f deploy/secret.yaml kubectl create -f deploy/operator.yaml Creating our first custom resource Now that we have an operator deployed, it\u0026rsquo;s waiting for some new lights to manage. In this example, I\u0026rsquo;m creating a resource for my Christmas tree. Here I\u0026rsquo;m declaring that the light named light.wall_plug_level in Home Assistant should be turned off.\ncat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: decorations.easte.rs/v1alpha1 kind: Light metadata: name: tree spec: entity: light.wall_plug_level state: \u0026quot;on\u0026quot; EOF You can now view a list of your lights inside Kubernetes:\n\u0026gt; kubectl get lights NAME AGE tree 14h View operator logs Once there is at least one custom resource, we can look at the operator\u0026rsquo;s logs to see how things are progressing:\n# Watch operator logs kubectl logs deploy/christmas-decoration-operator -c operator -f # Watch nicely-formatted output of Ansible runs kubectl logs deploy/christmas-decoration-operator -c ansible -f See it in action Now for the really cool part: watching your Christmas lights turn on with a kubectl command. Watch below as my Christmas tree lights turn on in response to the following command that sets the state of my tree \u0026ldquo;on\u0026rdquo;:\nkubectl patch light tree --type merge -p \u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;state\u0026#34;:\u0026#34;on\u0026#34;}}\u0026#39; Make something new! Managing Christmas decorations in Kubernetes isn\u0026rsquo;t likely to be applicable to most of our jobs, but it\u0026rsquo;s an easy example to show that you really can do just about anything with an operator. Hopefully this inspires you to try making one yourself.\nWhat are you looking to automate? Let me know on Twitter or in the comments.\n","permalink":"https://patrick.easte.rs/post/2019/putting-the-crd-in-christmas-decorations/","summary":"\u003cp\u003eIt\u0026rsquo;s a few days in to the holiday shutdown at work, so I\u0026rsquo;ve been enjoying some downtime with my family at home. There\u0026rsquo;s been plenty of last-minute shopping, gift wrapping, baking, and, evidently, building operators with the \u003ca href=\"https://github.com/operator-framework/operator-sdk\"\u003eOperator SDK\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eFor the unaquainted, the \u003ca href=\"https://coreos.com/blog/introducing-operator-framework\"\u003eOperator Framework\u003c/a\u003e is a toolkit that makes it easy to manage complex applications on top of Kubernetes. While I\u0026rsquo;ve had the chance to use the Go SDK for a few projects, I\u0026rsquo;ve recently been reading more about Ansible operators. Ansible operators allow you use Ansible roles to configure an application and respond to any changes to its Kubernetes resources. An Ansible operator allows you to handle complex scenarios just like the Go SDK, but lets you use the familiar Ansible syntax (no Go code required) and take advantage of the large Ansible module ecosystem.\u003c/p\u003e\n\u003cp\u003eWhile operators are designed to manage resources inside Kubernetes, they also do a great job at managing resources outside of the cluster such as TLS certificates or even external monitoring checks. As I was thinking of a good first Ansible operator, I looked up from my couch and saw my Christmas tree. I already had many of my Christmas lights integrated with \u003ca href=\"https://www.home-assistant.io/\"\u003eHome Assistant\u003c/a\u003e, so why not take advantage of the easy-to-use REST API and automate my Christmas lights?\u003c/p\u003e","title":"Putting the CRD in Christmas Decorations"},{"content":"Over the past few months at Red Hat, I\u0026rsquo;ve been working with my team on streamlining our CI/CD process and migrating some of our applications into OpenShift. As we\u0026rsquo;ve been slowly moving apps, it\u0026rsquo;s been a great opportunity to revisit some of the basics of our architecture and look at ways we can better use OpenShift to . What may have worked well in a VM-based deployment doesn\u0026rsquo;t necessarily translate well into a container-based deployment. For the sake of this post, I\u0026rsquo;ll be showing how we use a recently stable feature of OpenShift (and Kubernetes) to deploy memcached for one of our Ruby apps on the Red Hat Customer Portal.\nThe Problem In our VM-based deployment, we co-located an instance of memcached on each VM that ran our app (that\u0026rsquo;s the textbook use-case!). Our app was then configured to connect to each app server and shard keys across the memcached instances. This worked fine for us in an environment where hostnames didn\u0026rsquo;t change all-too-often. If you\u0026rsquo;ve ever deployed an app in OpenShift or Kubernetes, you\u0026rsquo;ve probably noticed that whenever you rollout a new deployment you end up with a bunch of pods with funky hostnames like app-8-m6t5v. You probably never even cared, since resources like routes and services abstract us from needing to care about those hostnames. In an OpenShift deployment like this where instances of our app come and go fairly often, it\u0026rsquo;s not feasible to configure our app to connect to memcached instances by pod name.\nPotential Solutions One Big Pod Memcached was designed to take advantage of unused memory resources distributed across web servers. We don\u0026rsquo;t quite have that same problem now that OpenShift will . Instead of using, say, (8) 256MB instances of memcached, we can use just use one 2GB instance, right?\nWell, you could, but having only one replica of anything is usually a bad idea. In our environment, our nodes get rebuilt at-minimum once a week due to things like updates and autoscaling. If our pod is on a node getting rebuilt, there will at least be a minute or two where it will be unavailable while it\u0026rsquo;s being rescheduled on a new node. Losing all of our cache each time that happens would be less than optimal. While most objects aren\u0026rsquo;t cached very long and our app gracefully handles cache being unavailable, it\u0026rsquo;s still not super great for performance or our backend services. Let\u0026rsquo;s see if we can find a better way to deploy this while still keeping it distributed.\nCreating multiple services and deployments One way to approach this would be to create a deployment and service for each instance of memcached we want. A deployment ensures that we have a pod running and a service provides a static hostname we can use for accessing the pod. We would ultimately need to create a deployment and service for each instance of memcached (e.g. memcached-a, memcached-b, memcached-c, etc). This would work fine, but it causes some management overhead since we have to configure each individual instance instead of configuring one resource to define them all.\nStatefulSets Building on top of the previous approach, a newer OpenShift feature called StatefulSets allows us to use a single controller to manage all of our memcached pods. The Kubernetes docs give a good overview of all the things you can do with them, but since memcached doesn\u0026rsquo;t need any persistent storage or have any dependencies on which pods come up first, we\u0026rsquo;ll mainly take advantage of the stable pod identities provided by StatefulSets. The StatefulSet resource will allow us to specify a desired number of replicas, and create those pods with stable names (e.g. memcached-0, memcached-1, memcached-3, etc). Whenever a pod is terminated, a new one will replace it with the same pod name. This means we can configure our app with a list of memcached shards and expect it work even when pods come and go.\nLet\u0026rsquo;s Deploy It Before we get started, I\u0026rsquo;m going to assume to you\u0026rsquo;re logged into an OpenShift or Kubernetes cluster and have the oc or kubectl CLI tools installed. Though I\u0026rsquo;ll probably mention OpenShift more often, all the concepts in this article will translate over to a vanilla Kubernetes cluster as well.\nCreate the service Since our apps shard keys across all memcached instances in the cluster, the typical load-balanced service isn\u0026rsquo;t all that useful. Instead, we\u0026rsquo;ll create a headless service where no load balancing or proxying takes place. This service simply allows endpoints to be looked up via DNS or via the API.\ncat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: component: memcached name: memcached spec: type: ClusterIP clusterIP: None selector: component: memcached ports: - name: memcached port: 11211 protocol: TCP targetPort: 11211 EOF Create the StatefulSet Now that we have the service in place, let\u0026rsquo;s spin up some memcached instances. For this example, lets create 3 shards/replicas with 64MB of memory each. Since we don\u0026rsquo;t care about what order pods spin up, we\u0026rsquo;re also specifying the parallel podManagementPolicy. This still ensures our pods get their unique names, but doesn\u0026rsquo;t limit us to spinning up one pod at a time. By specifying serviceName here, we also get a unique hostname for each pod based on the pod name and service name (e.g. memcached-0.memcached.default.svc.cluster.local, memcached-1.memcached.default.svc.cluster.local, etc)\ncat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: apps/v1beta1 kind: StatefulSet metadata: labels: component: memcached name: memcached spec: replicas: 3 revisionHistoryLimit: 5 selector: matchLabels: component: memcached serviceName: memcached podManagementPolicy: Parallel template: metadata: labels: component: memcached spec: containers: - name: memcached args: [\u0026quot;memcached\u0026quot;, \u0026quot;-m\u0026quot;, \u0026quot;64\u0026quot;] image: memcached:1.5 ports: - containerPort: 11211 name: memcached protocol: TCP livenessProbe: tcpSocket: port: memcached readinessProbe: tcpSocket: port: memcached resources: limits: cpu: \u0026quot;1\u0026quot; memory: 96Mi requests: memory: 64Mi EOF At this point, we should have a ready-to-go memcached cluster. Let\u0026rsquo;s check things out and make sure we see the pods and that they\u0026rsquo;re discoverable. Feel free to skip ahead.\n# Confirm we see 3 memcached pods running $ kubectl get po -l component=memcached NAME READY STATUS RESTARTS AGE memcached-0 1/1 Running 0 1d memcached-1 1/1 Running 0 1d memcached-2 1/1 Running 0 1d # Let\u0026#39;s spin up a pod with a container running digso we can confirm DNS entries $ kubectl run net-utils --restart=Never --image=patrickeasters/net-utils pod \u0026#34;net-utils\u0026#34; created # The service hostname should return with a list of pods # Don\u0026#39;t forget to replace default with the name of your OpenShift project $ kubectl exec net-utils dig +short memcached.default.svc.cluster.local 10.244.0.17 10.244.0.15 10.244.0.16 # Pod IPs can still change, so it\u0026#39;s best to configure any apps with hostnames instead # Looking up SRV records for the service hostname should give us the pod FQDNs $ kubectl exec net-utils dig +short srv memcached.default.svc.cluster.local 10 33 0 memcached-2.memcached.default.svc.cluster.local. 10 33 0 memcached-1.memcached.default.svc.cluster.local. 10 33 0 memcached-0.memcached.default.svc.cluster.local. # Let\u0026#39;s clean up this pod now that we\u0026#39;re done with our validation # (Or you can keep running queries... that\u0026#39;s cool too) $ oc delete po net-utils pod \u0026#34;net-utils\u0026#34; deleted Connecting Our App Now it\u0026rsquo;s time to point our app to our memcached cluster. Instead of configuring a static list of pods, we\u0026rsquo;ll take advantage of the built-in DNS service discovery. All we have to do is provide the hostname of our service, and our app can discover all the pods in the memcached cluster on its own. If you speak Ruby (or at least pretend to, like me), feel free to glean from the below example from one my team\u0026rsquo;s apps.\nmemcached_hosts = [] Resolv::DNS.new.each_resource(\u0026#39;memcached.default.svc.cluster.local\u0026#39;, Resolv::DNS::Resource::IN::SRV) { |rr| memcached_hosts \u0026lt;\u0026lt; rr.target.to_s } config.cache_store = :dalli_store, memcached_hosts Closing Thoughts Hopefully now you have a working memcached cluster and are on your way to configuring your app to take advantage of some of service discovery greatness we get for free from OpenShift and Kubernetes. Let me know in the comments or on Twitter if you were able to try this out for yourself. Happy caching!\n","permalink":"https://patrick.easte.rs/post/2018/openshift-memcached-statefulset/","summary":"\u003cp\u003eOver the past few months at Red Hat, I\u0026rsquo;ve been working with my team on streamlining our CI/CD process and migrating some of our applications into \u003ca href=\"https://www.openshift.com/\"\u003eOpenShift\u003c/a\u003e. As we\u0026rsquo;ve been slowly moving apps, it\u0026rsquo;s been a great opportunity to revisit some of the basics of our architecture and look at ways we can better use OpenShift to . What may have worked well in a VM-based deployment doesn\u0026rsquo;t necessarily translate well into a container-based deployment. For the sake of this post, I\u0026rsquo;ll be showing how we use a recently stable feature of OpenShift (and Kubernetes) to deploy \u003ca href=\"https://memcached.org/\"\u003ememcached\u003c/a\u003e for one of our Ruby apps on the Red Hat Customer Portal.\u003c/p\u003e","title":"Deploying memcached in a StatefulSet with OpenShift"},{"content":"As voice assistants, smart bulbs, and other devices increasingly become household staples, more people than ever are bringing smart technology into their homes. But the bewildering assortment of products on the market can present challenges: Remembering which app to use and trying to link things together with automation can get complicated quickly. In this article, I’ll show you a few ways I used an open source home automation platform, Home Assistant, to bring all my devices together.\nGetting started with Home Assistant When looking for a hub, I wanted something easily extensible, with a strong community and support for devices. After considering various hubs and platforms, I decided Home Assistant was the winner. It has an extremely active community, is lightweight enough to run on a Raspberry Pi, and best of all, it’s open source.\nI won’t get into the setup process here; check out Home Assistant’s Getting Started guide to get up and running.\nTapping into my home’s alarm system My wife and I bought our first house several years ago. Though this wasn\u0026rsquo;t exactly a selling point, it came pre-wired with an alarm system that featured all the best technology from the 1990\u0026rsquo;s. I had no intention of reviving the alarm panel, but I did want to take advantage of all the door sensors it included.\nAfter cracking open the alarm panel box in my laundry room, I decided this would be a great first project for an ESP8266 microcontroller. Using MQTT (a lightweight message queuing protocol), I was able to get instant status updates whenever my outside doors opened or closed.\nAutomating the lights Though quite a few devices were controllable through my Home Assistant instance, I still needed to control or trigger most things manually. The whole point of automation is to accomplish tasks with little or no human interaction. Using a Z-Wave motion sensor and the custom MQTT door sensors mentioned above, I decided to automate a few scenarios.\nMy first task was turning on my front porch lights when the door is opened after daylight hours. I used my front door as the trigger and took into account the current state of the sun to ensure I’m not turning on the lights when it’s still daylight.\nThe second task I automated was turning on a lamp when I come downstairs to get ready for work. Since I’m an early riser, it’s often quite dark when I am getting ready for my day. Using a motion sensor covering my stairs, I configured an automation to turn on a lamp as I come downstairs. It’s also intelligent enough to turn the lamp off once there’s enough daylight outside.\nClosing doors Since we have a dog who goes outside frequently and my wife and I are trying to wrangle an infant, it’s not uncommon for a door to be left open or not completely shut. Using a simple automation with my MQTT door sensors, Home Assistant automatically sends a push notification to our phones reminding us to close the door.\nI also have a similar automation setup for my garage door, which will alert us when the garage is open after 8:30 pm. While my OpenGarage controller is also able to close the door, I opted to have it notify us in case someone is still working outside, or for safety reasons—for example, if something is obstructing the door and preventing it from closing.\nFeeding the dog As our lives got more hectic with the arrival of our first kid, my wife and I found ourselves asking one particular question more often: “Did we feed the dog?” Staring at the food bin one morning trying to remember if I’d fed her already, I knew there had to be a better way. Using an extra Z-Wave door sensor attached to the lid of the dog food bin, I used Home Assistant to keep track of when we open the container. Using a few automations, I set up a system to notify us if we forget to feed our dog after a certain time each morning and evening. Though the dog is a bit disappointed to no longer enjoy “bonus” meals, it’s helped us keep our sanity.\nGoing forward You probably won’t have the same devices or problems to solve in your home, but I hope some of the things I’ve done will help you find ways to make your home smarter. The possibilities truly are endless.\nFollow my Home Assistant config repo on GitHub to keep up with my latest additions or find some inspiration. It’s constantly evolving!\nYou can also find me on Twitter or the Home Assistant community forums. I love answering questions and seeing what other ideas folks in the community come up with.\n This article was originally published on Opensource.com and is licensed under Creative Commons SA-BY 4.0.\n","permalink":"https://patrick.easte.rs/post/2018/home-assistant/","summary":"\u003cp\u003eAs voice assistants, smart bulbs, and other devices increasingly become household staples, more people than ever are bringing smart technology into their homes. But the bewildering assortment of products on the market can present challenges: Remembering which app to use and trying to link things together with automation can get complicated quickly. In this article, I’ll show you a few ways I used an open source home automation platform, Home Assistant, to bring all my devices together.\u003c/p\u003e","title":"Feed the dog and close the door with an open source home automation system"},{"content":"When my wife and I bought a house a couple years back, I knew it would only be a matter of time before I started getting into home automation. My house, like many built in the late 90s, was pre-wired for an alarm system. While I had no desire to revive a 20-year-old alarm panel, it did mean all my exterior doors were pre-wired with inconspicuous sensors. I already run Home Assistant on a Raspberry Pi, so I was looking for a way to integrate these hard-wired door sensors with what I already have. I had read about these cheap WiFi-enabled ESP8266 boards, so I decided this would be a simple project to try it out with.\nBreaking into the existing alarm system The brains of my old alarm system were tucked away in a wall-mounted cabinet in my laundry room.\n The old alarm system (with bonus lead-acid battery that I need to get rid of)  All the sensor wires were cut back, meaning I had to strip each one and trace what they did. It was decently quick work with my multimeter and a wife to open/close doors for me. Since the existing sensors were just simple reed switches, the two wires from each door would short when the door was closed, and open when the door opened. I guessed correctly that the small 2-conductor wires went to my three exterior doors, but honestly, I’m still not sure where the rest of them go.\n While I typically don’t use wire nuts in my projects, it made the most sense here since I was dealing with pre-existing structured wiring from the old security system.  Making it work on a breadboard Now the fun part: building it. This is a super simple circuit, with just a microcontroller and 3 switches. I used 3 of the GPIO pins to connect to the 3 door switches and connected the other side of the switches to ground. One nice feature of this board was that most of the pins had a built-in weak pull-up resistor. This meant that when a switch was open, the pin would be pulled high (3.3V).\n The components labeled S1-S3 represent the reed switches already installed in the doors. When a door opens, the magnet in the door causes the switch to open (or disconnect).  Adding some code All of my source code is on GitHub, so feel free to check out that repo and see how it works there. This is my first time using Lua, so I’m sure there are things that can be optimized in this code. Feel free to submit an issue or PR if you find anything that stands out!\n init.lua: the simple startup script. Best practice is to keep this file relatively simple and give yourself an opportunity to break out of the boot cycle should your app have an issue. secrets.lua: secret variables such as WiFi credentials config.lua: just some simple configuration variables sensor.lua: the brains of the project. All of the real logic lives in this file.  I won’t get too detailed here, but after establishing the initial WiFi and MQTT connections, the application code is triggered asynchronously by an interrupt when a change on one of the door sensors is detected.\nPreparing the NodeMCU board Before I could run my code on the NodeMCU board, I needed to flash it with updated firmware that contained the modules I needed. I used this handy Cloud Build Service to build a firmware image. For my needs, I simply needed the following modules: file, gpio, mqtt, net, node, tmr, uart, wifi.\n   To actually flash the firmware, I used an open-source Python tool called nodemcu-pyflasher. It worked great on my Mac and should on most other platforms as well.\n   Now that I was ready to upload my code, I used a tool called ESPlorer to handle that portion. It provides a pretty handy console that lets you run commands ad-hoc as well as edit your code. I personally found it easiest to just use my usual text editor and the “Upload” button in the left pane.\nIntegrating with Home Assistant Like I mentioned before, my home automation platform of choice is an open-source app called Home Assistant. It has a strong community backing and the maintainers are constantly pushing new releases. While I could have used the built-in REST API, I opted to use the MQTT protocol for a couple of reasons: first, MQTT is super lightweight and perfect for the modest compute resources of the NodeMCU board; and second, it’s a more universal protocol that can be used outside of the Home Assistant ecosystem without changes. If you use Home Assistant, I configured my door sensors as binary_sensors. The snippet for one sensor is below, but you can look at the rest of them in the Github repo for my config.\n- platform:mqttstate_topic:\u0026#34;pat/alarm/Front Door\u0026#34;name:\u0026#34;Front Door\u0026#34;payload_on:\u0026#34;open\u0026#34;payload_off:\u0026#34;closed\u0026#34;device_class:openingWrapping it up Thanks for reading this far! Hopefully this project inspired you to build something of your own or saved you a bit of time as you tackle something similar. I’m happy to clarify anything or answer questions, so feel free to reach out here or on GitHub.\nResources NodeMCU Documentation (super thorough and helpful) Project Code Home Assistant Config\n","permalink":"https://patrick.easte.rs/post/2017/home-security-esp8266-mqtt/","summary":"\u003cp\u003eWhen my wife and I bought a house a couple years back, I knew it would only be a matter of time before I started getting into home automation. My house, like many built in the late 90s, was pre-wired for an alarm system. While I had no desire to revive a 20-year-old alarm panel, it did mean all my exterior doors were pre-wired with inconspicuous sensors. I already run \u003ca href=\"https://home-assistant.io\"\u003eHome Assistant\u003c/a\u003e on a Raspberry Pi, so I was looking for a way to integrate these hard-wired door sensors with what I already have. I had read about these cheap WiFi-enabled \u003ca href=\"https://www.amazon.com/gp/product/B010O1G1ES\"\u003eESP8266 boards\u003c/a\u003e, so I decided this would be a simple project to try it out with.\u003c/p\u003e","title":"Integrating existing home security sensors with MQTT"},{"content":"Over the past few months, I’ve been working with Kubernetes a lot as Ayetier has been making the shift towards container orchestration. As easy as it was to create and scale services, it was a bit frustrating to see how most reverse proxy solutions seemed kludgy at best.\nThat’s why I was pretty intrigued when I first read about Traefik — a modern reverse proxy supporting dynamic configuration from several orchestration and service discovery backends, including Kubernetes.\nTraefik is still relatively new and doesn’t fully support Kubernetes’ TLS configuration for the ingress, so it took a bit of manual configuration. Much trial-and-error was involved, so I thought I’d share the process here.\nFirst Things First I’m going to assume you already have a working Kubernetes cluster and have the kubectl tool installed to manage your cluster. I’m also on Google Container Engine (GKE), so depending on your cloud provider, a few things like LoadBalancer service types may be different.\nAny code I use is also in this GitHub repo, so feel free to clone it and follow along. (It’s more fun that way)\ngit clone https://github.com/patrickeasters/traefik-k8s-tls-example.git Deploy Backend Services For the purposes of this post, I made a pretty simple web service in Go that will aid in testing. You could also make your own service to display cat facts if you really want. Let’s go ahead and deploy 3 replication controllers and services from the backend.yaml file.\nkubectl create -f backend.yaml Secure All The Things We’re just going to generate a self-signed certificate for this tutorial, but any certificate/key pair will work. Run the following command to generate your certificate and dump the certificate and private key.\nopenssl req -newkey rsa:2048 -nodes -keyout tls.key -x509 -days 365 -out tls.crt Now that we have the certificate, we’ll use kubectl to store it as a secret. We’ll use this so our pods running Traefik can access it.\nkubectl create secret generic traefik-cert --from-file=tls.crt --from-file=tls.key Configure Traefik As I mentioned earlier, due to lack of native support for TLS with Kubernetes ingresses, we’ll have to do a bit of manual configuration on the pods running Traefik.The only real points of interest here are setting up the HTTP to HTTPS redirect and then setting the certificate to be used for TLS.\n# traefik.toml defaultEntryPoints = [\u0026#34;http\u0026#34;,\u0026#34;https\u0026#34;] [entryPoints] [entryPoints.http] address = \u0026#34;:80\u0026#34; [entryPoints.http.redirect] entryPoint = \u0026#34;https\u0026#34; [entryPoints.https] address = \u0026#34;:443\u0026#34; [entryPoints.https.tls] [[entryPoints.https.tls.certificates]] CertFile = \u0026#34;/ssl/tls.crt\u0026#34; KeyFile = \u0026#34;/ssl/tls.key\u0026#34; Now let’s take this configuration and store it in a ConfigMap to be mounted as a volume in the Traefik pods.\nkubectl create configmap traefik-conf --from-file=traefik.toml Deploy Traefik Now we finally get to deploy the replication controller for Traefik. I’m going to use 2 pods, but this can be scaled out as desired. I’m also using a LoadBalancer service, but you can change it to NodePort and configure your external load balancer as required if your cloud provider doesn’t natively support it.\nHere are a few things to note in the pod spec from traefik.yaml, which contains the RC and service.\n The traefik-cert secret is mounted as a volume to /ssl, which allows the tls.crt and tls.key files to be read by the pod The traefik-conf ConfigMap is mounted as a volume to /config, which lets Traefik read the traefik.conf file The log level is set to debug, which is great when you’re troubleshooting or getting started, but it may be more manageable if you set it to something less chatty before going into production with it.  spec:terminationGracePeriodSeconds:60volumes:- name:sslsecret:secretName:traefik-cert- name:configconfigMap:name:traefik-confcontainers:- image:traefikname:traefik-ingress-lbimagePullPolicy:AlwaysvolumeMounts:- mountPath:\u0026#34;/ssl\u0026#34;name:\u0026#34;ssl\u0026#34;- mountPath:\u0026#34;/config\u0026#34;name:\u0026#34;config\u0026#34;ports:- containerPort:80- containerPort:443args:- --configfile=/config/traefik.toml- --kubernetes- --logLevel=DEBUGNow let’s go ahead and create this RC and service in the cluster.\nkubectl create -f traefik.yaml Configuring the Ingress Now that Traefik is up and running, we need to configure an ingress so it has actual rules. We’re going to set up 2 route prefixes, /s1 and /s2, pointing to svc1 and svc2 respectively. Anything else will be sent to svc3. The pods running Traefik are watching the API for any changes made to the ingress configuration\nA note for any GKE users: To prevent the default L7 load balancer ingress controller from picking up this configuration, I set the kubernetes.io/ingress.class annotation to traefik. Google’s ingress controller will ignore any ingresses whose class is not set to gcp.\napiVersion:extensions/v1beta1kind:Ingressmetadata:name:example-web-appannotations:kubernetes.io/ingress.class:\u0026#34;traefik\u0026#34;spec:tls:- secretName:traefik-certrules:- host:http:paths:- path:/s1backend:serviceName:svc1servicePort:8080- path:/s2backend:serviceName:svc2servicePort:8080- path:/backend:serviceName:svc3servicePort:8080Behold, our last configuration.\nkubectl create -f ingress.yaml Testing It Out At this point, you can test your routes and see your shiny new config doing its magic. In my output below, you can see each service identify itself.\n$ curl -k https://myhost/s1\nHi, I'm the svc1 service! Hostname: svc1-on0mm $ curl -k https://myhost/s2\nHi, I'm the svc2 service! Hostname: svc2-i485q $ curl -k https://myhost/\nHi, I'm the svc3 service! Hostname: svc3-iict9 $ curl -k https://myhost/lolcat\nHi, I'm the svc3 service! Hostname: svc3-iict9 Final Thoughts Hopefully at this point you have a working Traefik reverse proxy setup. Hit me up in the comments or on Twitter if you have any questions.\n","permalink":"https://patrick.easte.rs/post/2016/traefik-tls-k8s/","summary":"\u003cp\u003eOver the past few months, I’ve been working with \u003ca href=\"http://kubernetes.io/\"\u003eKubernetes\u003c/a\u003e a lot as Ayetier has been making the shift towards container orchestration. As easy as it was to create and scale services, it was a bit frustrating to see how most reverse proxy solutions seemed kludgy at best.\u003c/p\u003e\n\u003cp\u003eThat’s why I was pretty intrigued when I first read about \u003ca href=\"https://traefik.io/\"\u003eTraefik\u003c/a\u003e — a modern reverse proxy supporting dynamic configuration from several orchestration and service discovery backends, including Kubernetes.\u003c/p\u003e","title":"Using Traefik with TLS on Kubernetes"},{"content":"Patrick is a staff field engineer at Grafana Labs.\nHe is always trying to keep up with best practices for cloud native infrastructure and enjoys helping others learn the same. When he\u0026rsquo;s not busy tinkering with Kubernetes, he\u0026rsquo;s probably building something in his garage, trying to find the best pizza recipe, or stepping on snacks dropped by his kids.\n","permalink":"https://patrick.easte.rs/about/","summary":"Patrick is a staff field engineer at Grafana Labs.\nHe is always trying to keep up with best practices for cloud native infrastructure and enjoys helping others learn the same. When he\u0026rsquo;s not busy tinkering with Kubernetes, he\u0026rsquo;s probably building something in his garage, trying to find the best pizza recipe, or stepping on snacks dropped by his kids.","title":"About"}]