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
... 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
... 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
... 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
... 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 . ...
... 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 . ...
... 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 . ...
... 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 ...
... 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 ...
... 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 ...
... 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 ...
... 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 ...
... workflows: version: 2 build-master: - jobs: - build: filters: branches: only: master - build-image: requires: - build ... - publish-image: requires: - build-image ...
... workflows: version: 2 build-master: - jobs: - build: filters: branches: only: master - build-image: requires: - build ... - publish-image: requires: - build-image ...
... workflows: version: 2 build-master: - jobs: - build: filters: branches: only: master - build-image: requires: - build ... - publish-image: requires: - build-image ...
... 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
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
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
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
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
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
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