Host your own blog
on Kubernetes
using gitops with Flux
Ruby Gem
Ruby via Docker
docker pull jekyll/jekyll:3.8.6
Create container
docker run --rm --volume="$PWD:/srv/jekyll" \
-it jekyll/jekyll:3.8.6 /bin/bash
(inside container)
jekyll new myblog
exit
(outside container)
cd myblog
git init
git add .
git commit -m "Initial"
Start Jekyll webserver
exposing port 4000>
docker run --rm --volume="$PWD:/srv/jekyll" \
-p 4000:4000 -it jekyll/jekyll:3.8.6 /bin/bash
(inside container)
jekyll serve
(outside container)
open http://localhost:4000
version: '3'
services:
jekyll:
image: jekyll/jekyll:3.8.6
container_name: myblog
environment:
- JEKYLL_ENV=docker
command: jekyll serve --force_polling --livereload --drafts
ports:
- 4000:4000
- 35729:35729
volumes:
- ./:/srv/jekyll
docker-compose up
(outside container)
touch Gemfile
...
# gem "minima", "~> 2.0"
gem "jekyll-theme-hydejack"
...
touch _config.yml
...
# theme: minima
theme: jekyll-theme-hydejack
...
Restart docker compose
(outside container)
mkdir _drafts
touch _drafts/my-first-post.md
## My first blog post
Hello everyone
open http://localhost:4000/2021/06/18/my-first-post
git add _drafts/my-first-post.md
git commit -am "First post"
gh repo create myblog
git push
touch Dockerfile
FROM nginx:1.15-alpine
COPY _site /usr/share/nginx/html
mkdir .circleci
curl -L -O .circleci/config.yml \
https://raw.githubusercontent.com/flurdy/blog/master/.circleci/config.yml
...
jobs:
build:
docker:
- image: circleci/ruby:2.6.0-node
...
steps:
...
- run:
name: Jekyll build
command: bundle exec jekyll build
...
- persist_to_workspace:
root: ./
paths:
- _site
Part 2
...
build-image:
docker:
- image: circleci/buildpack-deps:stretch
environment:
IMAGE_NAME: quay.io/yourusername/myblog
...
steps:
...
- run:
name: Build Docker image
command: docker build -t $IMAGE_NAME:latest .
...
Part 3
...
publish-image:
...
- run:
name: Upload to registry
command: |
echo "$DOCKER_PASSWORD" | docker login quay.io -u "$DOCKER_LOGIN" --password-stdin
docker push $IMAGE_NAME:latest
IMAGE_TAG="1.0.${CIRCLE_BUILD_NUM}"
docker tag $IMAGE_NAME:latest $IMAGE_NAME:$IMAGE_TAG
docker push $IMAGE_NAME:$IMAGE_TAG
...
Part 4
...
workflows:
version: 2
build-master:
- jobs:
- build:
filters:
branches:
only: master
- build-image:
requires:
- build
...
- publish-image:
requires:
- build-image
...
brew install circleci
circleci config validate
git mv _drafts/my-first-post.md _posts/2021-06-18-my-first-post.md
git commit -am "Published first post"
git push
brew install kubectl
Optionally
brew install kubectx
brew install Helm
(DOKS)
digitalocean.com/products/kubernetes/
brew install doctl
doctl kubernetes cluster kubeconfig save 1234-1234-41234
kubectl cluster-info
kubectx
brew install fluxcd/tap/flux
export GITHUB_USER=your-username
export GITHUB_TOKEN=your-token
flux check --pre
flux bootstrap github \
--owner=$GITHUB_USER \
--repository=fleet-doubledragon \
--branch=main \
--path=./clusters/doubledragon-prod \
--personal
git clone https://github.com/$GITHUB_USER/fleet-doubledragon
cd fleet-doubledragon
tree
fleet-doubledragon/
├── clusters/
│ └── doubledragon-prod/
│ └── flux-system/
│ ├── gotk-components.yaml
│ ├── gotk-sync.yaml
│ └── kustomization.yaml
└── README.md
kubectl get all -n flux-system
brew install kubeseal
Add Helm repository
flux create source helm sealed-secrets \
--interval=2h \
--url=https://bitnami-labs.github.io/sealed-secrets
Create Helm release
flux create helmrelease sealed-secrets \
--interval=2h \
--release-name=sealed-secrets \
--target-namespace=flux-system \
--source=HelmRepository/sealed-secrets \
--chart=sealed-secrets \
--chart-version=">=1.15.0-0" \
--crds=CreateReplace
git add sealed-secrets-source ???
git add sealed-secrets-release ???
cd clusters/doubledragon-prod
mkdir secrets
kubeseal --fetch-cert \
--controller-name=sealed-secrets \
--controller-namespace=flux-system \
> secrets/pub-sealed-secrets.pem
git add secrets/pub-sealed-secrets.pem
Create secret
cd clusters/doubledragon-prod
mkdir registry
kubectl create secret docker-registry quay-registry \
--dry-run
--docker-server=quay.io \
--docker-username:MYROBOTUSER \
--docker-password=MYROBOTPASSWORD \
-o yaml > registry/quay-clear.yml
Encrypt secret
kubeseal --format=yaml \
--cert=secrets/pub-sealed-secrets.pem \
< registry/quay-clear.yml \
> registry/quay-encrypted.yml
git add registry/quay-encrypted.yml
rm registry/quay-clear.yml
Verify
git commit -m "registry added"
git push
kubectl get secret,sealedsecret
mkdir -p clusters/doubledragon-prod/arcade
cd clusters/doubledragon-prod/arcade
touch namespace.yml
apiVersion: v1
kind: Namespace
metadata:
name: arcade
git add namespace.yml
git commit -m "Added arcade namespace"
git push
mkdir myblog
touch myblog/deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myblog-deployment
# annotations:
# fluxcd.io/automated: "true"
spec:
selector:
matchLabels:
app: myblog
replicas: 2
template:
metadata:
labels:
app: myblog
spec:
containers:
- name: myblog-container
image: quay.io/yourusername/myblog:1.0.1
ports:
- containerPort: 80
imagePullSecrets:
- name: quay-registry
git add myblog/deployment.yml
touch myblog/service.yml
apiVersion: v1
kind: Service
metadata:
name: myblog-service
spec:
selector:
app: myblog
ports:
- protocol: TCP
port: 80
targetPort: 80
git add myblog/service.yml
Monitor rollout
git commit -m "Added myblog"
git push
watch flux get kustomizations
watch kubectl -n default get deployments,services
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/myblog 2/2 2 2 108s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/myblog ClusterIP 10.100.149.126 9898/TCP,9999/TCP 108s
Ingress-nginx
flux create source helm nginx \
--url https://kubernetes-charts.storage.googleapis.com
flux create helmrelease --chart my-nginx \
--source HelmRepository/nginx \
--chart-version 1.26.1 \
--namespace nginx
Create Ingress
touch clusters/doubledragon-prod/arcade/myblog/ingress.yml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: myblog-ingress
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: myblog.example.com
http:
paths:
- backend:
serviceName: myblog-service
servicePort: 80
git add clusters/doubledragon-prod/arcade/myblog/ingress.yml
Publish
git commit -m "myblog ingress"
git push
Monitor
watch kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
myblog-ingress myblog.example.com 5.4.3.2 80 3s
Test
curl -H "Host: myblog.example.com" \
--resolve hello.example.com:80:5.4.3.2 \
http://myblog.example.com | lynx -stdin
Image repository
cd ./clusters/doubledragon-prod
flux create image repository myblog \
--image=quay.io/yourusername/myblog \
--interval=5m \
--export > ./arcade/myblog/registry.yaml
Image policy
flux create image policy myblog \
--image-ref=myblog \
--select-semver=1.0.x
--export > ./arcade/myblog/policy.yaml
git add arcade/myblog/registry.yaml
git add arcade/myblog/policy.yaml
git commit -m "image scan"
git push
Force Flex to pull
flux reconcile kustomization flux-system --with-source
List images found
flux get image repository myblog
flux get image policy myblog
touch arcade/myblog/deployment.yaml
...
spec:
containers:
- name: myblog-container
image: quay.io/yourusername/myblog:1.0.1 # {"$imagepolicy": "flux-system:myblog"}
...
flux create image update flux-system \
--git-repo-ref=flux-system \
--git-repo-path="./clusters/doubledragon-prod" \
--checkout-branch=main \
--push-branch=main \
--author-name=fluxcdbot \
--author-email=fluxcdbot@users.noreply.github.com \
--commit-template="{{range .Updated.Images}}{{println .}}{{end}}" \
--export > ./flux-system/automation.yaml
git add arcade/myblog/deployment.yaml
git add flux-system/automation.yaml