之前自己做了一个小项目,用于记单词。因为目前的背单词app主要还是记拼写,而我比较欠缺的是记读音,很多单词看见认识,但是听到读音的不知道是哪个单词。所以想着做个程序来记读音,可以先听读音再选是否记住。
大概背景就是这样。做这个主要也是为了体验一下完整前端后端开发,以及部署的整个流程。毕竟工作中主要是后端,而且其实只做后端的一部分。做个小项目可以体验一下全流程。这次我用了fastapi作为后端,了解一下这个很火的新框架,前端暂时是vue,不过前端部分不会的太多,目标先是能用。
开发的事暂时按下不表,先来说说是如何部署的。在以前我玩wordpress时,还都是用个lnmp的脚本,然后把php程序上传到服务器,然后启动。之后在人人车,也是差不多,用git拉下来更新,重启服务,用supervisor自动重启挂掉的服务。而到了头条,一下子就先进了,有TCE系统,点一下就发布新镜像部署了。现在shopee的话,介于两者之间,也算是有自动化的部署流程,只不过系统还没整合到一起。对我自己来说,还真没有实践过容器部署和运维,所以也借这个项目实践一下。
我只买了一台丐中丐级别的vps,buyvm的512mb。一年只要20刀。不过没有新加坡节点,延时大一些。k8s/k3s暂时先不体验了,不想一步迈的太大,配置也不够,咋看起来复杂度也很高。
我主要参考的是这个文章,用的docker swarm加上portainer作为ui来管理,只不过我用了图形化nginx管理工具。这个项目比较简单,数据库用的postgresql(也是我不熟悉的,pgadmin不好用啊🤣),会有一个contianer,然后python后端起一个容器。前端是一个nginx的容器。我用到了nginx proxy manager来管理http/s,这个工具可以设置自动更新ssl证书,另外有ui界面,将subdomain分给portainer,前端,后端。这部分可以用traefik做,不过我还没有研究怎么写配置。
NPM配置
version: "3"
networks:
nginx-net:
external: true
volumes:
nginx-data:
external:
name: nginx-data
letsencrypt:
external:
name: letsencrypt
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
ports:
# HTTP port
- '80:80'
# HTTPS Port:
- '443:443'
# Admin UI
# - '81:81'
environment:
DB_SQLITE_FILE: "/data/npm.sqlite"
volumes:
- nginx-data:/data
- letsencrypt:/etc/letsencrypt
networks:
- nginx-net
npm没有使用官方的mysql配置,用了sqlite数据库,另外设置了两个volumes,用于存配置和证书。在https转发没配置好之前,81端口是对外开发的,在配置好后,注释掉,只保留80和443
有了这些,只是能让服务跑起来,还没有达到学习目的。为了自动化部署,还有些额外的工作。首先是镜像仓库,鉴于docker/github的私有镜像库免费额度不太够,所以我和那个教程介绍的一样,部署了自己的registry。和教程不一样的,我用了github action,用github action来触发工作流,当在github里发布之后,会触发工作流打包镜像,然后推到自己的registry,然后触发portainer的webhook,更新docker service。这里注意我在生成registry的密码时,用的参考文章的命令有问题,又查了下docker的文档。
docker run --entrypoint htpasswd httpd:2 -Bbn testuser testpassword > auth/htpasswd
github actions
name: Build and Publish Docker image
on:
release:
types: [published]
jobs:
push_to_registries:
name: Build and Push Docker image to multiple registries
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- id: get_version
uses: battila7/get-version-action@v2
- name: Check out the repo
uses: actions/checkout@v2
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
registry: ${{ secrets.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build BE image
uses: docker/build-push-action@v2
with:
push: true
context: server
tags: |
${{ secrets.DOCKER_REGISTRY }}/xxx-be:latest
${{ secrets.DOCKER_REGISTRY }}/xxx-be:${{ steps.get_version.outputs.version-without-v }}
- name: Invoke BE deploy webhook
uses: joelwmale/webhook-action@master
with:
url: ${{ secrets.BE_WEBHOOK_URL }}
- name: Build FE image
uses: docker/build-push-action@v2
with:
push: true
context: web_fe
build-args: VUE_APP_BASE_URL=${{ secrets.VUE_APP_BASE_URL }}
tags: |
${{ secrets.DOCKER_REGISTRY }}/xxx-fe:latest
${{ secrets.DOCKER_REGISTRY }}/xxx-fe:${{ steps.get_version.outputs.version-without-v }}
- name: Invoke FE deploy webhook
uses: joelwmale/webhook-action@master
with:
url: ${{ secrets.FE_WEBHOOK_URL }}
github actions这里是前端后端同时部署,可以分开两个action。我用到了取release version的,这样在push到registry时可以附带上version,我们就可以回滚服务了。我这里触发条件是release,所以在merge到main(是的,现在默认变成main分支了)时并不触发部署,只有在release页面打tag后才会。
有几个坑 - 为了安全,会把portainer的web端口不暴露在外,由nginx转发,所以在调整nginx容器时一旦挂了,portainer ui也会挂掉。所以一般可以先把9001 ui端口对外打开,设置好nginx之后,再更新portainer的设置。 - 同理,nginx proxy manager 也有个81端口,在配置好https转发之后,应该更新docker配置,不要让81端口对外。 - 如果有多台机器组网,对于portainer和db,应该使用配置固定到一台实体机上docker-swarm deploy placement constraints,因为volume只在一台上,实际上有容器部署db也不算是好的实践啦。 - 镜像如果有版本tag,registry会越来越多,如果空间小的话,要注意清理,前端的镜像应该比较小,我的python的镜像好像是150多mb。对于总共10g的vps来说,还挺占地方的。 - 在portainer新建network时,主要加上attachable,不然其他容器后期无法加入网络。