Compare commits
14 Commits
df143c30cc
...
20251120-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
bf1498d3c5
|
|||
|
0ffe8c334e
|
|||
|
9bb243e2ca
|
|||
|
43b2c213e4
|
|||
|
b198db2725
|
|||
|
13372066c3
|
|||
|
6d54cbef61
|
|||
|
3062cba541
|
|||
|
f0810eb8e5
|
|||
|
c8684d2d96
|
|||
|
550524cb1a
|
|||
|
4918cef5e6
|
|||
|
bbb21b34dd
|
|||
|
24d66c2fea
|
@@ -1,9 +1,9 @@
|
|||||||
# Package versions
|
# Package versions
|
||||||
ARG HUGO_VERSION="latest"
|
ARG HUGO_VERSION="0.152.2"
|
||||||
ARG CADDY_VERSION="2.10.0"
|
ARG CADDY_VERSION="2.10.2"
|
||||||
|
|
||||||
# Stage 1: Build
|
# Stage 1: Build
|
||||||
FROM git.brds.ca/d-b.ca/hugo-builder:${HUGO_VERSION} AS builder
|
FROM core.harbor.brds.ca/d-b.ca/hugo-builder:${HUGO_VERSION} AS builder
|
||||||
WORKDIR /project
|
WORKDIR /project
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN hugo --minify build
|
RUN hugo --minify build
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ title = "Drew Bowering"
|
|||||||
|
|
||||||
[params.author]
|
[params.author]
|
||||||
name = "Drew Bowering"
|
name = "Drew Bowering"
|
||||||
# email = "drew@d-b.ca"
|
email = "drew@d-b.ca"
|
||||||
image = "img/DrewBowering.jpg"
|
image = "img/DrewBowering.jpg"
|
||||||
# imageQuality = 96
|
# imageQuality = 96
|
||||||
headline = "IT Architect, Developer, Canoeist, Tubist"
|
headline = "IT Architect, Developer, Canoeist, Tubist"
|
||||||
# bio = "A little bit about me"
|
# bio = "A little bit about me"
|
||||||
links = [
|
links = [
|
||||||
# { email = "mailto:hello@your_domain.com" },
|
{ email = "mailto:drew@d-b.ca" },
|
||||||
# { link = "https://link-to-some-website.com/" },
|
# { link = "https://link-to-some-website.com/" },
|
||||||
# { amazon = "https://www.amazon.com/hz/wishlist/ls/wishlist-id" },
|
# { amazon = "https://www.amazon.com/hz/wishlist/ls/wishlist-id" },
|
||||||
# { apple = "https://www.apple.com" },
|
# { apple = "https://www.apple.com" },
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ disableTextInHeader = false
|
|||||||
defaultBackgroundImage = "img/BG_Jasper.jpg" # used as default for background images
|
defaultBackgroundImage = "img/BG_Jasper.jpg" # used as default for background images
|
||||||
# defaultFeaturedImage = "IMAGE.jpg" # used as default for featured images in all articles
|
# defaultFeaturedImage = "IMAGE.jpg" # used as default for featured images in all articles
|
||||||
|
|
||||||
# highlightCurrentMenuArea = true
|
highlightCurrentMenuArea = true
|
||||||
# smartTOC = true
|
smartTOC = true
|
||||||
# smartTOCHideUnfocusedChildren = true
|
smartTOCHideUnfocusedChildren = false
|
||||||
|
|
||||||
giteaDefaultServer = "https://git.brds.ca"
|
giteaDefaultServer = "https://git.brds.ca"
|
||||||
forgejoDefaultServer = "https://v8.next.forgejo.org"
|
forgejoDefaultServer = "https://v8.next.forgejo.org"
|
||||||
@@ -74,7 +74,7 @@ forgejoDefaultServer = "https://v8.next.forgejo.org"
|
|||||||
showPagination = true
|
showPagination = true
|
||||||
invertPagination = false
|
invertPagination = false
|
||||||
showReadingTime = true
|
showReadingTime = true
|
||||||
showTableOfContents = false
|
showTableOfContents = true
|
||||||
# showRelatedContent = false
|
# showRelatedContent = false
|
||||||
# relatedContentLimit = 3
|
# relatedContentLimit = 3
|
||||||
showTaxonomies = false
|
showTaxonomies = false
|
||||||
@@ -156,7 +156,7 @@ forgejoDefaultServer = "https://v8.next.forgejo.org"
|
|||||||
# globalWidgetPosition = "Right"
|
# globalWidgetPosition = "Right"
|
||||||
|
|
||||||
[verification]
|
[verification]
|
||||||
# google = ""
|
google = "w3v6pJQgijQRPkhgVV6SIOJEPIpAR9ase26ri4tPkS8"
|
||||||
# bing = ""
|
# bing = ""
|
||||||
# pinterest = ""
|
# pinterest = ""
|
||||||
# yandex = ""
|
# yandex = ""
|
||||||
|
|||||||
BIN
content/posts/new-homenet-architecture/featured.jpg
Executable file
BIN
content/posts/new-homenet-architecture/featured.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 MiB |
4
content/posts/new-homenet-architecture/first_net.svg
Normal file
4
content/posts/new-homenet-architecture/first_net.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 349 KiB |
4
content/posts/new-homenet-architecture/first_new_net.svg
Normal file
4
content/posts/new-homenet-architecture/first_new_net.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 307 KiB |
241
content/posts/new-homenet-architecture/index.md
Normal file
241
content/posts/new-homenet-architecture/index.md
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
---
|
||||||
|
title: "New Home Network Architecture"
|
||||||
|
date: "2025-11-20T07:30:00-07:00"
|
||||||
|
description: "The architecture of my current home network"
|
||||||
|
summary: "I decided to rearchitect my home network, and this is the result."
|
||||||
|
---
|
||||||
|
|
||||||
|
Almost two years ago, I decided to undertake another major overhaul of my home
|
||||||
|
network. It is the latest step in the long evolution of my personal systems,
|
||||||
|
and is now in a state that I'm fairly happy with.
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
I've been networking my computers together for decades. The first example that
|
||||||
|
could reasonably be called a network was around 1996, where I connected my
|
||||||
|
Amiga 1200 and Amiga 3000T together over a null-modem serial cable and ran IP
|
||||||
|
between them. I eventually got some Ethernet hardware and ran mostly simple
|
||||||
|
flat networks for a while.
|
||||||
|
|
||||||
|
### Orthodoxy
|
||||||
|
|
||||||
|
After working professionally designing and building networks and IT systems, I
|
||||||
|
had learned a few rules. Networks in particular always consisted of several
|
||||||
|
key elements:
|
||||||
|
|
||||||
|
- **Three Tiers**. You needed Core, Distribution, and Access switches. This
|
||||||
|
helps to scale the network and keep things well balanced.
|
||||||
|
- **VLANs**. Every packet needs to go through a VLAN. VLANs keep the network
|
||||||
|
segregated for security, and allow for smaller broadcast domains.
|
||||||
|
- **Firewalls**. The network has to protect the vulnerable endpoints by
|
||||||
|
blocking packets that aren't permitted by policy.
|
||||||
|
- **Virtualization**. Virtualize everything to decouple the systems from the
|
||||||
|
underlying infrastructure, keeping them portable.
|
||||||
|
|
||||||
|
Naturally, I took these ideas home and built my personal networks accordingly.
|
||||||
|
|
||||||
|
### Something's not right
|
||||||
|
|
||||||
|
Eventually, I had built myself a network that looked something like the
|
||||||
|
diagram below. I kept to the principles I was familiar with, and this was the
|
||||||
|
result.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
I had VLANs for everything coming from the VM hosts to the physical switches.
|
||||||
|
Traffic would loop through the tiers (in, out, and back in), routing
|
||||||
|
everything like it's supposed to, but this redundancy introduced unecessary
|
||||||
|
complexity. I had more VMs acting as routers than I had VMs doing productive
|
||||||
|
activity.
|
||||||
|
|
||||||
|
When I decided that I would like to start using IPv6 in my network, everything
|
||||||
|
doubled. I kept the IPv4 and IPv6 traffic on separate VLANs, and had separate
|
||||||
|
routers for everything, doubling what's in that diagram. It didn't take me
|
||||||
|
long to notice that this wasn't working out, and started to think about a new
|
||||||
|
approach.
|
||||||
|
|
||||||
|
## New Design
|
||||||
|
|
||||||
|
When I started to think about what I really needed in my home network, I
|
||||||
|
came up with several principles:
|
||||||
|
|
||||||
|
- **IPv6 First**. Using IPv6 makes so many things simpler. No NAT. Subnets
|
||||||
|
don't run out of addresses. Link-local addressing that works, and is useful.
|
||||||
|
No DHCP. *No NAT!*
|
||||||
|
- **Zero Trust**. I'm a fan of zero-trust architectures. When you place the
|
||||||
|
responsibility for security on the network, it tends to get complicated. You
|
||||||
|
make physical design choices around isolating packets on the right segments
|
||||||
|
and getting them to the right firewalls, where the network should focus on
|
||||||
|
moving traffic through it as quickly as possible. This principle of simply
|
||||||
|
getting packets to where they need to be is how the Internet scales so well.
|
||||||
|
We can keep physical LANs simple and efficient like this, and leave the
|
||||||
|
security concerns to the endpoints. The endpoints need to be secure anyways,
|
||||||
|
and we now have more modern and effective tools to help us.
|
||||||
|
- **Network Fabric**. Rather than redoing the same 3-tier model I was used to,
|
||||||
|
I wanted to do something more efficient. I was inspired by an article
|
||||||
|
written at Facebook about their
|
||||||
|
["data center fabric"](https://engineering.fb.com/2014/11/14/production-engineering/introducing-data-center-fabric-the-next-generation-facebook-data-center-network/)
|
||||||
|
architecture. This is obviously much larger than what anyone needs in a home
|
||||||
|
network, but I thought that these were good ideas that I could use.
|
||||||
|
- **Routing Protocols**. I've traditionally used [OSPF](https://en.wikipedia.org/wiki/Open_Shortest_Path_First)
|
||||||
|
in the networks I've operated. When I decided to implement IPv6 in the
|
||||||
|
network, I was using [Quagga](https://www.nongnu.org/quagga/) for the
|
||||||
|
routing software. It doesn't support OSPF areas in OSPFv3 for IPv6, which
|
||||||
|
made me reconsider. I settled on [IS-IS](https://en.wikipedia.org/wiki/IS-IS),
|
||||||
|
as it supports both IPv4 and IPv6 at the same time, and could do everything
|
||||||
|
that I needed it to do.
|
||||||
|
|
||||||
|
### Refresh, Version 1
|
||||||
|
|
||||||
|
My first refreshed network design looked like this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
I did away with all of the traditional complexity, and established two network
|
||||||
|
"fabrics" that all of the traffic would pass through. The fabrics do not
|
||||||
|
connect directly at layer 2, each side is separate. Each fabric is a single
|
||||||
|
flat network, there are no VLANs.
|
||||||
|
|
||||||
|
These were the key design decisions:
|
||||||
|
|
||||||
|
- **Routing over switching**. Every physical device connecting into the fabric
|
||||||
|
switches would conform to a basic set of requirements:
|
||||||
|
1. It will act as a router, running IS-IS to communicate with every other
|
||||||
|
device on the fabric switches.
|
||||||
|
2. Each endpoint uses a single loopback address as its network identity. It
|
||||||
|
advertises this address to the other nodes, as well as the subnets that
|
||||||
|
it can route to.
|
||||||
|
3. Routes are advertised over both fabrics, enabling [ECMP](https://en.wikipedia.org/wiki/Equal-cost_multi-path_routing)
|
||||||
|
for higher availability and bandwidth.
|
||||||
|
- **IPv6 first**. The access routers and Wifi routers only had IPv6 subnets
|
||||||
|
available for client devices. This allowed me to do away with DHCP services
|
||||||
|
on the network, only using [SLAAC](https://en.wikipedia.org/wiki/IPv6_address#Stateless_address_autoconfiguration_(SLAAC)).
|
||||||
|
Access to IPv4-only resources was through the use of
|
||||||
|
[DNS64 and the NAT64 gateway](https://en.wikipedia.org/wiki/IPv6_transition_mechanism#NAT64).
|
||||||
|
|
||||||
|
## Next Generation
|
||||||
|
|
||||||
|
At this point, I was fairly happy with the result. The network was efficient
|
||||||
|
and much easier to maintain. It was faster, thanks to ECMP and having fewer
|
||||||
|
hops. As I was using it however, I started to think about the next set of
|
||||||
|
improvements.
|
||||||
|
|
||||||
|
- **Single point routers**. I had only single devices acting as my edge,
|
||||||
|
access, and Wifi routers. I wanted some redundancy in case one failed, and
|
||||||
|
to make maintenance more transparent with the ability to fail over.
|
||||||
|
- **Virtual Machines**. Most of my workloads were set up as virtual machines.
|
||||||
|
I wanted to migrate to [Kubernetes](https://kubernetes.io/) as everything
|
||||||
|
I was running could be run there, along with many other benefits.
|
||||||
|
- **NAT64**. Here I was running IPv6 to get away from needing NAT, but I still
|
||||||
|
needed NAT. This setup was mostly working fine, but there were a few small
|
||||||
|
irritations:
|
||||||
|
- There are not very many NAT64 implementations. I was using [JooL](https://jool.mx/);
|
||||||
|
it's a non-standard Linux kernel module, and it's not really actively
|
||||||
|
developed anymore.
|
||||||
|
- The path from the NAT64 gateway out the Edge router is still IPv4, and I
|
||||||
|
still need to do NAT for IPv4 at the edge.
|
||||||
|
- Applications connecting directly to an IPv4 address weren't able to do so.
|
||||||
|
I could use [464XLAT](https://en.wikipedia.org/wiki/IPv6_transition_mechanism#464XLAT)
|
||||||
|
on endpoints that supported it, but it's yet another thing to set up.
|
||||||
|
- There's the occasional device that still doesn't support IPv6, or doesn't
|
||||||
|
support it properly.
|
||||||
|
- **BGP**. I was purely using IS-IS throughout the network, but Kubernetes
|
||||||
|
CNIs that work on bare metal systems like mine rely on BGP to advertise
|
||||||
|
routes into the network. I'd have to work out how to incorporate this.
|
||||||
|
- **Easier WiFi**. I was using a WiFi router running [OpenWRT](https://openwrt.org/),
|
||||||
|
connecting to both fabrics and running IS-IS just like everything else.
|
||||||
|
OpenWRT is great, but it is challenging to keep devices up-to-date.
|
||||||
|
- **Load Balancing**. I didn't have any solution for establishing network
|
||||||
|
load balancing for scale and availability.
|
||||||
|
|
||||||
|
### Refresh, Version 2
|
||||||
|
|
||||||
|
Incorporating the improvements I wanted to make, here is the resulting network
|
||||||
|
architecture:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The key changes are:
|
||||||
|
|
||||||
|
- **Redundant Routers**. I doubled up the edge and access routers. They can
|
||||||
|
effectively divide the load and fail over when needed.
|
||||||
|
- **Anycast for Load Balancing**. I've standardized on making use of
|
||||||
|
[Anycast](https://en.wikipedia.org/wiki/Anycast) addressing for creating
|
||||||
|
load balanced and redundant network services. I'm using this a few ways:
|
||||||
|
- The API server for my Kubernetes cluster is on an anycast address. This
|
||||||
|
address is advertised from the three control plane nodes.
|
||||||
|
- Kubernetes `LoadBalancer` type services allocate an address from a pool
|
||||||
|
and advertise it out from any node that can accept traffic for the
|
||||||
|
service.
|
||||||
|
- My recursive DNS servers providing DNS lookups for the network are on two
|
||||||
|
anycast addresses. Each edge router runs an instance and advertises one of
|
||||||
|
the addresses; this is so I can "bootstrap" the network from the edge
|
||||||
|
routers. I also run the DNS service under Kubernetes, which advertises the
|
||||||
|
same anycast addresses using ordinary `LoadBalancer` services.
|
||||||
|
- **IS-IS and BGP**. I took a few passes at getting this right. I first tried
|
||||||
|
to move fully from IS-IS to BGP only. This meant setting up peering
|
||||||
|
using IPv6 link local addresses, which worked, but it was a bit flaky under
|
||||||
|
[FRR](https://frrouting.org/). I settled on using IS-IS on the fabric
|
||||||
|
interfaces only to exchange the IPv6 loopback addresses of each node. I use
|
||||||
|
the globally routable loopback addresses for the BGP peering, which is much
|
||||||
|
easier in practice. All of the other routes (access subnets, Kubernetes
|
||||||
|
networks, anycast addresses, defaults from the edge routers) are exchanged
|
||||||
|
using BGP.
|
||||||
|
- **No NAT64**. I decided to do away with NAT64 and provide dual-stack
|
||||||
|
connectivity to the access networks. I set up [Kea](https://www.isc.org/kea/)
|
||||||
|
as a cluster on the two access routers, which is thankfully rather low
|
||||||
|
maintenance.
|
||||||
|
- **BGP Extended-Nexthop**. An added bonus to using BGP the way that I am is
|
||||||
|
that I could make use of the [BGP extended-nexthop](https://datatracker.ietf.org/doc/html/rfc8950)
|
||||||
|
capability. The old network with only IS-IS still required me to define IPv4
|
||||||
|
subnets on the switching fabrics, nodes used IPv4 addresses as the next hop
|
||||||
|
gateway addresses for IPv4 routes. With the extended-nexthop capability in
|
||||||
|
BGP, it uses the IPv6 link-local addresses for the next hop under both IPv4
|
||||||
|
and IPv6.
|
||||||
|
|
||||||
|
### High Availability
|
||||||
|
|
||||||
|
To migrate from single routers to redundant pairs, I needed to figure out a
|
||||||
|
few things.
|
||||||
|
|
||||||
|
#### Default Routes
|
||||||
|
|
||||||
|
With a single edge router, this was easy. With two, it's a bit of a puzzle. My
|
||||||
|
ISP doesn't actually provide fully routed IPv6 connectivity with my class of
|
||||||
|
service. I do get static IPv4 addresses, however. I've been using Hurricane
|
||||||
|
Electric's [tunnel broker](https://tunnelbroker.net/) service to get a routed
|
||||||
|
`/48` IPv6 subnet.
|
||||||
|
|
||||||
|
With a pair of edge routers, I've set them up with four static IPv4 addresses
|
||||||
|
on their Internet-facing interfaces. Each router gets one address. I then have
|
||||||
|
two [VRRP](https://en.wikipedia.org/wiki/Virtual_Router_Redundancy_Protocol)
|
||||||
|
interfaces, one that I use to terminate the IPv6 tunnel, and the other I use
|
||||||
|
for all IPv4 traffic. When both routers are up and running, one will have the
|
||||||
|
IPv6 tunnel and the other will have the IPv4 interface. Each one advertises a
|
||||||
|
default route for the address family it's taking care of. If one goes down,
|
||||||
|
the interface will fail over and everything reconverges rather quickly. IPv6
|
||||||
|
connections are unaffected, as the routing is stateless and traffic continues
|
||||||
|
to flow normally. IPv4 connections may get interrupted as the NAT state is
|
||||||
|
lost.
|
||||||
|
|
||||||
|
#### Access Routers
|
||||||
|
|
||||||
|
The interfaces facing the client machines provide connectivity for both IPv4
|
||||||
|
and IPv6.
|
||||||
|
|
||||||
|
The IPv6 configuration is much simpler. FRR can be configured to send router
|
||||||
|
advertisements to the subnets. Both routers are configured to advertise their
|
||||||
|
presence, as well as the subnet prefixes and DNS information. Client machines
|
||||||
|
will pick these up, and then have both routers as their default gateways.
|
||||||
|
|
||||||
|
While IPv6 configuration is seamless, IPv4 relies on VRRP to share a `".1"`
|
||||||
|
default gateway address, which, though functional, lacks the elegance of
|
||||||
|
IPv6's stateless design.
|
||||||
|
|
||||||
|
## It Works
|
||||||
|
|
||||||
|
After I got this all in place, it was finally possible to build myself a
|
||||||
|
working Kubernetes cluster and migrate all of my old services over to it. The
|
||||||
|
transition to Kubernetes not only streamlined service management but also laid
|
||||||
|
the foundation for future scalability and automation. I'll get into that
|
||||||
|
adventure in the next series of articles.
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 234 KiB |
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "New Website"
|
title: "New Website"
|
||||||
date: "2025-05-16T13:16:00-06:00"
|
date: "2025-06-12T08:00:00-06:00"
|
||||||
description: "There's finally a new website at d-b.ca."
|
description: "There's finally a new website at d-b.ca."
|
||||||
summary: "I've published my personal website. A brief history of some past endeavours, and some details on the technology behind the new site."
|
summary: "I've published my personal website. A brief history of some past endeavours, and some details on the technology behind the new site."
|
||||||
---
|
---
|
||||||
@@ -8,44 +8,56 @@ summary: "I've published my personal website. A brief history of some past endea
|
|||||||
I've finally published a proper website at [https://d-b.ca/](https://d-b.ca/).
|
I've finally published a proper website at [https://d-b.ca/](https://d-b.ca/).
|
||||||
The last time I had anything live on this domain was over 20 years ago,
|
The last time I had anything live on this domain was over 20 years ago,
|
||||||
according to the [Wayback Machine](https://web.archive.org/). What took so
|
according to the [Wayback Machine](https://web.archive.org/). What took so
|
||||||
long? Over the years I've learned a lot, and I'm constantly experimenting with
|
long? The truth is, my interests have shifted over the years, and I've been
|
||||||
new ways of getting things done. This site, while useful in its own right, is
|
learning a lot. This site, while useful in its own right, is really a
|
||||||
a culmination of the platform I've developed to host it.
|
culmination of a personal platform I've developed over time.
|
||||||
|
|
||||||
## History
|
## History
|
||||||
|
|
||||||
My first personal website was developed while I was a student at the
|
My first personal website was developed while I was a student at the
|
||||||
[University of Alberta](https://ualberta.ca), near the end of the previous
|
[University of Alberta](https://ualberta.ca), near the end of the previous
|
||||||
century. The web itself was still quite young, but the university provided
|
century. The web was still in its early stages, but the university provided
|
||||||
students with the means to publish web content. It was mostly a novelty at the
|
students with the means to publish web content. It was mostly a novelty at the
|
||||||
time and didn't last beyond my time at school, but it sparked my interest in
|
time and didn't last beyond my time at school, but it sparked my interest in
|
||||||
Internet technologies and their applications.
|
Internet technologies and their applications.
|
||||||
|
|
||||||
That early site included one interesting feature. I developed a mechanism to
|
That early site included one interesting feature. I developed a mechanism to
|
||||||
automatically update a page every time I logged into one of the school's
|
automatically update a page every time I logged into one of the school's
|
||||||
computers, so my friends could find me if they wanted to.
|
computers, so my friends could find me if they wanted to. At the time,
|
||||||
|
dynamically updating websites was a tricky thing. The most common way was
|
||||||
|
using [CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface), which is
|
||||||
|
like having a tiny program run every time someone requested a page. The
|
||||||
|
university did not allow student sites to use it. So, I wrote some shell
|
||||||
|
scripts that were called as part of my login and logout scripts that would
|
||||||
|
generate the static HTML file and write it to my web content directory. It
|
||||||
|
needed to handle cases like multiple logins, and the logout script didn't
|
||||||
|
always get triggered, so I'd have to keep an eye on it for stale entries.
|
||||||
|
|
||||||
### Self-Hosting
|
### Self-Hosting
|
||||||
|
|
||||||
I've always been an avid self-hoster. It began when I was working at a local
|
I've always been an avid self-hoster. Learning by getting a system up and
|
||||||
Internet service provider. I was able to get a special deal on a
|
running works well for me. I also value the privacy and control that it
|
||||||
business-class broadband connection at home, which included a small network
|
provides. Plus, it's much cheaper for me in the long run!
|
||||||
block (a `/28`, or 16 IP addresses) that I could use. I dedicated my most
|
|
||||||
powerful machine to be my server and developed several services, including a
|
It really began when I was working at a local Internet service provider. I was
|
||||||
new website.
|
able to get a special deal on a good broadband connection at home, which
|
||||||
|
included a small network block (a `/28`, consisting of 16 IP addresses) that I
|
||||||
|
could use. I dedicated my largest machine as my server and developed several
|
||||||
|
services, including a new website.
|
||||||
|
|
||||||
My website at that time wasn't fancy, and was geared primarily towards
|
My website at that time wasn't fancy, and was geared primarily towards
|
||||||
experimentation. I developed a simple content management system from scratch
|
experimentation. I developed a simple content management system from scratch
|
||||||
in PHP3, which I used to publish a blog. It also integrated with mailing
|
in PHP, which I used to publish a blog. It also integrated with mailing
|
||||||
lists, another area I was exploring at the time.
|
lists, another area I was exploring at the time.
|
||||||
|
|
||||||
### D-B.CA
|
### D-B.CA
|
||||||
|
|
||||||
I hadn't registered a domain name of my own in those early days, so everything
|
I hadn't registered a domain name of my own in those early days, so everything
|
||||||
resided under a friend's domain. In late 2002, I decided to finally register
|
resided under a friend's domain. In late 2002, I decided to finally register
|
||||||
one of my own, primarily so I could have a stable email address. I came up
|
one of my own, primarily so I could have a stable email address. I wanted to
|
||||||
with `d-b.ca` because someone was squatting on `db.ca` – and still is, I might
|
incorporate the initials in my name, so I came up with `d-b.ca`. This was a
|
||||||
add.
|
compromise because someone was squatting on `db.ca` at the time, and, to my
|
||||||
|
knowledge, they still are.
|
||||||
|
|
||||||
Early on, I focused mostly on operating my email services and other
|
Early on, I focused mostly on operating my email services and other
|
||||||
experiments, with little attention paid to a website. There were a few test
|
experiments, with little attention paid to a website. There were a few test
|
||||||
@@ -56,42 +68,131 @@ pages at times, but nothing substantial.
|
|||||||
One of the projects I've been following is [Hugo](https://gohugo.io/). I've
|
One of the projects I've been following is [Hugo](https://gohugo.io/). I've
|
||||||
seen and worked with various web content management systems in the past, and
|
seen and worked with various web content management systems in the past, and
|
||||||
they often feel cumbersome and present security concerns. Hugo is an example
|
they often feel cumbersome and present security concerns. Hugo is an example
|
||||||
of a "Static Site Generator," which transforms a source description of a site
|
of a "Static Site Generator." Think of it like this: instead of creating web
|
||||||
into the static resources used to serve it – much like a compiler. The
|
pages on the fly every time someone visits, Hugo takes all the raw content and
|
||||||
resulting static resources can be served as regular files from any web
|
turns it into a set of ready-to-serve files, much like a compiler turns code
|
||||||
service, without the need for dynamically generating content upon request from
|
into an executable program. The resulting static resources can be served as
|
||||||
a database, as traditional CMS systems do.
|
regular files from any web service, without the need for dynamically
|
||||||
|
generating content upon request from a database, as traditional CMS systems
|
||||||
|
do.
|
||||||
|
|
||||||
Using Hugo is much easier with a good base template, and there are
|
Using Hugo is much easier with a solid base template. There are
|
||||||
[many to choose from](https://themes.gohugo.io/). I've chosen one called
|
[many to choose from](https://themes.gohugo.io/), including the one I've
|
||||||
["Blowfish"](https://blowfish.page/) for this site.
|
selected here called ["Blowfish"](https://blowfish.page/). I like how it
|
||||||
|
looks, and it supports the style of site that this is very well.
|
||||||
|
|
||||||
Another benefit of a static site generator is that all the sources for the
|
Another benefit of a static site generator is that all the sources for the
|
||||||
site can be treated like software code, making it simple to use development
|
site can be treated like software code, making it simple to use development
|
||||||
tools like Git for version control. I keep the sources for this site in a
|
tools like [Git](https://git-scm.com/) for version control. I keep the sources
|
||||||
public repository on my own Git server. Feel free to take a look:
|
for this site in a public repository on my own Git server. Feel free to take a
|
||||||
|
look:
|
||||||
|
|
||||||
{{< gitea repo="d-b.ca/web" >}}
|
{{< gitea repo="d-b.ca/web" >}}
|
||||||
|
|
||||||
### CI/CD
|
### CI/CD
|
||||||
|
|
||||||
I've also set up a CI/CD pipeline to build and deploy the site whenever
|
I've also set up a CI/CD pipeline to build and deploy the site whenever
|
||||||
changes are made to the source repository. The CI portion is triggered by a
|
changes are made to the source repository. What does this mean?
|
||||||
push to the web repository. It runs a workflow that builds the site and
|
|
||||||
packages the resulting artifacts into a container image based on
|
**CI** = *Continuous Integration*
|
||||||
[Caddy](https://caddyserver.com/). The build container with Hugo is another
|
> This is the practice of frequently integrating changes into a source
|
||||||
image I maintain in this repository:
|
> repository. The changes are checked, assembled, and packaged through
|
||||||
|
> automated processes. [More information](https://martinfowler.com/articles/continuousIntegration.html)
|
||||||
|
|
||||||
|
**CD** = *Continuous Delivery*
|
||||||
|
> This is the capability of being able to take new changes (such as the
|
||||||
|
> outputs of the CI process) and getting them deployed and running
|
||||||
|
> automatically. [More information](https://continuousdelivery.com/)
|
||||||
|
|
||||||
|
The CI portion is triggered by a push to the
|
||||||
|
[`web`](https://git.brds.ca/d-b.ca/web) repository. It runs an automated
|
||||||
|
workflow that builds the site and packages the resulting artifacts into a
|
||||||
|
container image based on the [Caddy](https://caddyserver.com/) web server.
|
||||||
|
|
||||||
|
A container image is like a pre-packaged software environment that ensures the
|
||||||
|
website runs consistently regardless of the underlying infrastructure. The
|
||||||
|
resulting image contains everything the website needs to operate and can run
|
||||||
|
on any infrastructure that can support it, such as my own laptop or my server
|
||||||
|
cluster.
|
||||||
|
|
||||||
|
The build container with Hugo is another image that is only used to create the
|
||||||
|
website container. I maintain it in this repository:
|
||||||
|
|
||||||
{{< gitea repo="d-b.ca/hugo-builder" >}}
|
{{< gitea repo="d-b.ca/hugo-builder" >}}
|
||||||
|
|
||||||
Next, the workflow updates the CD GitOps repository to deploy this new version
|
After the workflow has built the container image for running the website, it
|
||||||
to a private staging site. When I want to publish the new version as the
|
updates the CD GitOps repository to deploy this new version immediately to a
|
||||||
production site, I use my regular GitOps repository to update the image tag,
|
private staging site. Another definition:
|
||||||
and the rest happens automatically.
|
|
||||||
|
> **GitOps** refers to the practice of managing infrastructure automation by
|
||||||
|
> keeping machine-readable descriptions of the intended infrastructure in a
|
||||||
|
> version-controlled Git repository. A CD system will monitor the repository
|
||||||
|
> for changes, immediately adding, modifying, or removing infrastructure to
|
||||||
|
> bring the state of the operational system into alignment with the source
|
||||||
|
> description.
|
||||||
|
|
||||||
|
There are many benefits to managing infrastructure this way. Changes are
|
||||||
|
automatically tracked because everything is stored in an existing code
|
||||||
|
repository system that's specifically designed for managing and tracking
|
||||||
|
change. Problematic changes can be reverted easily by reverting the change in
|
||||||
|
the repository. Automation keeps things in sync at all times - any changes
|
||||||
|
made manually outside of this system are immediately spotted and removed.
|
||||||
|
|
||||||
|
When I want to publish the new version as the production website, I use my
|
||||||
|
regular private production GitOps repository to update the image version tag,
|
||||||
|
and the rest happens automatically. The CD repository for the staging site is
|
||||||
|
public, you're welcome to check it out here:
|
||||||
|
|
||||||
{{< gitea repo="d-b.ca/db-cd" >}}
|
{{< gitea repo="d-b.ca/db-cd" >}}
|
||||||
|
|
||||||
|
#### Pipeline Diagram
|
||||||
|
|
||||||
|
{{< mermaid >}}
|
||||||
|
flowchart TB
|
||||||
|
subgraph GIT [Git Repository]
|
||||||
|
WR[(Web)]
|
||||||
|
CDR[(CD)]
|
||||||
|
end
|
||||||
|
|
||||||
|
WP(Push Web Updates)-->WR
|
||||||
|
WP ~~~ HBI
|
||||||
|
|
||||||
|
subgraph CI [CI Workflow]
|
||||||
|
CIP[Pull Source
|
||||||
|
Repository]-->BWI[Build Web
|
||||||
|
Image]
|
||||||
|
BWI-->PWI[Push Web
|
||||||
|
Image]
|
||||||
|
PWI-->UCD[Update CD
|
||||||
|
Repository]
|
||||||
|
end
|
||||||
|
|
||||||
|
PWI-->WI
|
||||||
|
WR-->CIP
|
||||||
|
|
||||||
|
subgraph DOCKER [Image Repository]
|
||||||
|
HBI((Hugo
|
||||||
|
Build))
|
||||||
|
WI((Web))
|
||||||
|
end
|
||||||
|
|
||||||
|
HBI-->BWI
|
||||||
|
|
||||||
|
UCD-->CDP(Push Image
|
||||||
|
Update)
|
||||||
|
CDP-->CDR
|
||||||
|
CDR-->CDPull
|
||||||
|
|
||||||
|
subgraph CD [CD Process]
|
||||||
|
CDPull[Pull Source Repository]-->DWI[Deploy Web Image]
|
||||||
|
end
|
||||||
|
|
||||||
|
WI-->DWI
|
||||||
|
{{< /mermaid >}}
|
||||||
|
|
||||||
## Underlying Platform
|
## Underlying Platform
|
||||||
|
|
||||||
In my next article, I'll describe the platform this site is running on, and
|
In future articles, I'll describe the evolution of the physical network and
|
||||||
some of the history and decisions that drove its design.
|
systems this site is running on, and how they enabled me to build a
|
||||||
|
[Kubernetes](https://kubernetes.io/) cluster to scale this site and run other
|
||||||
|
services, all on hardware I assembled myself!
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -1,5 +1,5 @@
|
|||||||
module git.brds.ca/drew/web
|
module git.brds.ca/drew/web
|
||||||
|
|
||||||
go 1.24.2
|
go 1.25.4
|
||||||
|
|
||||||
require github.com/nunocoracao/blowfish/v2 v2.86.0 // indirect
|
require github.com/nunocoracao/blowfish/v2 v2.92.0 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -1,2 +1,2 @@
|
|||||||
github.com/nunocoracao/blowfish/v2 v2.86.0 h1:wAnsjubJuoAWgvM0Xgh2H7+4bOBVFAgX3WUvYtXO2k8=
|
github.com/nunocoracao/blowfish/v2 v2.92.0 h1:1EgHMRaY6VI438TIAN/5luNx16lg1e0Lrbi+6kDxpdA=
|
||||||
github.com/nunocoracao/blowfish/v2 v2.86.0/go.mod h1:4SkMc+Ht8gpQCwArqiHMBDP3soxi2OWuAhVney+cuyk=
|
github.com/nunocoracao/blowfish/v2 v2.92.0/go.mod h1:4SkMc+Ht8gpQCwArqiHMBDP3soxi2OWuAhVney+cuyk=
|
||||||
|
|||||||
Reference in New Issue
Block a user