Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
0e4ed71d4e
|
|||
|
c432a2f405
|
|||
|
a93452bd90
|
|||
|
caef302171
|
|||
|
dca59d5369
|
|||
|
bf1498d3c5
|
|||
|
0ffe8c334e
|
|||
|
9bb243e2ca
|
|||
|
43b2c213e4
|
|||
|
b198db2725
|
|||
|
13372066c3
|
|||
|
6d54cbef61
|
|||
|
3062cba541
|
|||
|
f0810eb8e5
|
@@ -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,7 +16,7 @@ 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"
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
disabled = false
|
|
||||||
languageCode = "fr"
|
|
||||||
languageName = "Français"
|
|
||||||
weight = 2
|
|
||||||
title = "Drew Bowering"
|
|
||||||
|
|
||||||
[params]
|
|
||||||
displayName = "FR"
|
|
||||||
isoCode = "fr"
|
|
||||||
rtl = false
|
|
||||||
dateFormat = "2 January 2006"
|
|
||||||
logo = "img/d-bca_logo_light.png"
|
|
||||||
secondaryLogo = "img/d-bca_logo_dark.png"
|
|
||||||
description = "Drew Bowering's personal website"
|
|
||||||
# copyright = "Copy, _right?_ :thinking_face:"
|
|
||||||
|
|
||||||
[params.author]
|
|
||||||
name = "Drew Bowering"
|
|
||||||
# email = "drew@d-b.ca"
|
|
||||||
image = "img/DrewBowering.jpg"
|
|
||||||
# imageQuality = 96
|
|
||||||
headline = "Architecte informatique, Développeur, Canoeiste, Tubiste"
|
|
||||||
# bio = "A little bit about me"
|
|
||||||
links = [
|
|
||||||
{ email = "mailto:drew@d-b.ca" },
|
|
||||||
# { link = "https://link-to-some-website.com/" },
|
|
||||||
# { amazon = "https://www.amazon.com/hz/wishlist/ls/wishlist-id" },
|
|
||||||
# { apple = "https://www.apple.com" },
|
|
||||||
# { blogger = "https://username.blogspot.com/" },
|
|
||||||
# { bluesky = "https://bsky.app/profile/username" },
|
|
||||||
# { codepen = "https://codepen.io/username" },
|
|
||||||
# { dev = "https://dev.to/username" },
|
|
||||||
# { discord = "https://discord.gg/invitecode" },
|
|
||||||
# { dribbble = "https://dribbble.com/username" },
|
|
||||||
{ facebook = "https://www.facebook.com/drew.bowering" },
|
|
||||||
# { flickr = "https://www.flickr.com/photos/username/" },
|
|
||||||
# { foursquare = "https://foursquare.com/username" },
|
|
||||||
{ github = "https://github.com/drewbowering" },
|
|
||||||
# { gitlab = "https://gitlab.com/username" },
|
|
||||||
# { google = "https://www.google.com/" },
|
|
||||||
# { hashnode = "https://username.hashnode.dev" },
|
|
||||||
# { instagram = "https://instagram.com/username" },
|
|
||||||
# { itch-io = "https://username.itch.io" },
|
|
||||||
# { keybase = "https://keybase.io/username" },
|
|
||||||
# { kickstarter = "https://www.kickstarter.com/profile/username" },
|
|
||||||
# { lastfm = "https://lastfm.com/user/username" },
|
|
||||||
{ linkedin = "https://www.linkedin.com/in/drew-bowering/" },
|
|
||||||
# { mastodon = "https://mastodon.instance/@username" },
|
|
||||||
# { medium = "https://medium.com/username" },
|
|
||||||
# { microsoft = "https://www.microsoft.com/" },
|
|
||||||
# { orcid = "https://orcid.org/userid" },
|
|
||||||
# { patreon = "https://www.patreon.com/username" },
|
|
||||||
# { pinterest = "https://pinterest.com/username" },
|
|
||||||
# { reddit = "https://reddit.com/user/username" },
|
|
||||||
# { researchgate = "https://www.researchgate.net/profile/username" },
|
|
||||||
# { slack = "https://workspace.url/team/userid" },
|
|
||||||
# { snapchat = "https://snapchat.com/add/username" },
|
|
||||||
# { soundcloud = "https://soundcloud.com/username" },
|
|
||||||
# { spotify = "https://open.spotify.com/user/userid" },
|
|
||||||
# { stack-overflow = "https://stackoverflow.com/users/userid/username" },
|
|
||||||
# { steam = "https://steamcommunity.com/profiles/userid" },
|
|
||||||
# { telegram = "https://t.me/username" },
|
|
||||||
# { threads = "https://www.threads.net/@username" },
|
|
||||||
# { tiktok = "https://tiktok.com/@username" },
|
|
||||||
# { tumblr = "https://username.tumblr.com" },
|
|
||||||
# { twitch = "https://twitch.tv/username" },
|
|
||||||
# { twitter = "https://twitter.com/username" },
|
|
||||||
# { x-twitter = "https://twitter.com/username" },
|
|
||||||
# { whatsapp = "https://wa.me/phone-number" },
|
|
||||||
# { youtube = "https://youtube.com/username" },
|
|
||||||
# { ko-fi = "https://ko-fi.com/username" },
|
|
||||||
# { codeberg = "https://codeberg.org/username"},
|
|
||||||
]
|
|
||||||
@@ -67,3 +67,7 @@
|
|||||||
# name = "Categories"
|
# name = "Categories"
|
||||||
# pageRef = "categories"
|
# pageRef = "categories"
|
||||||
# weight = 20
|
# weight = 20
|
||||||
|
|
||||||
|
[[footer]]
|
||||||
|
name = "Privacy"
|
||||||
|
url = "https://comment.d-b.ca/web/privacy.html"
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
# -- Main Menu --
|
|
||||||
# The main menu is displayed in the header at the top of the page.
|
|
||||||
# Acceptable parameters are name, pageRef, page, url, title, weight.
|
|
||||||
#
|
|
||||||
# The simplest menu configuration is to provide:
|
|
||||||
# name = The name to be displayed for this menu link
|
|
||||||
# pageRef = The identifier of the page or section to link to
|
|
||||||
#
|
|
||||||
# By default the menu is ordered alphabetically. This can be
|
|
||||||
# overridden by providing a weight value. The menu will then be
|
|
||||||
# ordered by weight from lowest to highest.
|
|
||||||
|
|
||||||
[[main]]
|
|
||||||
name = "Posts"
|
|
||||||
pageRef = "posts"
|
|
||||||
weight = 10
|
|
||||||
|
|
||||||
#[[main]]
|
|
||||||
# name = "Parent"
|
|
||||||
# weight = 20
|
|
||||||
|
|
||||||
#[[main]]
|
|
||||||
# name = "example sub-menu 1"
|
|
||||||
# parent = "Parent"
|
|
||||||
# pageRef = "posts"
|
|
||||||
# weight = 20
|
|
||||||
|
|
||||||
#[[main]]
|
|
||||||
# name = "example sub-menu 2"
|
|
||||||
# parent = "Parent"
|
|
||||||
# pageRef = "posts"
|
|
||||||
# weight = 20
|
|
||||||
|
|
||||||
#[[subnavigation]]
|
|
||||||
# name = "An interesting topic"
|
|
||||||
# pageRef = "tags/interesting-topic"
|
|
||||||
# weight = 10
|
|
||||||
|
|
||||||
#[[subnavigation]]
|
|
||||||
# name = "My Awesome Category"
|
|
||||||
# pre = "github"
|
|
||||||
# pageRef = "categories/awesome"
|
|
||||||
# weight = 20
|
|
||||||
|
|
||||||
#[[main]]
|
|
||||||
# name = "Categories"
|
|
||||||
# pageRef = "categories"
|
|
||||||
# weight = 20
|
|
||||||
|
|
||||||
#[[main]]
|
|
||||||
# name = "Tags"
|
|
||||||
# pageRef = "tags"
|
|
||||||
# weight = 30
|
|
||||||
|
|
||||||
|
|
||||||
# -- Footer Menu --
|
|
||||||
# The footer menu is displayed at the bottom of the page, just before
|
|
||||||
# the copyright notice. Configure as per the main menu above.
|
|
||||||
|
|
||||||
|
|
||||||
# [[footer]]
|
|
||||||
# name = "Tags"
|
|
||||||
# pageRef = "tags"
|
|
||||||
# weight = 10
|
|
||||||
|
|
||||||
# [[footer]]
|
|
||||||
# name = "Categories"
|
|
||||||
# pageRef = "categories"
|
|
||||||
# weight = 20
|
|
||||||
@@ -56,6 +56,7 @@ forgejoDefaultServer = "https://v8.next.forgejo.org"
|
|||||||
showDate = true
|
showDate = true
|
||||||
showViews = false
|
showViews = false
|
||||||
showLikes = false
|
showLikes = false
|
||||||
|
showComments = true
|
||||||
showDateOnlyInArticle = false
|
showDateOnlyInArticle = false
|
||||||
showDateUpdated = false
|
showDateUpdated = false
|
||||||
showAuthor = true
|
showAuthor = true
|
||||||
@@ -156,7 +157,7 @@ forgejoDefaultServer = "https://v8.next.forgejo.org"
|
|||||||
# globalWidgetPosition = "Right"
|
# globalWidgetPosition = "Right"
|
||||||
|
|
||||||
[verification]
|
[verification]
|
||||||
# google = ""
|
google = "w3v6pJQgijQRPkhgVV6SIOJEPIpAR9ase26ri4tPkS8"
|
||||||
# bing = ""
|
# bing = ""
|
||||||
# pinterest = ""
|
# pinterest = ""
|
||||||
# yandex = ""
|
# yandex = ""
|
||||||
@@ -164,4 +165,10 @@ forgejoDefaultServer = "https://v8.next.forgejo.org"
|
|||||||
|
|
||||||
[rssnext]
|
[rssnext]
|
||||||
# feedId = ""
|
# feedId = ""
|
||||||
# userId = ""
|
# userId = ""
|
||||||
|
|
||||||
|
[remark42]
|
||||||
|
enabled = true
|
||||||
|
host = "https://comment.d-b.ca"
|
||||||
|
site = "d-b.ca"
|
||||||
|
locale = "en"
|
||||||
|
|||||||
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,208 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Nouveau site web"
|
|
||||||
slug: "nouveau-site-web"
|
|
||||||
date: "2025-06-12T08:00:00-06:00"
|
|
||||||
description: "Il y a enfin un nouveau site web sur d-b.ca."
|
|
||||||
summary: "J'ai publié mon site web personnel. Un aperçu de certaines réalisations passées et quelques détails sur la technologie utilisée pour le nouveau site."
|
|
||||||
---
|
|
||||||
|
|
||||||
J'ai enfin publié un site web correct à [https://d-b.ca/](https://d-b.ca/).
|
|
||||||
La dernière fois que j'avais quelque chose en ligne sur ce domaine remontait à
|
|
||||||
plus de 20 ans, selon le [Wayback Machine](https://web.archive.org/). Pourquoi
|
|
||||||
si longtemps? La vérité est que mes intérêts ont évolué au fil des années, et
|
|
||||||
j'ai beaucoup appris. Ce site, bien qu'utile en lui-même, est en réalité le
|
|
||||||
fruit d'une plateforme personnelle que j'ai développée au fil du temps.
|
|
||||||
|
|
||||||
## Histoire
|
|
||||||
|
|
||||||
Mon premier site web personnel a été développé alors que j'étais étudiant à
|
|
||||||
l'[Université de l'Alberta](https://ualberta.ca), à la fin du siècle dernier.
|
|
||||||
Le web était encore à ses débuts, mais l'université permettait aux étudiants
|
|
||||||
de publier du contenu web. À l'époque, c'était surtout une nouveauté qui n'a
|
|
||||||
pas duré au-delà de mon temps à l'école, mais cela a éveillé mon intérêt pour
|
|
||||||
les technologies internet et leurs applications.
|
|
||||||
|
|
||||||
Ce site initial comprenait une fonction intéressante. J'ai développé un
|
|
||||||
mécanisme pour mettre à jour automatiquement une page chaque fois que je me
|
|
||||||
connectais à l'un des ordinateurs de l'école, afin que mes amis puissent me
|
|
||||||
trouver si nécessaire. À l'époque, les mises à jour dynamiques des sites web
|
|
||||||
étaient difficiles. La méthode la plus courante était d'utiliser des
|
|
||||||
[CGI](https://fr.wikipedia.org/wiki/Common_Gateway_Interface), qui consistait
|
|
||||||
à exécuter un petit programme chaque fois qu'une page était demandée.
|
|
||||||
L'université n'autorisait pas les sites étudiants à utiliser cela. J'ai donc
|
|
||||||
rédigé des scripts shell qui étaient appelés lors de mes scripts de connexion
|
|
||||||
et de déconnexion, générant ainsi un fichier HTML statique et le stockant dans
|
|
||||||
mon répertoire de contenu web. Il fallait gérer des cas comme des connexions
|
|
||||||
multiples, et le script de déconnexion ne s'exécutait pas toujours, donc je
|
|
||||||
devais surveiller les entrées obsolètes.
|
|
||||||
|
|
||||||
### Hébergement autonome
|
|
||||||
|
|
||||||
J'ai toujours été un grand fan de l'hébergement autonome. Apprendre en mettant
|
|
||||||
un système en marche fonctionne bien pour moi. J'apprécie aussi la
|
|
||||||
confidentialité et le contrôle qu'il offre. De plus, c'est bien plus
|
|
||||||
économique à long terme!
|
|
||||||
|
|
||||||
Cela a vraiment commencé lorsque je travaillais chez un fournisseur local
|
|
||||||
d'accès à Internet. J'ai obtenu un bon accord sur une connexion large bande à
|
|
||||||
la maison, qui comprenait un petit bloc réseau (`/28`, comprenant 16 adresses
|
|
||||||
IP) que je pouvais utiliser. J'ai dédié mon ordinateur le plus puissant comme
|
|
||||||
serveur et développé plusieurs services, notamment un nouveau site web.
|
|
||||||
|
|
||||||
À l'époque, mon site web n'était pas très élaboré et était principalement
|
|
||||||
orienté vers l'expérimentation. J'ai développé un système de gestion de
|
|
||||||
contenu simple à partir de zéro en PHP, que j'ai utilisé pour publier un blog.
|
|
||||||
Il s'intégrait également aux listes de diffusion, un domaine que j'explorais
|
|
||||||
à l'époque.
|
|
||||||
|
|
||||||
### D-B.CA
|
|
||||||
|
|
||||||
À l'époque, je n'avais pas encore enregistré de nom de domaine personnel, donc
|
|
||||||
tout se trouvait sous un domaine d'un ami. En fin d'année 2002, j'ai décidé
|
|
||||||
d'enregistrer le mien, principalement pour avoir une adresse e-mail stable.
|
|
||||||
J'ai voulu intégrer les initiales de mon nom, donc j'ai choisi `d-b.ca`.
|
|
||||||
C'était un compromis, car quelqu'un squattait `db.ca` à l'époque, et, à ma
|
|
||||||
connaissance, c'est toujours le cas.
|
|
||||||
|
|
||||||
Au début, j'ai principalement focalisé mon attention sur l'exploitation de mes
|
|
||||||
services de messagerie et d'autres expérimentations, sans trop me soucier de
|
|
||||||
mon site web. Il y avait quelques pages de test de temps en temps, mais rien
|
|
||||||
de substantiel.
|
|
||||||
|
|
||||||
## Technologie moderne
|
|
||||||
|
|
||||||
L'un des projets que je suis depuis un certain temps est
|
|
||||||
[Hugo](https://gohugo.io/). J'ai eu l'occasion de travailler avec divers
|
|
||||||
systèmes de gestion de contenu web, et ils ont souvent semblé encombrants et
|
|
||||||
poser des problèmes de sécurité. Hugo est un exemple de «générateur de sites
|
|
||||||
statiques». Imaginez-le comme suit: au lieu de créer des pages web en temps
|
|
||||||
réel chaque fois qu'une personne les visite, Hugo prend tout le contenu brut
|
|
||||||
et le transforme en un ensemble de fichiers prêts à être servis, de manière
|
|
||||||
similaire à la manière dont un compilateur transforme du code en un programme
|
|
||||||
exécutable. Les ressources statiques résultantes peuvent être servies comme
|
|
||||||
des fichiers réguliers depuis n'importe quel service web, sans avoir besoin de
|
|
||||||
générer dynamiquement du contenu à partir d'une base de données, comme le font
|
|
||||||
les systèmes de gestion de contenu traditionnels.
|
|
||||||
|
|
||||||
Utiliser Hugo est beaucoup plus simple avec un modèle de base solide. Il
|
|
||||||
existe de nombreux modèles à choisir ([voir ici](https://themes.gohugo.io/)),
|
|
||||||
notamment celui que j'ai choisi ici appelé
|
|
||||||
[«Blowfish»](https://blowfish.page/). J'aime l'aspect qu'il a, et il prend
|
|
||||||
bien en charge le style de site que je souhaite.
|
|
||||||
|
|
||||||
Un autre avantage d'un générateur de sites statiques est que l'ensemble des
|
|
||||||
sources du site peut être traité comme du code logiciel, ce qui rend simple
|
|
||||||
l'utilisation d'outils de développement comme [Git](https://git-scm.com/) pour
|
|
||||||
le contrôle de version. Je garde les sources de ce site dans un dépôt public
|
|
||||||
sur mon propre serveur Git. N'hésitez pas à jeter un œil:
|
|
||||||
|
|
||||||
{{< gitea repo="d-b.ca/web" >}}
|
|
||||||
|
|
||||||
### CI/CD
|
|
||||||
|
|
||||||
J'ai également configuré un pipeline CI/CD pour construire et déployer le site
|
|
||||||
chaque fois que des modifications sont apportées au dépôt source. Qu'est-ce
|
|
||||||
que cela signifie ?
|
|
||||||
|
|
||||||
**CI** = *Intégration continue*
|
|
||||||
> C'est la pratique d'intégrer fréquemment les modifications dans un dépôt
|
|
||||||
> source. Les modifications sont vérifiées, assemblées et empaquetées via des
|
|
||||||
> processus automatisés. [Plus d'informations](https://martinfowler.com/articles/continuousIntegration.html)
|
|
||||||
|
|
||||||
**CD** = *Livraison continue*
|
|
||||||
> C'est la capacité à pouvoir prendre de nouvelles modifications (comme les
|
|
||||||
> sorties du processus CI) et les déployer et les exécuter automatiquement.
|
|
||||||
> [Plus d'informations](https://continuousdelivery.com/)
|
|
||||||
|
|
||||||
La partie CI est déclenchée par un push vers le dépôt
|
|
||||||
[web](https://git.brds.ca/d-b.ca/web) . Il exécute un workflow automatisé qui
|
|
||||||
construit le site et empaquette les artefacts résultants dans une image
|
|
||||||
conteneur basée sur le serveur web [Caddy](https://caddyserver.com/).
|
|
||||||
|
|
||||||
Une image conteneur est comme un environnement logiciel préemballé qui assure
|
|
||||||
que le site web fonctionne de manière cohérente, quel que soit
|
|
||||||
l'infrastructure sous-jacente. L'image résultante contient tout ce dont le
|
|
||||||
site a besoin pour fonctionner et peut s'exécuter sur n'importe quelle
|
|
||||||
infrastructure capable de l'héberger, comme mon propre ordinateur portable ou
|
|
||||||
mon cluster de serveurs.
|
|
||||||
|
|
||||||
L'image conteneur utilisée avec Hugo est une autre image, uniquement utilisée
|
|
||||||
pour créer l'image conteneur du site web. Je la maintiens dans ce dépôt:
|
|
||||||
|
|
||||||
{{< gitea repo="d-b.ca/hugo-builder" >}}
|
|
||||||
|
|
||||||
Une fois que le workflow a construit l'image conteneur pour exécuter le site
|
|
||||||
web, il met à jour le dépôt GitOps de livraison continue pour déployer
|
|
||||||
immédiatement cette nouvelle version sur un site de prévisualisation privé.
|
|
||||||
Une autre définition:
|
|
||||||
|
|
||||||
> **GitOps** désigne la pratique de gérer l'automatisation de l'infrastructure
|
|
||||||
> en conservant des descriptions lisibles par machine de l'infrastructure
|
|
||||||
> souhaitée dans un dépôt Git contrôlé en version. Un système de livraison
|
|
||||||
> continue surveille le dépôt pour des changements, ajoutant, modifiant ou
|
|
||||||
> supprimant immédiatement l'infrastructure pour aligner l'état du système
|
|
||||||
> opérationnel sur la description source.
|
|
||||||
|
|
||||||
Il y a de nombreux avantages à gérer l'infrastructure de cette manière. Les
|
|
||||||
changements sont automatiquement traqués, car tout est stocké dans un système
|
|
||||||
de dépôt de code existant conçu spécifiquement pour gérer et suivre les
|
|
||||||
changements. Les changements problématiques peuvent être facilement annulés en
|
|
||||||
annulant le changement dans le dépôt. L'automatisation maintient les choses
|
|
||||||
synchronisées en permanence - tout changement effectué manuellement en dehors
|
|
||||||
de ce système est immédiatement détecté et supprimé.
|
|
||||||
|
|
||||||
Quand je veux publier cette nouvelle version en tant que site web de
|
|
||||||
production, j'utilise mon dépôt GitOps privé régulier pour mettre à jour le
|
|
||||||
tag de version de l'image, et le reste se fait automatiquement. Le dépôt
|
|
||||||
GitOps de livraison continue pour le site de prévisualisation est public, vous
|
|
||||||
êtes les bienvenus pour le consulter ici:
|
|
||||||
|
|
||||||
{{< gitea repo="d-b.ca/db-cd" >}}
|
|
||||||
|
|
||||||
#### Diagramme du pipeline
|
|
||||||
|
|
||||||
{{< mermaid >}}
|
|
||||||
flowchart TB
|
|
||||||
subgraph GIT [Dépôt GIT]
|
|
||||||
WR[(Web)]
|
|
||||||
CDR[(CD)]
|
|
||||||
end
|
|
||||||
|
|
||||||
WP(Envoyer les mises à jour du site web)-->WR
|
|
||||||
WP ~~~ HBI
|
|
||||||
|
|
||||||
subgraph CI [Workflow CI]
|
|
||||||
CIP[Récupérer le dépôt source]-->BWI[Construire l'image web]
|
|
||||||
BWI-->PWI[Envoyer l'image web]
|
|
||||||
PWI-->UCD[Mettre à jour le dépôt CD]
|
|
||||||
end
|
|
||||||
|
|
||||||
PWI-->WI
|
|
||||||
WR-->CIP
|
|
||||||
|
|
||||||
subgraph DOCKER [Dépôt d'images]
|
|
||||||
HBI((Hugo
|
|
||||||
Build))
|
|
||||||
WI((Web))
|
|
||||||
end
|
|
||||||
|
|
||||||
HBI-->BWI
|
|
||||||
|
|
||||||
UCD-->CDP(Envoyer la mise à jour de l'image)
|
|
||||||
CDP-->CDR
|
|
||||||
CDR-->CDPull
|
|
||||||
|
|
||||||
subgraph CD [Processus CD]
|
|
||||||
CDPull[Récupérer le dépôt source]-->DWI[Déployer l'image web]
|
|
||||||
end
|
|
||||||
|
|
||||||
WI-->DWI
|
|
||||||
{{< /mermaid >}}
|
|
||||||
|
|
||||||
## Plateforme sous-jacente
|
|
||||||
|
|
||||||
Dans des articles futurs, je décrirai l'évolution du réseau physique et des
|
|
||||||
systèmes sur lesquels ce site s'exécute, et comment ils m'ont permis de
|
|
||||||
construire un cluster [Kubernetes](https://kubernetes.io/) pour mettre à
|
|
||||||
l'échelle ce site et exécuter d'autres services, tout cela sur du matériel que
|
|
||||||
j'ai assemblé moi-même!
|
|
||||||
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.93.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.93.0 h1:Icew7jladCzzfEVFmxP8rZWodDdE2moPAJZhfJvT8vU=
|
||||||
github.com/nunocoracao/blowfish/v2 v2.86.0/go.mod h1:4SkMc+Ht8gpQCwArqiHMBDP3soxi2OWuAhVney+cuyk=
|
github.com/nunocoracao/blowfish/v2 v2.93.0/go.mod h1:4SkMc+Ht8gpQCwArqiHMBDP3soxi2OWuAhVney+cuyk=
|
||||||
|
|||||||
28
layouts/partials/comments.html
Normal file
28
layouts/partials/comments.html
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{{- with .Site.Params.remark42 -}}
|
||||||
|
<hr>
|
||||||
|
<p><b>Comments and questions are welcome!</b> <i>You can use your email address to Sign In (your email address is not publicly displayed), or you can use your Google or GitHub account. All comment data is hosted locally by d-b.ca and is not tracked. Please refer to the <a href='https://remark42.com' target="_blank"><b>Remark42</b></a> <a href='https://remark42.com/privacy/' target="_blank">Privacy Policy</a> for more information.</i></p>
|
||||||
|
<div id="remark42"></div>
|
||||||
|
<script>
|
||||||
|
var remark_config = {
|
||||||
|
host: '{{ .host }}',
|
||||||
|
site_id: '{{ .site }}',
|
||||||
|
components: ['embed'],
|
||||||
|
url: '{{ $.Permalink }}',
|
||||||
|
theme: document.documentElement.classList.contains("dark") ? 'dark' : 'light',
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("appearance-switcher").addEventListener('click', event => {
|
||||||
|
window.REMARK42.changeTheme(document.documentElement.classList.contains("dark") ? 'light' : 'dark');
|
||||||
|
});
|
||||||
|
|
||||||
|
!function(e, n) {
|
||||||
|
for (var o = 0; o < e.length; o++) {
|
||||||
|
var r = n.createElement('script'),
|
||||||
|
c = '.js',
|
||||||
|
d = n.head || n.body;
|
||||||
|
'noModule' in r ? (r.type = 'module', c = '.mjs') : r.async = !0, r.defer = !0, r.src = remark_config.host + '/web/' + e[o] + c, d.appendChild(r)
|
||||||
|
}
|
||||||
|
}(remark_config.components || ['embed'], document);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{{- end -}}
|
||||||
Reference in New Issue
Block a user