最近因为工作需要使用Python来开发和维护之前同事的代码
记录一下快速搭建python环境的过程

安装 Miniconda

MiniCondaconda的免费最小安装版,里面只有少量软件例如pip,zlib

要求window10或以上, 目前MiniConda的最新版自带Python版本是3.12

我们可以使用conda来管理我们的依赖,并通过切换环境来使用不同版本的Python

从页面点击下载, 下载页面为Minicondda download page

或者命令行 (window)

curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe -o miniconda.exe
start /wait "" miniconda.exe /S
del miniconda.exe

默认安装即可

安装完成后打开Anaconda Prompt

输入conda list 测试安装环境是否成功

image-20240508184630590

更新镜像源(可选)

执行一下命令使用清华源

conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --set show_channel_urls yes

执行完成后查看

conda config  --show

image-20240509183446397

切换环境

默认的初始环境是base

image-20240509181808047

创建环境

我们通过如下命令来创建一个环境

conda create --name env1 python=3.10

创建了一个名称叫env1的python3.10版本的环境
创建的时候会创建一个环境目录(在安装目录的env文件夹下)
并下载一些依赖,例如pip, zlib

image-20240509182306044

下载的依赖

image-20240509182515021

切换环境

通过命令

conda activate env1

就可以切换到我们刚刚创建的env1环境

image-20240509182729365

可以看到python版本是3.10

停用环境

通过如下来停用环境

conda deactivate

安装依赖

我们也可以使用pip来安装依赖
但是推荐使用conda来安装

搜索依赖

conda search [-h] [--envs] [-i] [--subdir SUBDIR] [--skip-flexible-search] [-c CHANNEL] [--use-local]
[--override-channels] [--repodata-fn REPODATA_FNS] [--experimental {jlap,lock}]
[--no-lock] [--repodata-use-zst | --no-repodata-use-zst] [-C] [-k] [--offline] [--json]
[-v] [-q]

支持很多参数, 并且支持MatchSpec查询方式(conda查询语言)

普通查询

conda search numpy

![image-20240508190855795](images/python-quick-start/image-20240508190855795.png)

查询某个版本范围

conda search "numpy>=1.26"

模糊搜索

conda search "*mysql*"

支持*模糊匹配

查看某个版本的具体信息

conda search pandas[build=py39h5da7b33_0]  -i

image-20240508192428631

安装依赖

conda install scipy

下载scipy到当前环境

可以通过-n--name来指定环境

conda install -n env1 scipy
conda install --name env1 scipy

env1是环境名称

指定版本

conda install numpy=1.21.5 -y

-y 是跳过询问,直接下载

查看依赖

conda list

可以查看环境的依赖包有哪些

image-20240510115549385

安装 Pycharm

进过上面环境的安装之后

我们已经可以编写python代码啦

image-20240510170958572

工欲善其事,必先利其器

世界上有家做IDE很牛逼的公司叫JetBrains

使用PyCharm来开发,下载地址

使用免费的社区版就足矣了

image-20240521110030043

配置环境

为了使不同项目的依赖不冲突,我们可以新建一个项目,并创建一个conda环境

进入到配置页面

image-20241105164423729

依次点击 Project -> Python Interpreter -> Add Interpreter -> Add Local Interpreter

然后新增一个conda环境,如下

image-20241106095300670

接着我们就可以在这个环境开发了

开发一个最小WEB程序

创建一个新环境, web-hello-world

使用python版本为3.11

按照flasky依赖

pip install Flash

image-20241105174900874

安装完成

编写一个Flask程序

文件hello.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"

启动项目

flask --app hello run

image-20241105175206188

访问127.0.0.1:5000

image-20241105175226110

运行成功

开发一个爬虫项目

我们根据上面的步骤创建一个新的环境

创建一个新的项目 spider-demo

image-20241105180324772

安装依赖

pip install BeautifulSoup4 requests

编写一个爬虫抓取古诗

from bs4 import BeautifulSoup
import requests

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9999.999 Safari/537.36'
}
url = 'https://www.gushicimingju.com/gushi/shi/7.html'
response = requests.get(url, headers=headers)

soup = BeautifulSoup(response.text, 'html.parser')

contentList = soup.find(class_="gushi-info").find(class_="shici-content").select('p')

for item in contentList:
print(item.getText())

运行如下

image-20241105183428741

总结

结合Pycharm来使用可以大大提高开发的效率

针对不同的环境也自由切换成本也很低

依赖库的版本冲突问题也可以解决

参考

  • miniconda
  • switch-environment
  • pycharm configure conda environment

为了快速体验一下K8S,使用 minikube 在 linux 云服务器上搭建 K8S 集群

minikube 是一个本地化单节点的K8S,可以让我们更方便学习和开发 K8S, 我们这里采用Docker 作为它的容器环境

安装 minikube

需要先安装好docker环境,参考docker环境安装

使用一下命令安装minikube

curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/

完成之后

# 查看版本
minikube version

# 输出如下说明成功
# minikube version: v1.33.0
# commit: 86fc9d54fca63f295d8737c8eacdbb7987e89c67

启动 minikube

启动命令

minikube start \
--registry-mirror=https://registry.docker-cn.com \
--image-mirror-country=cn \
--image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers \
--kubernetes-version=v1.23.3 \
--force \
--memory=1690mb \
--driver=docker

一些参数说明
--registry-mirror使用国内镜像仓库
--image-mirror-country 使用镜像的国家代码,国内使用 cn
--image-repository 使用国内的镜像仓库
--kubernetes-version 使用 K8S 的版本, 默认使用stable版本(目前是v1.30.0)
--force 强制让minikube允许一些危险的操作(避免各种问题起不起来, 例如使用root)
--driver 设置驱动为docker
--memory 表示内存的大小,如果服务器内存比较大,可以给多一点

启动之后查看状态

minikube status

输出如下, 说明启动完成

minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

使用 kubectl 工具

K8S集群一般都是使用CLI工具来操作的,也就是 kubectl

可以让我们更加熟悉K8S的一些命令

别名方式(推荐)

minikube 已经自带了kubectl工具
我们可以通过

alias kubectl="minikube kubectl --"

来获得使用原生工具一样的体验

tips:只对当前会话有效,可以通过写到~/.bashrc让所有会话都生效

echo 'alias kubectl="minikube kubectl --"' >> ~/.bashrc
source ~/.bashrc

下载方式

通过以下命令安装

curl -LO "https://storage.googleapis.com/kubernetes-release/release/v1.23.3/bin/linux/amd64/kubectl" && chmod +x kubectl && sudo mv kubectl /usr/local/bin/

这里的v1.23.3版本与上面的kubernetes-version对应

验证

kubectl version --output=yaml

输出如下

image-20240503123552234

控制台 Dashboard

minikube集成 K8S 的控制台 Dashboard

启动

minikube dashboard

通过以上命令启动一个dashboard来管理minikube集群

会得到一个类型的url

http://127.0.0.1:40417/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/

只能在宿主机上访问,想要在外网访问,需要开启代理

kubectl proxy --port=8080 --address='172.22.223.75' --accept-hosts='^.*' &

--port是代理的端口
--address是内网ip

如下,可以看到有一个minikube的节点

image-20240503151530873

官网例子项目

使用官网的来测试一下

kubectl create deployment hello-minikube --image=kicbase/echo-server:1.0
kubectl expose deployment hello-minikube --type=NodePort --port=8080

上面创建了一个deployment并将8080端口暴露出来

但是此时还是无妨访问

kubectl port-forward --address 0.0.0.0 service/hello-minikube 31302:8080

我们通过端口转发,将service的端口暴露到本地,这样就可以在公网访问到

image-20240503135148043

在dashboard看到有一个 NodePort类型的服务hello-minikube

image-20240503151243111

部署Node后端项目

这里通过部署一个本地项目来演示一下

我们快速编写一个node后端项目

第一步安装环境

sudo apt update
sudo apt install nodejs npm

第二步初始化项目

## 初始化项目,使用默认值即可
npm init
## 配置镜像加速
npm config set registry https://registry.npmmirror.com
## 安装 express
npm install express

然后编写app.js

const express = require('express')
const app = express()

app.all('*', (req, res) => {
res.json({
method: req.method,
protocol: req.protocol,
query: req.query,
})
})

app.listen(3000, () => {
console.log(`Example app listening on port 3000`)
})

编写package.json

{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node app.js"
},
"author": "",
"dependencies": {
"express": "^4.19.2"
}
}

编写Dockerfile

FROM node:20.11.1-alpine3.19
## copy package.json app.js
COPY . /app
WORKDIR /app
RUN npm config set registry https://registry.npmmirror.com
RUN npm install
CMD npm start

测试

构建镜像

docker build -t test/simple-backend:v1.0 .

启动项目

docker run -d -p 30000:3000 --name backend test/simple-backend:v1.0

使用url请求(注意云服务开放3000端口)

http://localhost:30000/?a=1

响应如下

image-20240503160916921

K8S集群部署

想要部署本地的项目到minikube上,需要执行如下命令

eval $(minikube docker-env)

执行之后,当前会话的的docker命令当前终端会话指定minikube内部的docker

然后再重新执行build (十分重要

docker build -t test/simple-backend:v1.0 .

编写 deployment.yaml

apiVersion: apps/v1 # 指定Kubernetes API的版本,apps/v1是常用于Deployment的版本
kind: Deployment # 指定资源类型为Deployment,用于部署应用
metadata:
name: backend-deployment # Deployment的名称
labels:
app: backend # 为Deployment添加标签,用于标识和选择性地过滤资源
spec: # Deployment的规格说明
replicas: 1 # 指定副本数量为1,即运行一个Pod的实例
selector: # 选择器,用于确定哪些Pod应该由这个Deployment管理
matchLabels:
app: backend # 选择标签为app=backend的Pod
template: # Pod模板,定义Pod的标签和容器规格
metadata:
labels:
app: backend # Pod的标签,与上面的selector匹配,确保Deployment可以管理这个Pod
spec: # 定义Pod中容器的规格
containers: # Pod中容器的定义列表
- name: backend # 容器的名称
image: test/simple-backend:v1.0 # 容器使用的镜像
ports: # 容器暴露的端口列表
- containerPort: 3000 # 容器内部监听的端口号

这里的image就是上面发布

编写

apiVersion: v1 # 指定Kubernetes API的版本,v1是Service的标准版本
kind: Service # 指定资源类型为Service,用于暴露应用
metadata:
name: backend-service # Service的名称
spec: # Service的规格说明
selector: # 选择器,用于确定哪些Pod应该由这个Service管理
app: backend # 选择标签为app=backend的Pod
type: NodePort # Service的类型为NodePort,使得可以通过集群的节点IP和静态端口访问Service
ports: # 定义Service暴露的端口和目标Pod的端口的映射
- protocol: TCP # 使用的协议,这里是TCP
port: 80 # Service对外暴露的端口
targetPort: 3000 # Pod内部的目标端口,Service将流量转发到这个端口
nodePort: 30080 # 集群外部访问Service的端口,通过<节点IP>:<nodePort>的形式访问

部署deployment

kubectl apply -f deployment.yaml

查看deployment和pod的状态(如下所示

image-20240503190542614

部署service

kubectl apply -f service.yaml

查看service

image-20240503190457779

增加端口转发

kubectl port-forward --address 0.0.0.0 service/backend-service 38080:80

到浏览器验证一下

image-20240503190819562

说明项目部署成功

由于minikube不能直接讲k8s集群的端口绑定到宿主机,所以需要通过端口转发的方式

模拟单节点的k8s,还是推荐使用kubeadm

参考

  • minikube-start
  • kubectl工具
  • Minikube 部署安装
  • push image to minikube

在日常的开发中,经过需要使用Linux的命令行来完成一些工作
但是每次都需要去搜索引擎或GPT搜索

所以这里主要是记录一些工作使用频率较高的命令或工具的用法

命令篇

alias 别名

别名可以让我们更便捷使用一些常用的命令,提高使用效率

用法

alias [-p] [name[=value] ... ]

定义或展示系统别名,-p 参数可以打印可重用的 alias

例如

alias ll="ls -alh"

使用ll可以以列表方式展示当前目录下的文件和目录,并以友好格式展示文件大小

这里一些方便好用的alias

展示服务器所有ip

alias ips="ifconfig -a | grep inet"

匹配文本着色

alias grep='grep --color'
alias egrep='grep -E --color=auto
alias fgrep='grep -F --color=auto

启动一个http服务

alias http="python3 -m http.server 80"

node版本

# 安装http-server
sudo npm install -g http-server

alias http="http-server -p 80"

使用

image-20240324181048329

cp / scp 复制 / 远程复制

本地复制 cp

用法

Usage: cp [OPTION]... [-T] SOURCE DEST
or: cp [OPTION]... SOURCE... DIRECTORY
or: cp [OPTION]... -t DIRECTORY SOURCE...
Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.

一些参数

参数 说明
-a 尽可能保留副本的属性和结构,递归复制,相当于 -dR --preserve=all
-d 复制软链接为软链接,并保留硬链接,箱单于 -P --preserve=links
-debug 输出复制过程的一些信息
-i, --interactive 覆盖文件前询问(输入Y覆盖)
-l, --link 硬链接文件,不复制内容
-n, --no-clobber 不覆盖一个存在的文件 ( 会被-i参数覆盖 )
-v, --verbose 显示复制过程
-P, --no-dereference 复制软链接(符号链接)为软链接
--preserve=[ATTR] 配置保留执行的属性,例如mode, timerstamps 或直接 all
-p 保留权限、所有者、时间戳等信息, 相当于--preserve=mode,ownership,timestamps
-R, -r, --recursive 递归复制目录,复制目录时使用

例如

## 递归复制source_dir目录到dest_dir
cp -R source_dir dest_dir

## 创建一个硬链接
cp -l dir/a.txt a1.txt

## 复制source_dir 比保留权限信息
cp --preserve=mode -R source_dir dest_dir

远程复制 scp

cp 只能复制在同一台主机上的文件或目录,需要复制远程主机的文件或目录,需要使用scp

scp 叫做OpenSSH secure file copy, 通过ssh来传输数据

命令

scp [-346ABCOpqRrsTv] [-c cipher] [-D sftp_server_path] [-F ssh_config] [-i identity_file] [-J destination] [-l limit] [-o ssh_option] [-P port]
[-S program] source ... target

参数

参数 说明
-C 开启压缩
-l 限制传输带宽,单位 Kb/s
-P 指定链接的端口,否认为22 (ssh的默认端口)
-p 保留源文件的修改时间,访问时间、权限位
-s 使用sftp协议
-r 递归复制目录
-v 详细模式,打印复制过程的内容
## 递归复制server1主机上/home/reader/source_dir到当前目录 (使用reader用户)
scp -r reader@server1:/home/reader/source_dir .


## 通过22222端口链接主机并复制文件目录
scp -r -P '22222' root@server1:/root/dir .


## 打印复制过程
scp -v root@server1:/root/dir/file .

grep 文本匹配

命令

grep [OPTION...] PATTERNS [FILE...]

参数

参数 描述
-V 输出grep版本并退出
-F 固定字符串匹配模式
-G 基础正则表达式模式,默认就是这种模式
-i, --ignore-case 忽略大小写
-v, --invert-match 反向匹配,选择不匹配的行
-c, --count 不输出匹配结果,只输出匹配的数量
-l, --files-with-matches 不输出匹配结果,只输出匹配上内容的文件名
-L, --files-without-match 不输出匹配结果,只输出未匹配上内容的文件名
-H, --with-filename 每行匹配的内容增加文件名, 当有多个文件时,默认输出
-h, --no-filename 不是输出文件名, 当只有一个文件时
-n, --line-number 每行输出匹配的内容增加行数
-A NUM, --after-context=NUM 打印匹配行后NUM行的内容
-B NUM, --before-context=NUM 打印匹配行前NUM行的内容
-C NUM, -NUM, --context=NUM 打印匹配行前后NUM行的内容
-d ACTION, --directories=ACTION 匹配目录,ACTION有read 像文件一样读,skip 跳过文件夹,recurse 递归读取,相当于-r
-r, --recursive 递归匹配文件夹下的所有文件

例如

## 管道匹配,输出hello字符串的个数
echo "hello world" | grep -n "hello"

## 递归匹配_posts目录下所有文件,忽略大小写
grep -ri "controller" _posts

## 搜索"10:32"这一分钟内pool-12-thread-1线程的所有日志,匹配"biz func"并输出后10行的日志
grep "2024-04-21 10:32.*?pool-12-thread-1" info.log | grep "biz func" -A 10

## 计算info.log文件中出现 xxx的次数,只输出一个数值
find -c "xxx" info.log

head / tail / less / more文件操作

文本文件查看

输出文件的头部内容

例如:

输出info.log文件的前10行内容

head -n 10 info.log

tail

输出文件的尾部内容

例如

输出info.log文件的后10行内容

tail -n 10 info.log

监听info.log后续添加的内容

## 一开始打印后10行数内容,然后不断输出新增的内容到控制台
tail -f -n 10 info.log

find / locate 文件查找

find

从文件目录中查询文件

表达式

find [-H] [-L] [-P] [-D debugopts] [-Olevel] [starting-point...] [expression]

一些参数

-name 名称匹配,支持*通配符

过滤类型

-type 
b 缓存块
c 字符串
d 目录
p 命名管道(FIFO)
f 普通文件
l 软链接
s 套接字socket

大小过滤

-size [cwbkMG]
b 512字节块
w 2字节
k kb
M 兆字节Mb
G Gb
使用 +(加号) -(减号) 大于或小于

例如
-size +10M 表示过滤大大于10M的文件

动作

-fls [file]  将内容输出到文件
-fprint file 将内容输出到文件
-ls 以`ls -dils`输出到控制台

例如

## 搜索 /tmp目录下名字core的普通文件, 并删除
find /tmp -name core -type f -print | xargs /bin/rm -f

## 搜索权限位是664的文件
find . -perm -664

## 模糊匹配,列出当前文件及`.c`后缀的文件
find . -name "*.c"

文件操作

文件分割 split

语法

split [OPTION]... [FILE [PREFIX]]

参数

参数 描述
-a, --suffix-length=N 生成后缀的默认长度,默认为2
-b, --bytes=SIZE 切割每个小文件多少字节
-d 使用数字后缀,从0开始
-l, --lines=NUMBER 切割每个小文件多少行
--verbose 生成小文件前打印一行输出

例子

## 将 out.log 切割成每个100行的小文件,放在out目录下,前缀是out_ 
## 后缀是数字
split -l 100 -d out.log out/out_

终端连接 ssh

免密登录 ssh-copy-id

通过ssh-copy-id验证远程服务器的账号密码,并将本地key复制到服务器的authorized_keys来实现免密登录

可以使用ssh-keygen来生成key

语法

ssh-copy-id [-f] [-n] [-i [identity_file]] [-p port] [-o ssh_option] [user@]hostname

参数

参数 描述
-i identity_file 指定本地公钥的位置
-p port 指定ssh的端口号
-o ssh_option 使用ssh的参数

例如

## 复制密钥到远程服务器vm1的root账号
ssh-copy-id root@vm1

## 复制密钥 `id_rsa.pub`(默认就是这个) 到远程服务器vm1的root账号
ssh-copy-id -i ~/.ssh/id_rsa.pub root@vm1

然后就可以通过ssh root@vm1直接登录

ssh

登录远程客户端

参数

参数 描述
-b bind_address 使用本地地址bing_address连接
-E log_file 输出debug日志到log_file文件
-i identity_file 指定本地私钥文件identity_file,默认是~/.ssh/id_rsa
-p port 指定远程服务的端口,默认是22
-q 安静模式,不输出debug日志和诊断日志
-T 禁用伪终端分配,一般用与测试连接,不需要执行命令
-v 详细模式,打印调试日志

例子

## 测试能否连接github.com,并输出调试日志
ssh -vT github.com

## 通过10022端口连接到vm1服务器上
ssh -P '10022' root@vm1

## 远程执行 `df -h` 命令
ssh root@vm1 "df -h"

工具篇

软件篇

docker

现代开发项目无论是前端还是后端都离不开 CI (持续构建)工具,不仅可以解放生产,提高生产力,也非常方便管理,同类型的项目只需要编写构建规则,后续都可以复用

相信很多人使用的第一款 CI 工具应该都是 Jenkins ,Jenkins 非常强大,提供了用户权限和很多插件,可以非常灵活满足企业的需求

但是针对个人开发者或者小团队,可能并不需要那么复杂的功能,那么 Drone 可能是个更好的选择,无论是占用资源还是使用方式都非常的轻便,能够很容易上手

我们通过演示如何自动构建一个前端 vue 项目和后端 SpringBoot 项目来学习如何使用 Drone

安装Drone

首先需要准备 Docker 环境

可参考

准备好Docker后,我们需要安装 Drone Server
Drone Server 是一个独立的守护进程,它轮询服务器以获取待执行的 Pipeline

Drone 脚本一般需要与某个代码仓库平台集成并完成OAuth配置,之后才能够拉取项目代码

与Github集成

这里我们与Github集成

需要在github-setting 新增一个 OAuth App

image-20240324221648745

这里的192.168.64.2是本地虚拟机的 ip

如果有云服务器,可以替换成云服务器的公网 ip 地址

注意回调url的路径是/login

image-20240324212126843

这个Client IDClient secret需要在下面使用 (密钥需要手动点击生成)

生成一个密钥来验证Drone ServerRunner之间的 RPC 通信

> openssl rand -hex 16
19e111caff5871dceba3440a17cba196

启动Drone Server

使用如下的命令启动

docker run \
--volume=/var/lib/drone:/data \
--env=DRONE_GITHUB_CLIENT_ID=23d9347147e27b327c7c \
--env=DRONE_GITHUB_CLIENT_SECRET=b04520f2d429cfc641264c0f96198834e7fb8afc \
--env=DRONE_RPC_SECRET=19e111caff5871dceba3440a17cba196 \
--env=DRONE_SERVER_HOST=192.168.64.2 \
--env=DRONE_SERVER_PROTO=http \
--env=DRONE_USER_CREATE=username:qaqRose,admin:true \
--publish=80:80 \
--publish=443:443 \
--restart=always \
--detach=true \
--name=drone \
drone/drone:2

挂载/var/lib/drone:/data, 这样容器重启之后数据不会丢失

环境参数说明

DRONE_GITHUB_CLIENT_ID是github oauth app的Client ID

DRONE_GITHUB_CLIENT_SECRET是github oauth app的Client secret

DRONE_RPC_SECRET 是 RPC通信的密钥,前面手动生成

DRONE_SERVER_HOST 是 drone服务的域名

DRONE_USER_CREATE可以指定github账号为admin,admin账号拥有最高的权限

启动Runner

有多种方式可以启动Runner
这里通过docker方式启动

docker run --detach \
--volume=/var/run/docker.sock:/var/run/docker.sock \
--env=DRONE_RPC_PROTO=http \
--env=DRONE_RPC_HOST=192.168.64.2 \
--env=DRONE_RPC_SECRET=19e111caff5871dceba3440a17cba196 \
--env=DRONE_RUNNER_CAPACITY=2 \
--env=DRONE_RUNNER_NAME=my-first-runner \
--publish=3000:3000 \
--restart=always \
--name=runner \
drone/drone-runner-docker:1

解释参数的作用
这里挂载/var/run/docker.sock到 Runner容器的/var/run/docker.sock
让Runner可以操作外部的docker容器

DRONE_RPC_HOST 让Runner连接指定主机Server

登录Server后台

Drone Server和Runner都启动之后,查看

image-20240324222353884

通过http://192.168.64.2:80访问到Drone Server的后台

image-20240324222646815

点击 CONTINUE,跳转到Github授权登录

然后会跳转到注册页面,不用管,直接 SUBMIT 即可

image-20240326001456056

进入主页面

左下侧边栏可以配置新增用户,但是需要admin权限

image-20240326001323732

到这里 Drone 搭建好,我们开始尝试部署前后端项目

构建前端 Vue 项目

我们在 Github 新建一个 Vue 项目

初始化项目

首先新建一个测试项目

# 安装 vue-cli
sudo npm install -g @vue/cli

# 新建一个vue项目
vue create drone-test-vue

编写 drone 流水线

在项目主目录下编写.drone.yml

kind: pipeline # 定义对象类型,还有secret和signature两种类型
type: docker # 定义流水线类型,还有kubernetes、exec、ssh等类型
name: drone-test-vue

## 平台,系统与预设不一致的话会在build会一直pending
## 预设是 linux / amd64
platform:
os: linux
arch: arm64

## 克隆代码时,depth=1只获取最后一次提交,速度更快
clone:
depth: 1

steps:
- name: build # 流水线名称
image: node:lts-alpine3.19
commands:
- npm config set registry https://registry.npmmirror.com
- npm install
- npm run build

- name: build-docker
image: plugins/docker
volumes: # 将容器内目录挂载到宿主机,仓库需要开启Trusted设置
- name: docker
path: /var/run/docker.sock # 挂载宿主机的docker
settings:
context: /drone/src
commands: # 定义在Docker容器中执行的shell命令
# 后续执行需要开启 stop 跟 remove
#- echo stop container
#- docker stop `docker ps -a | grep drone-test-vue | awk '{print $1}'`
#- echo remove image
#- docker rm -f `docker ps -a | grep drone-test-vue | awk '{print $1}'`
- echo build new images
- docker build -t drone-test-vue .
- echo run container
- docker run -p 8080:80 --name drone-test-vue -d drone-test-vue

# 定义流水线挂载目录
volumes:
- name: docker
host:
path: /var/run/docker.sock

steps 将流水线执行过程分成多个步骤

默认第一个步骤是 clone, 会从代码仓库中获取代码, 配置depth=1可以提高拉取速度

第二步是自定义的build步骤,主要是配置了npm国内源,执行了npm命令打包前端项目

第三步也是自定义步骤build-docker, 需要挂载宿主机的docker进程文件,这样可以在容器内启动一个宿主机的docker容器,主要是容器生命周期的流程

Dockerfile

前端项目的docker构建脚本如下

FROM nginx:1.25.2

COPY ./dist /usr/share/nginx/html/dist
COPY ./nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

nginx 配置

nginx的配置文件如下

server {
listen 80;
root /usr/share/nginx/html/dist;

location ~ .*\.(js|css)?$ {
expires 7d;
access_log off;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico)$ {
expires 30d;
access_log off;
}
location / {
root /usr/share/nginx/html/dist;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;

location = /50x.html {
root /usr/share/nginx/html;
}
}

构建流程

提交代码,到 Drone 执行构建

image-20240407112821596

激活仓库

image-20240407112859345

由于我们挂载宿主机的docker,所以需要激活特权容器

image-20240407113203076

然后点击构建,等待容器构建

image-20240404205332971

构建页面

image-20240407113435820

等待构建完成,第一次一般久一点

image-20240407124127127

构建成功,访问页面,可以看到

image-20240404205231508

构建后端 SpringBoot 项目

新建一个SpringBoot项目

新建项目

start.spring.io新建一个springboot项目,然后导入到本地

增加一个简单的接口,方便测试

@RestController
@SpringBootApplication
public class SpringbootDroneTestApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootDroneTestApplication.class, args);
}
@GetMapping
public String hello() {
return "hello drone";
}
}

编写 dockerfile

增加Dockerfile

FROM arm64v8/openjdk:17-ea-16-jdk

WORKDIR /app

COPY ./target/springboot-drone-test-0.0.1-SNAPSHOT.jar /app/app.jar

ENTRYPOINT ["java","-jar","app.jar"]

这里的jdk17版本适用的是arm64的架构

编写 drone 流水线

增加.drone.yml

kind: pipeline # 定义对象类型,还有secret和signature两种类型
type: docker # 定义流水线类型,还有kubernetes、exec、ssh等类型
name: drone-test-springboot

## 执行系统平台,不然在build会一直pending
## 预设是 linux / amd64
platform:
os: linux
arch: arm64

## 自定义clone流程,这里手动关闭掉
clone:
disable: true
#depth: 1

steps:
- name: my-clone
image: drone/git
commands:
## 使用http1 (不知道为什么一直报错)
- git config --global http.version HTTP/1.1
- git clone https://github.com/qaqRose/drone-test-springboot.git --depth=1 .

- name: build # 流水线名称
image: maven:3.8.5-openjdk-17
volumes:
- name: maven-repository
path: /root/.m2 # 将maven下载依赖的目录挂载出来,复用jar和修改配置
commands:
- mvn clean package -DskipTests=true

- name: build-docker
image: plugins/docker
volumes: # 将容器内目录挂载到宿主机,仓库需要开启Trusted设置
- name: docker
path: /var/run/docker.sock # 挂载宿主机的docker
settings:
context: /drone/src
commands: # 定义在Docker容器中执行的shell命令
# 第一次执行注释 stop 跟 remove
# - echo stop container
# - docker stop `docker ps -a | grep drone-test-springboot | awk '{print $1}'`
# - echo remove image
# - docker rm -f `docker ps -a | grep drone-test-springboot | awk '{print $1}'`
- echo build new images
- docker build -t drone-test-springboot .
- echo run container
- docker run -p 8080:8080 --name drone-test-springboot -d drone-test-springboot

# 定义流水线挂载目录
volumes:
- name: docker
host:
path: /var/run/docker.sock
- name: maven-repository
host:
path: /data/.m2

相比前端项目的流水线,这里第一个步骤没有采用通用的clone
而是自定义了一个my-clone的克隆源代码的步骤
这样可以自定义一些clone的配置

然后就是编译、打包、管理docker生命周期,跟前端项目大同小异

maven 配置

使用maven中央仓库会比较慢,有些包也拉不到

我们可以配置一下阿里的maven镜像仓库

挂载仓库目录也可以提高后续的编译速度

/data/.m2 创建一个 repository文件夹 和 一个settings.xml文件

mkdir -p /data/.m2/repository
vim /data/.m2/settings.xml

maven仓库配置如下

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">

<localRepository>/.m2/repository</localRepository>
<mirrors>
<!-- 阿里云仓库 -->
<mirror>
<id>z_alimaven</id>
<mirrorOf>*</mirrorOf>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
</mirror>
</mirrors>
</settings>

构建流程

在drone 点击构建

image-20240407163923006

构建完成,测试一下是否可以访问接口

image-20240407164028818

可以正常访问

问题处理

访问github失败

这里我是使用 multipass 开的 Ubuntu 22.04 虚拟机
在虚拟机里面出现了无法访问 github.com 的情况

最后通过增加dns解析解决
执行命令

vim /etc/systemd/resolved.conf.d/dns_servers.conf

写入一下内容

[Resolve]
DNS=8.8.8.8 1.1.1.1

服务器行为不当

image-20240407112516197

同样也是访问github失败,在docker容器的 /etc/resolv.conf

nameserver 192.168.64.1
search .

# Based on host file: '/run/systemd/resolve/resolv.conf' (legacy)

192.168.64.1修改成8.8.8.8 即可

Clone仓库失败

克隆github仓库失败,出现Could not resolve host: github.com

image-20240407113938359

在Ubuntu系统中, 修改/etc/resolv.conf之后还是会复原,需要使用netplan工具来配置网络

例如vim /etc/netplan/50-cloud-init.yaml

network:
ethernets:
enp0s1:
dhcp4: true
match:
macaddress: 52:54:00:89:d6:83
set-name: enp0s1
nameservers:
addresses:
- "8.8.8.8"
version: 2

添加

nameservers:
addresses:
- "8.8.8.8"

然后执行来使DNS生效

netplan apply

在build的时候一直 pending

点击构建之后,一直在pendind
通过

docker logs <container_id>

也看不到什么错误日志

最后发现是platform的问题

调整一下即可

platform:
os: linux
arch: arm64

os应该都是linux
arch 可以通过uname -a 查看

image-20240325235508153

这里是aarch64arm64

如果是 amd64或者其他,需要重新配置

参考

  1. drone-sever-github
  2. drone-clone
  3. 如何排查网络故障 |多通道文档 (multipass.run)
  4. systemd-resolved - Arch Linux 中文维基 (archlinuxcn.org)
  5. 成功解决git clone遇到的error: RPC failed; curl 16 Error in the HTTP2 framing layer fatal
0%