前言

这篇文件记录了我将博客应用部署在真实服务器的 Kubernetes 上运行的整个过程❤️。

安装和配置 k8s 集群环境

第一步是先安装需要的软件包:

# 先安装Docker
➜  ~ sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
➜  ~ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
➜  ~ sudo apt-key fingerprint 0EBFCD88
# 由于我在服务器安装了Python3.7,/usr/bin/python3链接不再不再是原来系统自带的Python3.5,需要明确指定
➜  ~  sudo sed -i 's/python3/python3.5/g' /usr/bin/add-apt-repository
➜  ~ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
➜  ~ sudo apt-get update
➜  ~ sudo apt install docker-ce
➜  ~ sudo systemctl enable docker
# 安装k8s用到的三个包
➜  ~ cat <<EOF | sudo tee -a /etc/apt/sources.list.d/kubernetes.list
deb http://mirrors.ustc.edu.cn/kubernetes/apt kubernetes-xenial main
EOF
➜  ~ sudo apt-get update
W: GPG error: http://mirrors.ustc.edu.cn/kubernetes/apt kubernetes-xenial InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 6A030B21BA07F4FB
W: The repository 'http://mirrors.ustc.edu.cn/kubernetes/apt kubernetes-xenial InRelease' is not signed.
N: Data from such a repository can't be authenticated and is therefore potentially dangerous to use.
N: See apt-secure(8) manpage for repository creation and user configuration details.
# 由于没有对应的公钥所以不能验证这个源包的签名,我这里给大家看墙内对应解决方案,否则可以用官网提供的名字添加
# 上面的输出有个 6A030B21BA07F4FB 下面2句都要用到它:
➜  ~ gpg --keyserver keyserver.ubuntu.com --recv-keys 6A030B21BA07F4FB
➜  ~ gpg --export --armor 6A030B21BA07F4FB | sudo apt-key add -
OK
➜  ~ sudo apt-get update  # 这次就正常了
➜  ~ sudo apt-get install -y kubelet kubeadm kubectl
➜  ~ sudo apt-mark hold kubelet kubeadm kubectl  # 停止它们的自动更新
➜  ~ kubelet --version
Kubernetes v1.16.1

这次安装的 3 个软件包分别用来:

  • kubeadm。用来初始化集群
  • kubelet。在集群中的每个节点上用来启动 Pod (豆荚) 和 container (容器) 等。
  • kubectl。用来与集群通信的命令行工具

写文本时,Kubernetes 版本为v1.16.1

搭建主节点环境

我的博客用的云服务器是单节点的,而且由于它已经是虚拟主机 (腾讯用的的是自研的基于 KVM 的 Havisor) 所以无法再被虚拟化成多台主机,所以只能搭建只有一个主节点 (没有 Node 节点) 的集群:

# 下面这部分每个服务器都要执行:
➜  ~ sudo swapoff -a  # 从v1.8开始要求关闭系统的Swap,但永久关闭需要编辑/etc/fstab注释掉交换分区所在行
➜  ~ cat <<EOF | sudo tee -a /etc/docker/daemon.json  # 统一cgroup驱动为systemd, 并且使用国内源
pipe heredoc> {
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2",
  "registry-mirrors": [
    "https://registry.docker-cn.com",
    "http://hub-mirror.c.163.com",
    "http://docker.mirrors.ustc.edu.cn",
    "http://dockerhub.azk8s.cn"
  ]
}
pipe heredoc> EOF
➜  ~ sudo systemctl daemon-reload
➜  ~ sudo systemctl restart docker

# 下面这部分只在Master执行:
# 初始化,使用阿里云源,指定Pod网络地址(10.244.0.0/16网段) ,要下载image,需要花费几分钟
➜  ~ sudo kubeadm init --image-repository registry.aliyuncs.com/google_containers -v 5 --pod-network-cidr 10.244.0.0/16
...  #  省略输出
Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 172.17.87.14:6443 --token r4aa9m.n3xtkspn48x4j0s4 \
    --discovery-token-ca-cert-hash sha256:047fbe9327aae75856433d95d298b97ca6de2f3efe253cfed618369448e49ff5
➜  ~ mkdir -p $HOME/.kube
➜  ~ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
➜  ~ sudo chown $(id -u):$(id -g) $HOME/.kube/config

使用 Calico

容器网络是容器选择连接到其他容器、主机和外部网络的机制,但据说 Docker 原生的通信方案效率很差,所有通常会选择开源的 CNI 插件在配置或销毁容器时动态配置适当的网络配置和资源。全部可选网络插件列表可以看:https://kubernetes.io/docs/concepts/cluster-administration/addons/#networking-and-network-policy

最主流的是 Flannel 和 Calico,它们之间的对比我没有实际经验,可以参考延伸阅读链接 2。出于我个人对于性能的极端追求选择了「Calico+BGP 模式 (默认)」,当然对于小博客来说我想多了~

➜  ~ kubectl apply -f https://docs.projectcalico.org/v3.10/manifests/calico.yaml
# 等待1-2分钟,查看kube-system命名空间下的pod状态都是Running的了:
➜  ~ kubectl get pod -n kube-system
NAME                             READY   STATUS    RESTARTS   AGE
calico-kube-controllers-6b64bcd855-mf2nw   1/1     Running   0          2m18s
calico-node-fttbw                          1/1     Running   0          2m18s
coredns-58cc8c89f4-px2kz                   1/1     Running   0          3m17s
coredns-58cc8c89f4-xhxsr                   1/1     Running   0          3m17s
etcd-master                                1/1     Running   0          2m35s
kube-apiserver-master                      1/1     Running   0          2m33s
kube-controller-manager-master             1/1     Running   0          2m16s
kube-flannel-ds-amd64-jmbpf                1/1     Running   0          2m57s
kube-proxy-7hl5l                           1/1     Running   0          3m17s
kube-scheduler-master                      1/1     Running   0          2m28s

# 我这个博客应用没有其他Node节点,所以会主节点上安排Pod,但出于安全原因考虑需要主节点隔离:
➜  ~ kubectl taint nodes --all node-role.kubernetes.io/master-
node/vm-17-14-ubuntu untainted

搭建节点环境 (可选)

下面这部分只在 Node 执行,如果你搭建的环境多 Node 需要执行下面这部分:

# 按之前的kubeadm init输出提示,每个节点执行join加入集群
➜  ~ sudo kubeadm join 172.17.87.14:6443 --token r4aa9m.n3xtkspn48x4j0s4 --discovery-token-ca-cert-hash sha256:047fbe9327aae75856433d95d298b97ca6de2f3efe253cfed618369448e49ff5

# 等待几分钟后,就可以查看集群状态了:
➜  ~ kubectl get nodes
NAME     STATUS     ROLES    AGE     VERSION
master   Ready      master   9m      v1.16.1
...

这部分由于我们这次实验没有节点并没有执行。

安装 NGINX Ingress 控制器

和之前在 Minikube 虚拟机或者 Docker for Mac 上安装用的配置文件不同,需要这样:

➜  ~ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
➜  ~ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/baremetal/service-nodeport.yaml

# 稍等1分钟:
➜  ~ kubectl get pods --all-namespaces -l app.kubernetes.io/name=ingress-nginx
NAMESPACE       NAME                                        READY   STATUS    RESTARTS   AGE
ingress-nginx   nginx-ingress-controller-568867bf56-7b4qb   1/1     Running   0          87s

这个 Pod 里面运行了控制器和 Nginx,控制器会把 Ingress 资源转化为 Nginx 反向代理配置文件。

偏个题,之前几篇文章都是在 Minikube 虚拟机里面,主要是当时 macOS 上使用 Ingress 控制器不知道怎么把域名绑定在 localhost 上,这次实验正好找到了方案,如果你想本地跑 NGINX Ingress 控制器,不必使用 Minikube 并minikube addons enable ingress,还可以:

➜  ~ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/cloud-generic.yaml

看一下 cloud-generic.yaml 的内容:

kind: Service
apiVersion: v1
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  externalTrafficPolicy: Local
  type: LoadBalancer
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
  ports:
    - name: http
      port: 80
      targetPort: http
    - name: https
      port: 443
      targetPort: https

所以重点是type: LoadBalancer和 externalTrafficPolicy (将外部流量路由到节点本地),#TIL

另外提一下,基于 Nginx 的 Ingress Controller 有两种,一种是 K8s 社区提供的 ingress-nginx (上面我们用到的),另一种是 Nginx 社区提供的 kubernetes-igress ,他俩们之间的区别可以看延伸阅读链接 4

后记

然后应用配置并初始化数据库:

➜  ~ kubectl apply -f k8s
➜  ~ kubectl get pods -l app.kubernetes.io/name=lyanna
NAME                                 READY   STATUS    RESTARTS   AGE
lyanna-deployment-579f79ff47-q84gm   1/1     Running   0          5m31s
lyanna-deployment-579f79ff47-rfvgv   1/1     Running   0          5m31s
lyanna-deployment-579f79ff47-w7dgh   1/1     Running   0          5m31s
➜  ~ kubectl exec -it lyanna-deployment-579f79ff47-q84gm -- sh -c ./setup.sh  # 在其中一个Pod上执行

现在【集群】已经搭建好了,指定 host 后就可以访问http://lyanna.local/或者https://lyanna.local/(当然证书是不安全的) 了

修改 lyanna 的配置文件

上篇文章写好的 k8s 配置文件 可以在本地 k8s 上运行了,但是放到生产环境还有一些要修改的。我们先分析一下用云服务器会有什么不同:

  • MySQL 服务使用云服务,所以不需要部署 mariadb.yaml
  • Memcached 要跑在 k8s 上,考虑到 aiomcache 多个 Memcached 实例存在缓存共享问题 (不像我厂 libmc 那样提供的集群配置),Pod 已经保证了有一个 Memcached 运行状态,且现在博客应用就使用着单个进程,我会把副本数调整成 1
  • Redis 不仅要跑在 k8s 上,还需要持久化存储数据,并且要考虑 Redis 导数据到 k8s 中的容器
  • 需要应用 local_settings.py 中配置 (也包含数据库密码)
  • 需要把之前的 Nginx 的配置应用到 NGINX Ingress 控制器上
  • HTTPS 支持 (之前在博客就使用了 Certbot + Let's Encrypt 的方案)
  • 本地用的域名是 lyanna.local,正式域名是 dongwm.com 怎么在不影响现有代码逻辑的前提下让域名配置方便呢?

问题还挺多,我们挨个解决吧。

线上不部署 MariaDB

其实在一开始时已经考虑到这点,上篇文章也提过:我特意把 mariadb.yaml 放在了 optional 子目录,不使用 - R 参数即可

应用生产环境配置

k8s/config.yaml这个配置文件中包含了数据库、Redis 的设置,除此之外会改变的还有 Ingress 设置中的域名,所以我把 Ingress 部分也迁了进来。这个配置文件已经写好了默认设置,不过来看一下我实际使用的效果 (当然已隐去关键信息)。先看 ConfigMap 部分:

apiVersion: v1
kind: ConfigMap
metadata:
  name: lyanna-cfg
data:
  db_port: "3306"
  database: YOUR_DB
  db_user: DB_USER
  db_password: DB_PASSWD
  memcached_host: lyanna-memcached
  replication-password: lyanna
  redis_sentinel_host: ""
  redis_sentinel_port: ""
  redis_url: redis://YOUR_SERVER_IP:6379
  db_url: mysql://DB_USER:DB_PASSWD@YOUR_SERVER_IP:3306/YOUR_DB?charset=utf8

ConfigMap 部分使用了外部的 Redis-server 和 MySQL。MySQL 是我预想的,因为我的服务器资源有限 (2 核 CPU/4G 内存),数据库是非常占资源且重要的,另外还要自己做监控和数据备份等麻烦,所以一开始我就使用了云 MySQL,准备在 k8s 里面使用外部的 MySQL 服务。

而 Redis 也用外部的是我没有预计到的在我试用时发现 k8s 非常占 CPU 和内存,直接让服务器负载涨到 40+,且内存占用 > 90%,所以第一步我先限制了不同类型的 Pod 占用资源,比如 lyanna 应用是这样写的:

resources:
  limits:
    cpu: 300m  # 1m等于千分之一个CPU
    memory: 200Mi
  requests:
    cpu: 200m
    memory: 100Mi

也就是每个 Pod 最多占用 0.3CPU 和 200M 内存,请求最多 0.2CPU 和 100M 内存 (为容器管理计算资源部分内容可以看延伸阅读链接 5)。不用的 Pod 限制的量是不一样的,但是架不住 Pod 多,由于服务器还运行很多内容,所以一部分 Pod 就已经占满服务器剩余资源,每次都有 1-2 个 Pod 是 Pending 状态.... 哎,当时写的时候 Redis Sentinel 集群有 4 个 Pod,其实我有点设计复杂了,只跑一个 Master 就算了。所以我索性珍把 Redis 也迁到 k8s/optional 下作为备选,直接用了之前已经购买用于其他用途的云 Redis 服务。

一个细节,如果想使用单 Redis 的话,除了要设置redis_url还需要让redis_sentinel_host或者redis_sentinel_port为空,这样 lyanna 就不会把 Redis 当做 Redis Sentinel 集群了。

应用 Nginx 的配置

现在线上运行的 Nginx 使用了 srv/templates/nginx.conf 中的配置,其中有多个 location 设置各不相同,还有一些全局的如keepalive_timeout等配置,在 k8s 中需要遵循 Nginx Ingress 控制器的思路:把对应配置项写入 ConfigMap 里,或者使用对应 Annotation。

Label 和 Annotation 都可以将元数据关联到 Kubernetes 资源对象。Label 主要用于选出挑选出满足特定条件的对象,而 Annotation (注解) 更宽泛,可以为对象附加任意的非标识的元数据,可以说按开发者和团队的意愿随意添加,注解中的元数据,可以很小也可以很大,可以是结构化的,也可以是非结构化的,能够包含标签不允许的字符。

全局的 Nginx 设置 (k8s/nginx.yaml):

apiVersion: v1
data:
  keep-alive: "5"
  http-snippet: |
      proxy_cache_path /tmp/nginx-cache levels=1:2 keys_zone=static-cache:2m max_size=100m inactive=7d use_temp_path=off;
      proxy_cache_key $scheme$proxy_host$request_uri;
      proxy_cache_lock on;
      proxy_cache_use_stale updating;
kind: ConfigMap
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

Nginx Ingress 控制器已经内置了很多 Nginx 相关配置项,可以通过 ConfigMap 的方式指定,如keep-alive,具体的可以看延伸阅读链接 7。http-snippet是在 http 区块下把 Nginx Ingress 控制器不支持的 Nginx 配置直接写进去。

然后就是 Ingress 部分。和之前文档提的单个 Ingress 不同,这里有三个 Ingress 指向一个域名 (blog.dongwm.com):

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: lyanna-ing-static
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/proxy-buffering: "on"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      include /etc/nginx/mime.types;
      proxy_cache static-cache;
      proxy_ignore_headers Cache-Control;
      proxy_cache_valid any 30m;
      add_header X-Cache-Status $upstream_cache_status;
spec:
  rules:
  - host: blog.dongwm.com
    http:
      paths:
        - path: /static
          backend:
            serviceName: lyanna-svc
            servicePort: 80
        - path: /img
          backend:
            serviceName: lyanna-svc
            servicePort: 80
        - path: /fonts
          backend:
            serviceName: lyanna-svc
            servicePort: 80
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: lyanna-ing-admin
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      allow "1.2.3.4";
      deny all;
spec:
  rules:
  - host: blog.dongwm.com
    http:
      paths:
        - path: /admin
          backend:
            serviceName: lyanna-svc
            servicePort: 80
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: lyanna-ing
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: blog.dongwm.com
    http:
      paths:
        - path: /
          backend:
            serviceName: lyanna-svc
            servicePort: 80

上述部分也在k8s/config.yaml里面。三个 Ingress 的后端都是 lyanna-svc 服务,总体是根据 Nginx 配置的不同来分开的:

  • lyanna-ing-static。/static、/img、fonts 这三个静态文件子路由
  • lyanna-ing-admin。管理后台 /admin 部分,只允许某些 IP 访问
  • lyanna-ing。Web 服务,除上述 2 个 Ingress 管理的其他路由

通过 annotations 键来设置 Nginx:

  • kubernetes.io/ingress.class: "nginx"。指定使用 Nginx Ingress 控制器,其实在我们这个例子中不指定也行,反正只有一个控制器
  • nginx.ingress.kubernetes.io/use-regex: "true"。如果使用正则需要加这个 Annotation
  • nginx.ingress.kubernetes.io/configuration-snippet。把对应 location 下的 Nginx 配置写进去,这部分基于来自原来的 nginx.conf

注意静态文件部分的 Ingress 里面有这么一句add_header X-Cache-Status $upstream_cache_status;,这是 Nginx Upstream 缓存,服务跑起来之后可以请求静态文件体验一下:

➜  ~ kubectl exec nginx-ingress-controller-568867bf56-7b4qb -n ingress-nginx cat /etc/nginx/nginx.conf|less  # 生成的nginx.conf,可以测试配置是否生效
# 本地试验缓存效果:
❯ curl --head http://lyanna.local/static/css/index.min.css
...
X-Cache-Status: MISS

❯ curl --head http://lyanna.local/static/css/index.min.css
...
X-Cache-Status: HIT

❯ curl --head http://lyanna.local/static/css/index.min.css
...
X-Cache-Status: HIT

k8s 和外部服务通信的问题

k8s 里面的容器之前是可以方便的内部通信的,但和 MySQL 和 Redis 这 2 个外部云服务怎么通信呢?

我一开始是觉得应该自带了某个域名指向主节点的 IP,在我的 macOS 上的 /etc/hosts 不知道什么时候写入了127.0.0.1 kubernetes.docker.internal,后来发现 kubernetes.docker.internal 这个域名只在 Docker for Mac 下可用,在实际服务器上没有找到这样的域名,但是尝试了一下在容器中其实可以 ping 通主节点的那些 ip,但是 ping 不同和主节点 ip 同网站的其他 IP (云服务所在 IP),所以我想到了通过 iptables 做端口转发,转发云服务流量,比如 MySQl 是这样操作的:

# 172.17.17.14是主节点IP,172.17.17.13是MySQL服务IP
➜  ~ sudo iptables -t nat -A OUTPUT -o lo -d 172.17.17.14 -p tcp --dport 3306 -j DNAT  --to-destination 172.17.17.13:3306
➜  ~ sudo iptables -t nat -A POSTROUTING -d 172.17.17.13 -p tcp --dport 3306 -j SNAT --to 172.17.17.14
# 我的服务器是Ubuntu,所以用下面方法让策略生效
➜  ~ sudo sh -c "iptables-save > /etc/iptables/rules.v4"
➜  ~ sudo sh -c "iptables-restore < /etc/iptables/rules.v4"

如本例,把 k8s/config.yaml 中的YOUR_SERVER_IP改成172.17.17.14就能实现连通了。

让外部流量转到到 Nginx Ingress 上

本来我是想直接让 Nginx Ingress 接收用户请求,不知道你有没有发现有什么问题?

在整个过程中,我使用 kubectl 都用了一个非 root 账号,全程没有速度,如果让用户通过 HTTP/HTTPS 协议访问服务必要要通过 80/443 端口,但是 1024 以下的端口是 root 管理的,所以我们需要有一种转发方案。

我在这里用了 2 层 Nginx 结构:本机的 Nginx 接受用户请求,用 upstream 把请求转发给 nginx-ingress。

当然用 iptables 做 NAT 服务器转发给 nginx-ingress 也可以,但是请教我厂平台工程师说曾经试验有性能问题就作罢了。

那 upstream 里面怎么找到 ingress 的 ip 呢?大家还记不记得之前曾经用baremetal/service-nodeport.yaml创建过一个 Service?

➜  ~ kubectl get svc -n ingress-nginx
NAME            TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx   NodePort   10.105.221.35   <none>        80:31255/TCP,443:30786/TCP   1h16m
➜  ~ ping 10.105.221.35  # ping不同
PING 10.105.221.35 (10.105.221.35) 56(84) bytes of data.
^C
--- 10.105.221.35 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2011ms
➜  ~ curl 10.105.221.35  # 但是curl可以
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>openresty/1.15.8.2</center>
</body>
</html>
➜  ~ curl localhost:31255  # 使用Node的对应随机端口也可以
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>openresty/1.15.8.2</center>
</body>
</html>

我们可以直接用服务的 NodePort,另外注意服务 ip 是虚拟的可以访问但是不能 ping 通。选择 2 层 Nginx 还有一个原因,就是本机 Nginx 里面还有其他 Web 服务的配置,需要不影响它们。

ok,到这里我们可以直接把这部分写入本机的 Nginx 配置中:

# 新建/etc/nginx/sites-enabled/lyanna.conf
server {
    server_name blog.dongwm.com;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header X-NginX-Proxy true;
        proxy_redirect off;
        proxy_pass http://10.105.221.35;
        proxy_http_version 1.1;
    }
}

重启 Nginx 后,访问http://blog.dongwm.com/就可以看到博客效果了 (当然前提是要在域名解析里面加好 blog.dongwm.com 的 A 记录)!

支持 HTTPS

本来我是想在 Nginx Ingress+Let’s Encrypt 实现外部服务的自动化 HTTPS (用 cert-manager,具体文档延伸阅读链接 9),现在既然外部有 Nginx,还用原来的 Certbot 实现即可:

➜  ~ sudo certbot -d blog.dongwm.com
...  # 省略输出
Congratulations! You have successfully enabled https://blog.dongwm.com
...

现在再访问https://blog.dongwm.com就可以啦,如果访问http://blog.dongwm.com会自动跳转到https://blog.dongwm.com

管理 local_settings.py

在 k8s 中管理文件我觉得最好的方法是用 ConfigMap,看一下 k8s/config.yaml 的 ConfigMap 中的最后一部分:

apiVersion: v1
kind: ConfigMap
metadata:
  name: lyanna-cfg
data:
  ...
  local_settings.py: |
    SITE_TITLE = '小明明s à domicile(Kubernetes版)'
    BLOG_URL = 'https://blog.dongwm.com'  # 省略了很多配置

接着在 Deployment 的容器配置中挂载它:

containers:
  - image: dongweiming/lyanna:latest
  ...
    volumeMounts:
    - name: config-volume
      mountPath: /app/local_settings.py
      subPath: local_settings.py
  volumes:
  - name: config-volume
    configMap:
      name: lyanna-cfg

这样就可以了。

后记

很曲折,不过终于都解决了。重新kubectl apply让全部修改生效,就完成了。大家现在可以访问https://blog.dongwm.com/体验 Kubernetes 版的 lyanna 博客效果啦~

Kubernetes 版本的问题

现在博客主页面还是原来的https://www.dongwm.com/,是因为我觉得切换的时机还不到,因为在整个过程我发现了很多问题:

  • 访问速度慢。我对博客页面打开速度的要求是 & lt;100ms,期望超过一半的请求能 & lt;50ms。但是 k8s 版本博客我实测 300-700ms,偶尔请求会超过 2s!这是无法接受的。这其中原因很多,如 2 层 Nginx 架构、Memcached、lyanna app (Gunicorn) 等进程不直接在本机运行而是在容器 (需要网络请求)、服务器配置低影响 k8s 性能
  • 占用服务器资源。现在博客的负载基本在 4+(原来基本 & lt;0.3)、CPU>60%(原来 & lt;20%)、MEM>96%(原来 & lt;40%),可以感受到差别是巨大的。虽然之前在配置文件中对相关应用的 Pod 的资源占用都做了限制,但是对于 k8s 自己和 Nginx-ingress 的 Pod 都没有做限制,如果那个环节资源请求提上来就会直接把内存占满,现在这个 96% 可以侧面说明情况确实如此
  • kubectl apply 卡。在本机 macOS 里面其实也会 k8s 做了资源限制 (可以看 Docker Desktop 的 Advanced Tab), 我这里是默认的 2CPU+2MEM,可见比服务器的还少,从来不会 kubectl apply -f XX 会卡住 5-10 分钟,哪怕 cd lyanna 这样的命令也会卡很久,这段时间服务器负载可以达到 50+!虽然不做和 lyanna 有关的不怎么卡,但这也太吓人了。不知道这是服务器资源太低还是因为不是物理机器 (前面说了,我的服务器其实是也是个虚拟机)

我觉得大部分开发者的博客服务器配置都不会高于我这个吧?所以个人博客使用 k8s 目前看不太合适。当然如果未来我能解决这一系列问题,就另当别论了。

延伸阅读

  1. https://kubernetes.io/zh/
  2. https://juejin.im/entry/599d33ad6fb9a0247804d430
  3. https://github.com/nginxinc/kubernetes-ingress/blob/master/docs/nginx-ingress-controllers.md
  4. https://github.com/kubernetes/ingress-nginx/issues/739
  5. https://kubernetes.io/zh/docs/concepts/configuration/manage-compute-resources-container/
  6. https://kubernetes.io/zh/docs/concepts/overview/working-with-objects/annotations/
  7. https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/
  8. https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/
  9. https://docs.cert-manager.io/