跳到主要内容

L2TP/IKEv2-IPsec (VPN)搭建

· 阅读需 1 分钟

L2TP-IPsec (VPN)搭建

系统版本:Centos 7.6 AlibabaCloud

下载 docker 及 镜像

# 添加 docker yum 源
curl -o /etc/yum.repos.d/docker-ce.repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 安装 docker
yum install -y docker-ce-cli-19.03.9-3.el7 docker-ce-19.03.9-3.el7

# 下载镜像
docker pull hwdsl2/ipsec-vpn-server
# 上面的镜像源在 docker hub,如果很慢可以从 Quay.io 下载:
docker pull quay.io/hwdsl2/ipsec-vpn-server
docker image tag quay.io/hwdsl2/ipsec-vpn-server hwdsl2/ipsec-vpn-server

配置环境变量

vim vpn.env

VPN_IPSEC_PSK=your_ipsec_pre_shared_key
VPN_USER=your_vpn_username
VPN_PASSWORD=your_vpn_password

# 添加用户
VPN_ADDL_USERS=additional_username_1 additional_username_2
VPN_ADDL_PASSWORDS=additional_password_1 additional_password_2

启动 IPsec VPN服务器

docker run \
--name ipsec-vpn-server \
--env-file ./vpn.env \
--restart=always \
-v ikev2-vpn-data:/etc/ipsec.d \
-v /lib/modules:/lib/modules:ro \
-p 500:500/udp \
-p 4500:4500/udp \
-d --privileged \
hwdsl2/ipsec-vpn-server

检查 VPN 登录详细信息

docker logs ipsec-vpn-server

# 可以看到如下登录内容
Connect to your new VPN with these details:

Server IP: your_vpn_server_ip
IPsec PSK: your_ipsec_pre_shared_key
Username: your_vpn_username
Password: your_vpn_password

Linux 装x指南

· 阅读需 9 分钟

为什么要整理这种?

只有能讲出来和写出来的东西才是自己的,此文章为 Linux 日常操作指南。 快捷操作 -> 装x必备

你说你懂 Linux,其实大家都懂点,连测试和前端可能都经常打点命令去压测或者部署测试什么的,那你如何证明你更熟悉,来用熟练的快捷操作让旁边观摩的人暗暗惊叹吧~

命令行光标移动指令

光标快速切换到行首尾
ctrl a  行首
ctrl e 行尾
删除至行尾 | 清除当前光标位置之前的一个单词
ctrl k  可以将当前光标位置之后的所有部分快速清除
ctrl w 可以清除当前光标位置之前的一个单词
删除此条命令行
ctrl u  可以直接将整行命令直接清除
左|右 移动一个单词
esc b  左移一个单词
esc f 右移一个单词

vim 常用记录

光标漫游
j  向下
k 向上
h 向左
l 向右

shift + 上下左右 光标快速移动

gg 快速到文件头
G 快速到文件尾

shift 4($) 快速到行尾
shift 6(^) 快速到行首

i 插入模式
o 在下一行插入
复制 / 剪切
yy  复制一行
3yy 复制三行
yw 复制光标开始的第一个单词
y$ 复制光标到行尾

x 剪切光标所在字符
3x 剪切光标所在处往后的三个字符
删除 / 粘贴
dd  删除一行
3dd 删除三行
dw 删除一个单词
df" 删除到出现的第一个双引号

p 粘贴
ctrl + v 块模式
# 举个例子
1. 按下 gg 到行首
2. 按 `ctrl+v` 进入可视化模式,然后按 G 到行尾
3. 按 `I` 进入插入模式,输入 #
4. 按两次 ecs 回到普通模式,每一行都会生效
命令行模式
# 在普通模式下,输入 : 即可进入

%s/$/sth/ 在行尾追加sth
%s/\^M//g 替换掉dos换行符,\^M使用ctrl+v + Enter即可输入
g/^\s*$/d 删除空行以及只有空格的行
%s/#.*//g 删除#之后的字符
查找字符串
# 在普通模式下,按下/直接进入查找

n 查找下一个匹配
N 查找上一个匹配
2n 查找下面第二个匹配
宏录制

一般不咋用,用上了还挺酷的,算是 vim 特有操作

1. 按下 gg 到行首
2. 按下 qa 进行宏录制,a 是我们起的一个标记名称
3. 按 I 进入插入模式,输入 list.add("
4. 按 ESC 进入普通模式,然后按 $ 跳到行尾, 按 a 输入 ")
5. 按 j 进入下一行,然后按 ^ 回到行首
6. 再次按下 q 结束宏录制
7. 输入 @a 触发宏测试一下录制效果
8. 输入 3@a 重复宏3次,也就是影响下面的3行

aaa
bbb
ccc
⬇️
list.add("aaa")
list.add("bbb")
list.add("ccc")
其他操作
r    替换字符
ggVG 全选
u 恢复更改
:e /tmp/a 在同一个编辑器内打开/tmp/a文件。同一个编辑器的缓冲区是剪贴板是共享的,可以方便在多个文件中复制

wq wqa q! qa! 保存退出和不保存退出,a 即为 all 的意思

sed 常用记录

常用替换模式(更高级的我很少用)
sed '/^txt/s/a/b/g' file
# /^txt/ 选择以 ^txt 开头的行 ,s是substitute意思,a 是要匹配的字符,b 是要替换成为的字符,g 为flag

# 示例(匹配 test.txt 文件中 config 字符的行,以及后面的三行,将a替换为b)
sed '/config/, +3 s/a/b/g' test.txt
示范例子
# 统计文件中有每个单词出现了多少次
sed 's/ /\n/g' file | sort | uniq -c

# 查找目录中的py文件,删掉所有行级注释,并创建.bak 的备份文件
find ./ -name "*.py" | xargs sed -i.bak '/^[ ]*#/d'

awk 常用记录

常用文本筛选功能 (比如做一些日志处理和统计)
awk -F "," '/^a/' {print $1} file
# -F "," 参数
# '/^a/' 范围
# {print $1} 操作

# 举个例子1:采用awk统计netstat命令的一些网络状态
netstat -ant |
awk ' \
BEGIN{print "State","Count" } \
/^tcp/ \
{ rt[$6]++ } \
END{ for(i in rt){print i,rt[i]} }'
# 输出结果为:
State Count
LAST_ACK 1
LISTEN 64
CLOSE_WAIT 43
ESTABLISHED 719
SYN_SENT 5
TIME_WAIT 146

# 例子2,跟例子1的效果其实一样的:
netstat -ant | awk '{print $6}' | sort | uniq -c | sort -n -k 1 -r
示范例子
# 外网连接数,根据ip分组
netstat -ant | awk '/^tcp/{print $4}' | awk -F: '!/^:/{print $1}' | sort | uniq -c

# 输出 nginx 日志的 ip 和每个 ip 的 pv,pv 最高的前10
awk -F"|" '{print $3}' access.log | sort | uniq -c | sort -nk1 -r | head -n10

Linux 运维一定要懂得命令

CPU
  • 使用top查看cpu的load,使用 shift+p 按照 cpu 排序。

    需要了解 wa,us 等都是什么意思

  • 使用uptime查看系统启动时间和load

    什么算是系统过载 ?(服务所收到的请求量原大于它能处理的请求,load值需跟核心数做对比)

  • ps 命令勃大茎深,除了查进程号外,你还需要知道R、S、D、T、Z、<、N状态位的含义

  • topps 很多功能是相通的,比如 watch "ps -mo %cpu,%mem,pid,ppid,command ax" 相当于top的进程列表;

    top -n 1 -bcps -ef 的结果相似。

  • 有生就有死,可以用kill杀死进程。

    对java来说,需要关注 kill -9kill -15kill -3 的含义,kill的信号太多了,可以用kill -l查看,搞懂大多数信号大有裨益。

  • 如果暂时不想死,可以通过&符号在后台执行,比如tail -f a.log &

    jobs命令可以查看当前后台的列表,想恢复的话,使用fg回到幕前。

    这都是终端作业,当你把term关了你的后台命令也会跟着消失,所以想让你的程序继续执行的话,需要nohup命令,此命令需要牢记

top 中的 wa 是指当CPU空闲且磁盘IO阻塞的时间占比。注意这里只统计磁盘IO,不包含网络IO
top 中的 us 是指用户空间占用CPU百分比

kill -15 系统级别杀死,告知需要关闭,让其自行停止并退出,当然有些进程会拒绝结束
kill -9 内核级别杀死,需要立即退出,强制杀死
kill -3 可以打印进程各个线程的堆栈信息,默认保存为 /proc/${pid}/cwd/antBuilderOutput.log
#注:如果 -Xrs JVM选择没有被使用,kill -3命令会被忽略,可以使用 jstack -l [java pid] > jstack.out 来达到一样的效果
内存
  • free -m 命令,了解free、used、cached、swap各项的含义

  • cat /proc/meminfo 查看更详细的内存信息

    细心的朋友可能注意到,CPU和内存的信息,通过top等不同的命令显示的数值是一样的。

  • slabtop 用来显示内核缓存占用情况,比如遍历大量文件造成缓存目录项。

    曾在生产环境中遇到因执行find /造成dentry_cache耗尽服务器内存。

  • vmstat 命令是我最喜欢也最常用的命令之一,可以以最快的速度了解系统的运行状况。

    每个参数的意义都要搞懂。

  • swapon、swapoff 开启,关闭交换空间

  • sar 又一统计类轮子,一般用作采样工具

存储
  • 使用df -h查看系统磁盘使用概况
  • lsblk 列出块设备信息
  • du 查看目录或者文件大小
网络
  • rsync 强大的同步工具,可以增量哦

  • netstat 查看Linux中网络系统状态信息,各种

  • ss 它能够显示更多更详细的有关TCP和连接状态的信息,而且比netstat更快速更高效。

  • curl、wget 模拟请求工具、下载工具。

    如wget -r https://baidu.com 将下载整个站点

  • ab Apache服务器的性能测试工具

  • ifstat 统计网络接口流量状态

  • nslookup 查询域名DNS信息的工具,在内网根据ip查询域名是爽爆了

  • nc 网络工具中的瑞士军刀,不会用真是太可惜了

  • arp 可以显示和修改IP到MAC转换表

  • traceroute 显示数据包到主机间的路径,俗称几跳,跳的越少越快

  • tcpdump 不多说了,去下载 wireshark了

网络方面推荐安装体验一下kali Linux(有很多渗透工具,界面UI也很有安全【黑客】风格),上面的工具会让你 high 到极点。

上面资源的组合途径

/proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。只不过以文件系统的方式为访问系统内核数据的操作提供接口。系统的所有状态都逃不过它的火眼金睛。例如:

  • cat /proc/vmstat 看一下,是不是和vmstat命令的输出很像?
  • cat /proc/meminfo 是不是最全的内存信息
  • cat /proc/slabinfo 这不就是slabtop的信息么
  • cat /proc/devices 已经加载对设备们
  • cat /proc/loadavg load avg原来就躺在这里啊
  • cat /proc/stat 所有的CPU活动信息
  • ls /proc/$pid/fd 静静地躺着lsof的结果
以 java 应用为例:

![在这里插入图片描述](/img/Linux 装x实录/1.jpeg)

举个例子:

怎么查看某个Java进程里面占用CPU最高的一个线程具体信息?

  • 获取进程中占用CPU最高的线程,计为n。

    • 使用 top top -H -p pid,肉眼观察之
    • 使用 ps ps -mo spid,lwp,stime,time,%cpu -p pid
  • 将线程号转化成十六进制 printf 0x%x n

  • 使用 jstack 找到相应进程,打印线程后的100行信息 jstack -l pid| grep spid -A 100

最后附上经典一图 ![在这里插入图片描述](/img/Linux 装x实录/2.jpeg)

Jenkins Pipeline 构建

· 阅读需 6 分钟

Jenkins pipeline 构建

Jenkins Pipeline 有几个核心概念:

  • Node:节点,一个 Node 就是一个 Jenkins 节点,Master 或者 Agent,是执行 Step 的具体运行环境,比如我们之前动态运行的 Jenkins Slave 就是一个 Node 节点
  • Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作,比如:Build、Test、Deploy,Stage 是一个逻辑分组的概念,可以跨多个 Node
  • Step:步骤,Step 是最基本的操作单元,可以是打印一句话,也可以是构建一个 Docker 镜像,由各类 Jenkins 插件提供,比如命令:sh 'make',就相当于我们平时 shell 终端中执行 make 命令一样。

如何创建 Jenkins Pipline 呢?

  • Pipeline 脚本是由 Groovy 语言实现的,但是我们没必要单独去学习 Groovy,当然你会的话最好
  • Pipeline 支持两种语法:Declarative(声明式)和 Scripted Pipeline(脚本式)语法
  • Pipeline 也有两种创建方法:可以直接在 Jenkins 的 Web UI 界面中输入脚本;也可以通过创建一个 Jenkinsfile 脚本文件放入项目源码库中
  • 一般我们都推荐在 Jenkins 中直接从源代码控制(SCMD)中直接载入 Jenkinsfile Pipeline 这种方法

安装插件

  • Pipeline

  • Kubernetes (能够动态的生成 Slave 的 Pod)

  • Pipeline: Stage View Plugin (可视化构建)

Slave 构建任务

实例:

# 添加 Slave Pod 时,添加的 label 给 node 添加一个 lable 属性
node('salve-jnlp') {
stage('Clone') {
echo "1.Clone Stage"
}
stage('Test') {
echo "2.Test Stage"
}
stage('Build') {
echo "3.Build Stage"
}
stage('Deploy') {
echo "4. Deploy Stage"
}
}

构建前:

$ kubectl get pods -n kube-ops
NAME READY STATUS RESTARTS AGE
jenkins-bb4b795c5-wkpkq 1/1 Running 3 (6h59m ago) 3d23h
......

构建后:

$ kubectl get pods -n kube-ops
NAME READY STATUS RESTARTS AGE
jenkins-agent-8x175 1/1 ContainerCreating 0 0s
jenkins-bb4b795c5-wkpkq 1/1 Running 3 (6h59m ago) 3d23h
......

构建完成后:

这个 agent pod 就不在了

部署 kubernetes 应用

node('slave-jnlp') {
stage('Clone') {
echo "1.Clone Stage"
}
stage('Test') {
echo "2.Test Stage"
}
stage('Build') {
echo "3.Build Docker Image Stage"
}
stage('Push') {
echo "4.Push Docker Image Stage"
}
stage('YAML') {
echo "5.Change YAML File Stage"
}
stage('Deploy') {
echo "6.Deploy Stage"
}
}
使用 podTemplate 定义个阶段容器
Clone 代码 -> 单元测试 -> Golang 编译打包 -> Docker 镜像构建/推送 -> Kubectl 部署服务。

使用 kubectl 需提前将目录 /root/.kube 挂载到容器的 /root/.kube 目录下面,才可以使用 kubectl

另外如果在配置了后运行 Slave Pod 的时候出现了权限问题,这是因为 Jenkins Slave Pod 中没有配置权限,所以需要配置上 ServiceAccount,在 Slave Pod 配置的地方点击下面的高级,添加上对应的 ServiceAccount 即可

这里写了一段很简单的定义 podTemplate

def label = "slave-${UUID.randomUUID().toString()}"

podTemplate(label: label, containers: [
containerTemplate(name: 'golang', image: 'golang:1.18.3-alpine3.16', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'docker', image: 'docker:latest', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true)
], serviceAccount: 'jenkins', volumes: [
hostPathVolume(mountPath: '/home/jenkins/.kube', hostPath: '/root/.kube')
], envVars: [
envVar(key: 'DOCKER_HOST', value: 'tcp://docker-dind:2375') // 环境变量
]) {
node(label) {
def myRepo = checkout scm
def gitCommit = myRepo.GIT_COMMIT
def gitBranch = myRepo.GIT_BRANCH

stage('单元测试') {
echo "测试阶段"
}
stage('代码编译打包') {
container('golang') {
echo "代码编译打包阶段"
}
}
stage('构建 Docker 镜像') {
container('docker') {
echo "构建 Docker 镜像阶段"
}
}
stage('运行 Kubectl') {
container('kubectl') {
echo "查看 K8S 集群 Pod 列表"
sh "kubectl get pods"
}
}
}
}

直接贴一个完整的 Jenkinsfile ,有需要直接改动吧
def label = "slave-${UUID.randomUUID().toString()}"

def helmLint(String chartDir) {
println "校验 chart 模板"
sh "helm lint ${chartDir}"
}

def helmDeploy(Map args) {
if (args.debug) {
println "Debug 应用"
sh "helm upgrade --dry-run --debug --install ${args.name} ${args.chartDir} -f ${args.valuePath} --set image.tag=${args.imageTag} --namespace ${args.namespace}"
} else {
println "部署应用"
sh "helm upgrade --install ${args.name} ${args.chartDir} -f ${args.valuePath} --set image.tag=${args.imageTag} --namespace ${args.namespace}"
echo "应用 ${args.name} 部署成功. 可以使用 helm status ${args.name} 查看应用状态"
}
}

podTemplate(label: label, containers: [
containerTemplate(name: 'golang', image: 'golang:1.14.2-alpine3.11', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'docker', image: 'docker:latest', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'helm', image: 'cnych/helm', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true)
], serviceAccount: 'jenkins', envVars: [
envVar(key: 'DOCKER_HOST', value: 'tcp://docker-dind:2375') // 环境变量
]) {
node(label) {
def myRepo = checkout scm
// 获取 git commit id 作为镜像标签
def imageTag = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
// 仓库地址
def registryUrl = "harbor.k8s.local"
def imageEndpoint = "course/devops-demo"
// 镜像
def image = "${registryUrl}/${imageEndpoint}:${imageTag}"

stage('单元测试') {
echo "测试阶段"
}
stage('代码编译打包') {
try {
container('golang') {
echo "2.代码编译打包阶段"
sh """
export GOPROXY=https://goproxy.cn
GOOS=linux GOARCH=amd64 go build -v -o demo-app
"""
}
} catch (exc) {
println "构建失败 - ${currentBuild.fullDisplayName}"
throw(exc)
}
}
stage('构建 Docker 镜像') {
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: 'docker-auth',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASSWORD']]) {
container('docker') {
echo "3. 构建 Docker 镜像阶段"
sh """
cat /etc/resolv.conf
docker login ${registryUrl} -u ${DOCKER_USER} -p ${DOCKER_PASSWORD}
docker build -t ${image} .
docker push ${image}
"""
}
}
}
stage('运行 Helm') {
withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
container('helm') {
sh "mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config"
echo "4.开始 Helm 部署"
def userInput = input(
id: 'userInput',
message: '选择一个部署环境',
parameters: [
[
$class: 'ChoiceParameterDefinition',
choices: "Dev\nQA\nProd",
name: 'Env'
]
]
)
echo "部署应用到 ${userInput} 环境"
// 选择不同环境下面的 values 文件
if (userInput == "Dev") {
// deploy dev stuff
} else if (userInput == "QA"){
// deploy qa stuff
} else {
// deploy prod stuff
}
helmDeploy(
debug : false,
name : "devops-demo",
chartDir : "./helm",
namespace : "kube-ops",
valuePath : "./helm/my-values.yaml",
imageTag : "${imageTag}"
)
}
}
}
stage('运行 Kubectl') {
withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
container('kubectl') {
sh "mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config"
echo "5.查看应用"
sh "kubectl get all -n kube-ops -l app=devops-demo"
}
}
}
stage('快速回滚\?') {
withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
container('helm') {
sh "mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config"
def userInput = input(
id: 'userInput',
message: '是否需要快速回滚?',
parameters: [
[
$class: 'ChoiceParameterDefinition',
choices: "Y\nN",
name: '回滚\?'
]
]
)
if (userInput == "Y") {
sh "helm rollback devops-demo --namespace kube-ops"
}
}
}
}
}
}