本文 cnblog 博客地址
背景 从 阮一峰老师的博客 了解到 sadserver 这样一个可以提供 linux 服务器,并尝试解决系统和服务相关问题的在线测试平台。非常难得的是它可以直接提供一个公网的linux服务器(一般40-60分钟后会自动销毁),你可以在上面做任何探索。对于想要学习常用 linux 指令的同学,是一个非常不错的练手平台
sadserver 大部分题目都是免费的,46道题中11道需要会员,会员一个月5美元。可以在完成其他题目之后再小小氪💰一下挑战剩下的题目
本文整理了目前sadserver所有题目的分类和解题相关指令,带部分“剧透”,建议对题目本身有强烈兴趣的同学直接动手尝试
*推荐: 标注推荐的主要是和 linux 常用指令相关的题目,其他开源服务相关的题目可以选择性了解
*提醒: 部分题目需要root用户权限,以执行系统配置修改等相关指令,可通过sudo su
指令提权
题目分类
题目
类型/相关服务
题目/问题概述
相关指令
“Saint John”: what is writing to this log file?
linux
查找文件占用的进程
lsof, find
“Saskatoon”: counting IPs. (推荐)
linux
字符串计数
awk, uniq, sort
“Santiago”: Find the secret combination (推荐)
linux
文本内容过滤
grep
“Taipei”: Come a-knocking (推荐)
linux
knock 端口开放
knocked
“Lhasa”: Easy Math (推荐)
linux
数值计算
bc
“Bucharest”: Connecting to Postgres
postgresql
pgsql配置修复
“Bilbao”: Basic Kubernetes Problems
kubernetes
服务配置修复
kubectl
“Apia”: Needle in a Haystack
linux
文本比较
diff
“Manhattan”: can’t write data into database.
postgresql
pgsql 配置修复
“Tokyo”: can’t serve web file (推荐)
httpd, linux
httpd 配置修复
iptables
“Cape Town”: Borked Nginx
nginx
nginx 配置修复
systemctl
“Salta”: Docker container won’t start
Docker
容器内服务启动问题
docker, netstat
“Oaxaca”: Close an Open File
linux
查找文件占用的进程
lsof, exec
“Melbourne”: WSGI with Gunicorn
gunicorn
gunicorn 配置修复
“Lisbon”: etcd SSL cert troubles
etcd 访问不通
etcd, iptables
“Kihei”: Surely Not Another Disk Space Scenario (推荐)
linux
挂盘
pvcreate, vgcreate, lvcreate, mount
“Unimak Island”: Fun with Mr Jason
linux
文本解析
jq
“Ivujivik”: Parlez-vous Français?
python
excel 解析
python
“Paris”: Where is my webserver?
linux
http 接口调用和参数
curl
“Buenos Aires”: Kubernetes Pod Crashing
kubernetes
kubernetes 权限体系
kubectl
“Tarifa”: Between Two Seas
nginx
nginx 配置修复
docker
“Marrakech”: Word Histogram (推荐)
sqlite
文本解析
sqlite
“Rosario”: Restore a MySQL database
mysql
mysql 故障恢复
mysql
“Abaokoro”: Restore MySQL Databases Spooked by a Ghost
mysql
mysql 数据导入
mysql
“Poznań”: Helm Chart Issue in Kubernetes
kubernetes
helm 基本使用
helm
“Manado”: How much do you press?
linux
解压缩常用指令的使用
zip, tar, xz
“Warsaw”: Prometheus can’t scrape the webserver
linux
go 服务问题修复
docker
“Moyogalpa”: Security Snag. The Trials of Mary and John
linux
服务文件权限和防火墙设置
apparmor, systemctl, update-ca-certificates
“Helsingør”: The first walls of postgres physical replication
postgresql
postgresql 同步主库相关配置修复
docker
“Bekasi”: Supervisor is still around
wsgi
wsgi 启动配置修复
curl, supervisorctl
“Depok”: Nginx with Brotli
nginx
nginx 插件安装
cmake, make, systemctl
“Tukaani”: XZ LZMA Library Compromised
linux
preload 库问题修复
lsof
“Jakarta”: it’s always DNS.
linux
DNS 解析配置修复
“Bern”: Docker web container can’t connect to db container.
wordpress
通过 docker 启动 wordpress
docker
“Karakorum”: WTFIT – What The Fun Is This? (推荐)
linux
定位系统和进程启动问题
perl, strace, python
“Singara”: Docker and Kubernetes web app not working.
kubernetes
镜像重建和提交
docker, kubectl
“Hong-Kong”: can’t write data into database.
postgresql
postgresql 配置问题修复
postgres, systemctl
“Pokhara”: SSH and other sshenanigans (推荐)
linux
ssh 和 su 问题解决
ssh, su, chage, ssh-keygen
“Roseau”: Hack a Web Server
linux
john 暴力破解工具的使用
john
“Belo-Horizonte”: A Java Enigma
linux
java 程序执行问题解决
java, javap, free, fallocate, mkswap, swapon
“Chennai”: Pull a Rabbit from a Hat
rabbitmq
rabbitmq 测试生产消费
docker, python
“Monaco”: Disappearing Trick
linux
git和进程环境变量
git
“Florence”: Database Migration Hell
微服务
js+web后台+pgsql问题解决
docker, sed
如何解题
Level: 简单
题意 服务器上有一个日志文件在被一个进程占用,需要找到这个进程并停止
相关指令 1 2 3 4 5 6 7 # 查看文件占用进程 id lsof /var/log/bad.log ls -l /proc/*/fd/* | grep bad.log fuser /var/log/bad.log # 确认文件是否最近未更新 find /var/log/bad.log -mmin -0.1
题意 /home/admin/access.log 是一个 web 服务的访问记录日志(如 nginx 的访问日志就在 access.log 中),从中统计出访问次数最多的源ip
相关指令 1 2 3 4 5 6 7 8 9 10 # 截取ip cat /home/admin/access.log | awk '{print $1}' > /tmp/access_ips.log # 排序 cat /tmp/access_ips.log | sort > /tmp/sort_ips.log # 计数 & 再排序 # # cat /tmp/sort_ips.log | uniq -c | sort -nr > /tmp/count_ips.log
题意 统计特定字符串在每个文件中的出现次数,并导出只出现一次的文件,出现位置的下一行内容
相关指令 1 2 3 4 5 6 # 查找文件内容 & 统计数量 # find /home/admin -name "*.txt" | xargs grep -c 'Alice' # 输出之后一行(前一行是-B),且过滤出数字部分 grep "Alice" -A1 /home/admin/1342-0.txt | grep -oE "[0-9]+"
本题更像是解密游戏,而不是解决服务器问题,因此笔者选择(偷懒)跳过了这题,喜欢读解密类小说的可以试一下
题意 本机 80 端口启动了 http 服务,但端口被 knockd 进程限制访问,无法直接访问
需要通过 knock 对所有端口进行“开门”,使得 80 端口放开
参考-knock:端口敲门服务
相关指令 1 2 3 4 5 6 7 8 9 10 11 # 对 1000 到 65535 的所有端口发送 knock 请求( 发送 SYN ) for port in {1000..65535} do knock localhost ${port} done # 扫描本地常见服务端口( 发送 SYN ) nmap -p- localhost # 查看 nmap 扫描的端口范围 nmap -v -oG - | grep "Ports scanned"
题意 将一个文本中的第二列数字求和以及求平均
linux 本身的运算符支持整数运算,因此节点还提供了 python3 和 bc
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 获取第二列 sum_expr=`awk '{print $2}' /home/admin/scores.txt # 将文本的每一行拼接到同一行,并用加号分隔 paste -sd+ test.txt # 计算数学表达式 sum=`echo ${expr} | bc` # 文本行数 count=`cat scores.txt | wc -l` # 保留两位小数,计算两数相除 echo "scale=2; ${sum} / ${count}" | bc
1 2 3 4 5 6 7 8 9 10 11 12 13 sum = 0 num = 0 score_file = '/home/admin/scores.txt' with open (score_file, "r" ) as scores_file: lines = data = scores_file.read().splitlines() for line in lines: score = line.split(" " )[1 ] sum += float (score) num += 1 print ("%.2f" % (sum / num))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport ( "bufio" "fmt" "os" "strconv" "strings" ) func main () { scoreFile, _ := os.Open("/root/scores.txt" ) defer scoreFile.Close() scanner := bufio.NewScanner(scoreFile) scoreCount := 0 scoreSum := 0.0 for scanner.Scan() { scoreStr := strings.Split(scanner.Text(), " " )[1 ] scoreFloat, _ := strconv.ParseFloat(scoreStr, 32 ) scoreSum += scoreFloat scoreCount++ } fmt.Printf("%.2f\n" , float32 (scoreSum)/float32 (scoreCount)) }
题意 本机启动了 postgresql 服务,但因为配置问题连接失败。需要修复 pgsql 配置并重启
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 指定密码、用户和 db 连接 pgsql PGPASSWORD=app1user psql -h 127.0.0.1 -d app1 -U app1user -c '\q' # 查看 pgsql 进程和启动参数 (-e: 查看所有进程,-f: 列举所有进程信息) ps -ef | grep pgsql # 查看 pgsql 服务基础配置文件 vim /etc/postgresql/13/main/postgresql.conf # 查看 pgsql 登录身份和允许访问源地址的配置文件 vim /etc/postgresql/13/main/pg_hba.conf # 重启 pgsql systemctl restart postgresql
题意 manifest.yaml 中定义了 nginx service,但启动失败了,需要通过 pod 的启动信息去解决
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 检查指令: curl 10.43.216.196 # 查看 nginx pod 状态 kubectl get pods kubectl describe pod nginx-deployment-67699598cc-zrj6f # 解决 1 node(s) didn't match Pod' s node affinity/selector ... # kubectl label nodes node1 disk=ssd # kubectl delete pod nginx-deployment-67699598cc-zrj6f # 解决 1 Insufficient memory # sed -i "s/memory:.*/memory: 500Mi/g" manifest.yml # kubectl apply -f manifest.yml
题意 在 /home/admin/data 目录下有很多 txt 文件,其中一个和其他文件内容不一样,而且只是在一个段落中多了一个单词,找到这个单词
相关指令 1 2 3 4 5 6 7 8 # 输出文本的 md5 值 md5sum /home/admin/data/file1.txt # 对比两个文本差异(段落级别) diff /home/admin/data/file1.txt /home/admin/data/file2.txt # 对比两个文本差异(单词级别) wdiff /home/admin/data/file1.txt /home/admin/data/file2.txt
中等
题意 本机启动了 postgresql 服务,但执行 insert 会失败,找到原因并解决
相关指令 1 2 3 4 5 # 查看 postgresql 日志 tail -f /var/log/postgresql/postgresql-14-main.log # 重启 postgresql systemctl restart postgresql
题意 本机启动了 apache2(即 httpd ) 服务,但执行 curl 127.0.0.1:80 没有任何返回,确认具体原因
相关指令 1 2 3 4 5 6 7 8 9 10 11 # 查看防火墙规则列表 iptables -L --line-numbers # 删除指定规则 iptables -t filter --delete INPUT 1 # 清空所有防火墙规则 iptables -F # 给文件添加所有用户可读权限 chmod +r /var/www/html/index.html
题意 本机安装了 nginx 但无法启动,检查启动失败原因,修复并启动
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 重启 nginx 服务 systemctl restart nginx # 查看 nginx 启动日志 journalctl -u nginx # 查看 nginx 启动错误日志 tail -f /var/log/nginx/error.log # 查看 nginx 配置 /etc/nginx/nginx.conf # 查看 nginx systemd 服务管理配置 # vim /etc/systemd/system/nginx.service # 重新加载配置并重启 nginx systemctl daemon-reload systemctl restart nginx
题意 本机 /home/admin/app 目录下有一个支持容器化的 node 应用,并提供 Dockerfile 可以直接构建 服务主逻辑在 server.js 中 需要构建镜像后,启动该服务,并解决调用该服务接口遇到的问题
相关指令 1 2 3 4 5 6 7 8 9 10 11 # 构建镜像 docker build -t app . # 启动 docker run -d --name app -p 8888:8888 app # 检查端口占用 netstat -tunlp | grep 8888 # 查看容器日志 docker logs dd231758183e
题意 /home/admin/somefile 正在被一个进程占用,找到占用原因,并使得下次登录终端后,该文件不会再被进程占用
相关知识点: exec 指令
相关指令 1 2 3 4 5 # 找到占用文件的进程 ,并直接 kill lsof /home/admin/somefile | tail -2 | awk '{print $2}' | xargs kill -9 # 一个会导致 /tmp/test.txt 被bash进程占用的指令 exec 77> /tmp/test.txt
题意 本机运行了 nginx 和 gunicorn (实现了 WSGI 协议的 HTTP 服务器,master-worker 架构)
正常情况下,通过 curl 调用服务的调用链: curl -> nginx -> gunicorn -> wesi.py,其中 /home/admin/wesi.py 中定义了接口的响应方式为返回200状态码和字符串”Hello World”
但是通过 curl -i http://localhost 调用接口失败了,找到原因并通过修改配置解决
相关教程: Deploying Gunicorn 相关知识点: http header: content-length
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 查看 nginx 错误日志 tail -f /var/log/nginx/error.log # 查找 nginx 配置目录下带有指定关键字的配置文件 find /etc/nginx -name '*' -type f | xargs grep "gunicorn" # 重启 nginx systemctl restart nginx # 查看 gunicorn 配置 vim /etc/systemd/system/gunicorn.service # 查看 wsgi 服务配置 vim /home/admin/wsgi.py # 重启 gunicorn systemctl restart gunicorn
题意 本机部署了 etcd 服务,正常情况下可通过 etcdctl get foo
或 curl -i https://localhost:2379/v2/keys/foo
获取配置,但目前访问失败,需要解决
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 通过 etcdctl 获取配置 etcdctl get foo # 设置系统时间 date -s '2023-01-29 00:00:00' # 调用etcd接口 curl -ik https://localhost:2379/v2/keys/foo # 查看防火墙NAT类型的配置 iptables -t nat -L # 清空NAT类型防火墙规则 iptables -t nat -F
题意 当前路径的 kihei 程序会在 /home/admin/data 目录下创建一个 1.5G大小的文件,但因为磁盘空间不足,执行失败了,需要在不删除其他文件的情况下使得 kihei 能够正常执行
相关指令 本题是非常完整的磁盘挂载练手好题,可以结合挂盘相关的指令一起理解
参考: lvcreate 创建逻辑卷、vgcreate 创建卷组、pvcreate 创建物理卷、vgextend 扩容卷组、lv缩容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 # 查看 kihei 进程的详细日志 # ./kihei -v # 查看本地磁盘使用情况和剩余空间 df -h # 查看所有磁盘 lsblk # 创建物理卷 pvcreate /dev/nvme1n1 # 查看所有物理卷 pvs # 创建卷组 vgcreate vg1 /dev/nvme1n1 /dev/nvme2n1 # 查看所有卷组 # 创建逻辑卷 lvcreate -n lv1 -l 100%FREE vg1 # 查看逻辑卷 lvs # 格式化卷,初始化文件系统 mkfs -t ext4 /dev/vg1/lv1 # 卷挂载,并设置目录权限 mount /dev/vg1/lv1 /home/admin/data chown -R admin:root /home/admin/data
题意 station_information.json 中包含若干个单车租赁站的信息,筛选其中 没有自动服务机(has_kiosk=false)且剩余单车超过30辆(capacity>30)的租赁站,将它的id(station_id)写入答案文件中
相关指令 虽然题目提示本机有 jq 、 gron 和 jid 等工具,但后两者操作起来都不如同时包含解析和筛选器的 jq 方便,这里直接展示 jq 的几个基本指令,组合几个 filter ,就可以解答本题了
1 2 3 4 5 # 获取数组长度 echo "[1,2,3,4,5]" | jq length # 过滤器 echo '{"dates":[{"day":"monday","emotion":"happy"},{"day":"tuesday","emotion":"crazy"}]}' | jq -r '.dates[] | select(.emotion=="happy")'
题意 table_tableau11.csv 文件中记录了总统选举中,各个选区的投票情况信息,需要按题目的筛选条件得到对应的选区名称
省份,选区名称,选区编号,人口,选民,投票站,有效选票,有效选票百分比,被拒绝的选票 被拒绝的选票,被拒绝的选票百分比,总投票数,选民投票率,当选候选人
相关指令 本题主要考察通过各个语言/工具解析 csv 的方法,这里展示 python 和 go 的解答方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import csvmax_reject = 0 answer = "" with open ('table_tableau11.csv' ) as f: reader = csv.reader(f) next (reader) for row in reader: current_reject = int (float (row[8 ])) current_population = int (row[3 ]) if current_reject > max_reject: max_reject = current_reject if current_population < 100000 : answer = row[1 ] with open ('mysolution' , 'w' ) as f: f.write(f"{answer} \n" ) f.close()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport ( "encoding/csv" "os" "io/ioutil" "strconv" ) func main () { f, _ := os.Open("table_tableau11.csv" ) csvReader := csv.NewReader(f) csvReader.FieldsPerRecord = -1 records, _ := csvReader.ReadAll() answer := "" maxReject := 0 for _, currentRecord := range records[1 :] { currentReject, _ := strconv.Atoi(currentRecord[8 ]) currentPopulation, _ := strconv.Atoi(currentRecord[3 ]) if currentReject > maxReject { maxReject = currentReject if currentPopulation < 100000 { answer = currentRecord[1 ] } } } ioutil.WriteFile("mysolution" , []byte (answer + "\n" ), 0644 ) }
题意 本题没有提示应该比较难做出来,因为问题相关的代码甚至都看不到,所以不是很推荐,主要考察对 user-agent header 的了解
user-agent : 表示客户端类型,默认为空,浏览器访问时将会自动填充
这里访问 5000 端口启动的服务端会对 user-agent 进行校验,如果为空将不会返回数据,需要指定任意的 user-agent 再调用 5000 端口,拿到答案需要的密码
相关指令 1 2 3 4 # 添加 user-agent 调用接口 curl --user-agent "hhh" http://localhost:5000 curl -H 'User-Agent: hhh' http://localhost:5000 curl -A 'hhh' http://localhost:5000
题意 本机部署了 k8s ,其中名为 logshipper 的 pod 启动失败了,需要确认失败原因
本题主要考察 k8s 的 clusterrole 对应的权限类型
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # 查看所有 pod 的 label kubectl get pod --show-labels # 查看指定 label 对应的 pod 信息和状态 kubectl describe pod -l app=logshipper kubectl describe pod logshipper-597f84bf4f-6ssjq kubectl describe deployment logshipper # 查看 pod 日志(提示权限不足) kubectl logs logshipper-597f84bf4f-6ssjq --follow # 查看 logshipper pod 绑定的 serviceAccount 对应的 rolebinding 和 clusterrolebinding kubectl get rolebinding,clusterrolebinding --all-namespaces -o jsonpath='{range .items[?(@.subjects[0].name=="logshipper-sa")]}[{.roleRef.kind},{.roleRef.name}]{end}' # 查看指定 clusterrole kubectl describe ClusterRole logshipper-cluster-role # 编辑 clusterrole 权限 kubectl edit ClusterRole logshipper-cluster-role # 删除(触发自动拉起新的pod) kubectl delete pod logshipper-597f84bf4f-6ssjq # 获取服务状态 kubectl get pods -l app=logshipper --no-headers -o json | jq -r '.items[] | "\(.status.containerStatuses[0].ready)"'
题意 本机通过 docker compose 启动了一个 haproxy 和两个 nginx。因为 haproxy 的配置(haproxy.cfg)策略是对两个 nginx 节点进行轮询(roundrobin)访问,因此正常情况下,调用 localhost:5000 会依次返回 “nginx_0” 和 “nginx_1” 但目前调用只返回了 “nginx_0”,需要找到原因
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 调用 haproxy curl localhost:5000 # 查看 haproxy pod 日志 docker logs c79c9eb25143 # 查看 haproxy 配置 vim haproxy.cfg # 查看 nginx 配置 vim custom-nginx_0.conf vim custom-nginx_1.conf # 查看 docker compose 服务定义 vim docker-compose.yml # 重启 docker-compose 服务组 docker compose down docker compose up -d
题意 frankestein.txt 文本中有一段文章,找到其中出现次数排第二多的单词 常见的单词分隔符为: .,:; ,且单词需忽略大小写
相关指令 这里通过 sqlite3 来解决本题
1 2 3 4 5 6 7 8 9 10 11 # 所有单词分隔后,转成全大写并按换行符分隔写入新文件 cat frankestein.txt | tr '.' '\n' | tr ',' '\n' | tr ':' '\n' | tr ';' '\n' | tr ' ' '\n' | tr '"' '\n' | sed -r '/^\s*$/d' | tr 'a-z' 'A-Z' > frankestein_new.txt # 写入文件头(作为 sqlite 加载的列名) sed -i '1iword' frankestein_new.txt # sqlite: 导入文本到words表 .import frankestein_new.txt words # sql: 出现次数排第二的单词 SELECT a.word, a.count_word FROM (SELECT word, count(word) AS count_word FROM words GROUP BY word) AS a ORDER BY a.count_word DESC LIMIT 1 OFFSET 1;
本机启动了 mariadb ,需要在 main 数据库中执行 backup.sql 中的语句,但题目没有提供数据库登录密码
本题主要考察 mysql/mariadb 如何通过免密模式启动,参考 B.3.3.2 How to Reset the Root Password
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # 查看/修改 mariadb 启动配置 vim /usr/lib/systemd/system/mariadb.service # 添加systemctl启动服务的环境变量 [Service] Environment="MYSQLD_OPTS='--skip-grant-tables'" # 重启 mariadb systemctl daemon-reload service mariadb restart # 免密登录 mysql # 修正 backup.sql 语法问题 sed -i 's#?$#;#g' /home/admin/backup.sql # 执行 backup.sql use main; source /home/admin/backup.sql; mysql -Dmain < /home/admin/backup.sql
题意 本机启动了 mariadb ,需要将 dbs_to_restore.zip 中的三个 sql 文件分别导入 first 、 second 和 third 三个数据库中
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 检查磁盘占用 du -h --max-depth=1 /var # 解压 tar -xzvf dbs_to_restore.tar.gz # 导入三个数据库(注:导入时间较长) mysql -e "create database first" mysql -Dfirst < first.sql mysql -e "create database second" mysql -Dsecond < second.sql mysql -e "create database third" mysql -Dthird < third.sql
本机通过 web_chart 目录定义的 nginx chart 启动了一个 nginx pod,期望是调用任何一个 nginx 的 80 端口都能返回 configmap.yaml 中定义的 “Welcome SadServers” 页面
但现在调用返回的是 nginx 默认的页面,configmap 配的页面并没有生效。需要使之生效,还需要将 nginx 的副本数修改为 3
本题主要考察 helm 的基本使用,以及 k8s configmap 如何注入到 pod 中,参考:How do I attach a configmap to a deployment in Kubernetes?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 获取 nginx pod 对应的 ip 并调用 nginx 服务 pod_ip=`kubectl get pods -n default -o jsonpath='{.items[0].status.podIP}'` curl ${pod_ip} # 查看 deployment kubectl describe deployment web-chart-nginx # 修改 chart values(包含副本数配置) helm get values web-chart --all # 重装 web-chart helm uninstall web-chart helm install web-chart web_chart/ # 或: 更新 chart helm upgrade web-chart ./web_chart --values ./web_chart/values.yaml
修改后的 deployment:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 (省略) spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }} :{{ .Values.image.tag }} " imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 80 protocol: TCP volumeMounts: - mountPath: /usr/share/nginx/html/ name: nginx-html volumes: - name: nginx-html configMap: name: {{ .Release.Name }}-cm-index-html items: - key: index.html path: index.html
题意 通过本机提供的 gzip、xz、lzip、tar 等工具,将 35147 字节大小的 names 文件压缩成不到 9400 字节,并将压缩文件放到 solution 目录下
可以在不删除 任何行的前提下对文件进行修改
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 # gzip 压缩和解压 gzip -v9 names gunzip names.gz # xz xz names xz -d names.xz # lzip tar --use-compress-program=lbzip2 -cvf names.tar.bz2 names tar --use-compress-program=lbzip2 -xvf names.tar.bz2 # lz4 lz4 names lz4 -d names.lz4 # lzip lzip --best names lzip -d names.lz # lzop lzop -9 names lzop -d names.lz.lzo # zstd zstd -19 names zstd -d names.zst # 文件内容排序 sort names > names_new
题意 app 目录下是一个通过 gorilla/mux 框架搭建的 web 服务,并通过 promhttp 暴露 /metrics 接口
但是目前直接调用 http://localhost:9000/metrics 接口将失败,需要解决
相关指令 1 2 3 4 5 6 7 8 9 10 11 # 调用 metrics 接口 curl http://localhost:9000/metrics # 重启 docker compose 服务 docker compose down --volumes docker compose up -d # docker compose up --build -d # 查看 app 的启动逻辑 vim app/main.go
题意 本地有一个通过 systemctl 启动的 web 服务,虽然它可以运行但访问其接口会失败,需要修复证书和权限相关的问题,以及防火墙 apparmor 的配置问题
apparmor 的使用方式参考
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 # 查看服务定义 # cat /etc/systemd/system/webapp.service # 重启 webapp systemctl restart webapp # 解决 webapp 无法 su 的问题: 替换 webapp 的登录 shell vim /etc/passwd # 手动启动 webapp export APP_STATIC_DIR=/home/webapp/static-files export APP_CERT=/home/webapp/pki/server.crt export APP_KEY=/home/webapp/pki/server.pem su webapp -c "/usr/local/bin/webapp" # 修复证书权限问题 chown -R webapp:webapp /home/webapp/ # 添加 hosts echo "127.0.0.1 webapp" >> /etc/hosts # 检查指令 curl https://webapp:7000/users.html # 修复 SSL certificate problem: unable to get local issuer certificate 的问题 # cp /home/webapp/pki/CA.crt /usr/local/share/ca-certificates chmod 644 /usr/local/share/ca-certificates/CA.crt update-ca-certificates # 解决 open /home/webapp/static-files/users.html: permission denied 的问题 # vim /etc/apparmor.d/usr.local.bin.webapp # apparmor_parser -r /etc/apparmor.d/usr.local.bin.webapp
题意 本地通过 docker compose 启动了 postgresql,agent/check.sh 也就是检查答案的脚本中,将会先确认备库和主库的同步是否正常。但是目前备库启动失败了,需要找到原因并解决
扩展: yaml 中复用内容的写法(&)
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 查看节点状态 docker ps -a # 查看日志 docker logs 98e1b8d4a341 # 重启 compose docker compose down --volumes docker compose up -d # docker compose up -d --force-recreate # 修改配置 vim postgres/replica/postgres.conf # sed -i "s/max_connections = 80/max_connections = 100/g" postgres/replica/postgres.conf sed -i "s/max_worker_processes = 4/max_worker_processes = 8/g" postgres/replica/postgres.conf sed -i "s/max_wal_senders = 5/max_wal_senders = 10/g" postgres/replica/postgres.conf sed -i "s/max_locks_per_transaction = 32/max_locks_per_transaction = 64/g" postgres/replica/postgres.conf
题意 本地通过 uwsgi 启动了一个 https 服务,但通过 curl -k https://bekasi 调用失败
本题思路是先确认 uwsgi 的部署方式,再确认如何修改它的部署配置,最后重启
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 调用服务 curl -k https://bekasi # 查看 uwsgi 进程 ps -ef | grep uwsgi # 查看后台逻辑 cat bekasi/bekasi.py # vim /etc/supervisor/conf.d/uwsgi.conf # 重载配置并重启服务 sudo supervisorctl reload
题意 brotli 是 是 Google 在 2015 年 9 月推出的一种压缩算法,使用通用的 LZ77 无损压缩算法、Huffman 编码和二阶上下文建模(2nd order context modelling)的特定组合,旨在进一步提高压缩比
部署在本地的 nginx 服务目前还不支持 brotli 压缩算法,需要通过 brotli 源码安装插件
本题主要考察 nginx 插件的安装方法
参考: CentOS 8 安装并加载 Nginx 模组 ngx_brotli 然后启用 brotli 压缩
How to use nginx modules
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 # 调用 nginx 并指定编码为 br curl -H "Accept-Encoding: br" -I http://localhost # 编译 brotli 插件 # cd /home/admin/ngx_brotli/deps/brotli mkdir out cd out cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_C_FLAGS="-Ofast -m64 -march=native -mtune=native -flto -funroll-loops -ffunction-sections -fdata-sections -Wl,--gc-sections" -DCMAKE_CXX_FLAGS="-Ofast -m64 -march=native -mtune=native -flto -funroll-loops -ffunction-sections -fdata-sections -Wl,--gc-sections" -DCMAKE_INSTALL_PREFIX=./installed .. cmake --build . --config Release --target brotlienc # 重新编译 nginx 并链接 brotli cd /home/admin/nginx ./configure --with-compat --add-dynamic-module=../ngx_brotli make modules # 拷贝编译后产生的链接文件 cp objs/ngx_http_brotli_filter_module.so /usr/lib/nginx/modules/ cp objs/ngx_http_brotli_static_module.so /usr/lib/nginx/modules/ # 添加 # load_module modules/ngx_http_brotli_filter_module.so; load_module modules/ngx_http_brotli_static_module.so; # 开启 brotli 插件 # server { brotli on; } # 重载 nginx 配置 sudo systemctl reload nginx # 重启 nginx sudo systemctl restart nginx
题意 liblzma 是一个用于数据压缩的库,提供对 LZMA(Lempel-Ziv-Markov chain algorithm)和 XZ 压缩格式的支持
正常情况下本地服务需要加载的是 /usr/lib/x86_64-linux-gnu/liblzma.so.5.2.5,但通过 sudo lsof | grep liblzma.so.5 可以看到很多进程都加载了 /opt/.trash/liblzma.so.5,需要解决这个问题
本题主要考察预加载库的设置(LD_PRELOAD)
相关指令 1 2 3 4 5 6 7 8 9 10 11 # 查看 liblzma 相关进程 lsof | grep liblzma.so.5 # 删除无效库 rm /opt/.trash/liblzma.so.5 # 查看所有动态链接库之前预加载的库配置 vim /etc/ld.so.preload # 重启节点 reboot
困难 提醒: 困难的题目似乎登录节点后默认在根目录,需要记得切换到 /home/admin 才能看到题目提供的文件
题意 本机无法 ping 通 google.com,直接 ping 返回的是 Name or service not known,
本题是以前 sadserver 的实验题,所以虽然放在困难部分,但只要修复 /etc/nsswitch.conf 配置即可解决
参考-Linux系统解析域名的先后顺序files(/etc/hosts)OR dns(/etc/resolv.conf)
相关指令 1 2 3 4 5 6 # 查看并修复域名解析优先级配置 vim /etc/nsswitch.conf sed -i "s/hosts:.*/hosts: files dns/g" /etc/nsswitch.conf # 检查指令 ping google.com
题意 本地通过 docker 启动了 wordpress 和 mariadb 两个容器,mariadb 数据库状态正常,但 wordpress 服务无法访问 mariadb,需要解决这个问题
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 查看 wordpress 接口返回结果 curl -s localhost:80 |tail -4 # 检查 wordpress 容器访问域名 "mysql" 的结果 docker exec wordpress mysqladmin -h mysql -u root -ppassword --connect-timeout 2 ping # 查看 wordpress 容器的详细信息( 主要看环境变量 Env ) docker inspect 6ffb084b515c # 查看 wordpress 容器中设置的连接数据库相关的环境变量 docker exec wordpress env | grep WORDPRESS_DB_ # 查看 wordpress 配置文件中,和连接数据库相关的环境变量 grep WORDPRESS_DB_ /home/admin/html/wp-config.php # 删除容器 docker rm 6ffb084b515c # 启动新的 设置了域名映射的 wordpress 容器 docker run -it -p 80:80 -e WORDPRESS_DB_PASSWORD=password -e WORDPRESS_DB_USER=root --add-host mysql:172.17.0.1 --name wordpress -d wordpress:sad
题意 /home/admin/wtfit 文件是一个需要连接某个地址才能启动的服务。但首先它因为没有可执行权限而无法执行,其次它实际执行也有报错,需要给它添加可执行权限,并解决运行文件使得其能够正常启动
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # chmod 添加可执行权限 chmod +x /home/admin/wtfit # 通过 perl 脚本调用 chmod perl -e 'chmod 0755, "/usr/bin/chmod"' # 通过 ld-linux (动态链接器) 调用 chmod /lib64/ld-linux-x86-64.so.2 /usr/bin/chmod +x /usr/bin/chmod # 执行二进制文件,并跟踪进程堆栈 strace /home/admin/wtfit # 通过 python 快速启动一个监听端口 7777 http 服务端 python3 -m http.server 7777 &
题意 本地通过 k3s 启动了一个轻量级 kubernetes 集群,并在 /home/admin/deployment.yml 中定义了 webapp 这个 web 服务,但是容器启动有问题,通过 curl -i localhost:8888 无法调通,需要解决
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 获取所有 pod kubectl get pod -A # 获取 pod 的描述信息 kubectl describe pod webapp-deployment-666b67994b-5sffz -n web # 启动 register 镜像仓库 docker run -d -p 5000:5000 registry:2 # 提交镜像 webapp 到本地镜像仓库 docker tag webapp localhost:5000/webapp docker push localhost:5000/webapp # 修改 deployment 并重新启动 kubectl delete deployment webapp-deployment -n web vim /home/admin/deployment.yml kubectl apply -f /home/admin/deployment.yml # 开放端口 kubectl port-forward deployments/webapp-deployment 8888 -n web &
题意 本地启动了 postgresql 但无法正常连接和插入数据,需要从 pg 的存储目录配置去分析原因
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # 检查指令: 向 pg 中插入一条数据 sudo -u postgres psql -c "insert into persons(name) values ('jane smith');" -d dt # 手动启动 pg server 并确认报错 pg_ctlcluster 14 main restart -m fast # 查看配置中的存储目录 cat /etc/postgresql/14/main/postgresql.conf | grep data_directory # 创建数据目录并初始化 mkdir -p /opt/pgdata chown -R postgres:postgres /opt/pgdata su postgres -c "/usr/lib/postgresql/14/bin/initdb -D /opt/pgdata/main" # 重启 pgsql systemctl restart postgresql # 从日志确认启动启动是否正常 tail -f /var/log/postgresql/postgresql-14-main.log # 创建库表 sudo -u postgres psql -c "create database dt" sudo -u postgres psql -c "CREATE TABLE persons(name varchar(100))" -d dt
题意 本机启动了 sshd,且 client 用户下已经设置了 ssh key , 期望是可以通过 ssh client@localhost 免密访问本机,但是目前会失败,找到账号和 ssh 相关的问题并解决
注: /home/client/.ssh/authorized_keys 中已经添加了公钥
本题涉及面很广,包括 ssh 配置、linux 用户配置、用户可用系统资源配置等,但整体都是围绕 su client 和 ssh client@localhost 失败来解决的,建议实际解题时,通过这两个指令执行的报错一步步解决
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 # 切换 client 用户 su client # 解决 "Your account has expired; please contact your system administrator." 问题 # chage -l client # chage -E 2024-12-31 client # 解决 "su: failed to execute /usr/sbin/nologin: Resource temporarily unavailable" 问题 # lslogins client # 或: cat /etc/passwd | grep client # 设置用户登录 shell 为 /bin/bash usermod --shell /bin/bash client # 或: sed -i "s#/home/client:/usr/sbin/nologin#/home/client:/bin/bash#g" /etc/passwd # 检查指令: ssh 到 client 用户并执行 pwd ssh -v client@localhost "pwd" # # # vim /etc/security/limits.conf # 解决 ssh client 问题 # # ssh-keygen cat ~/.ssh/id_rsa.pub >> /home/client/.ssh/authorized_keys # echo "" > /home/client/.ssh/known_hosts # 修正私钥文件权限 chmod 600 /home/client/.ssh/* # 删除 ssh 错误配置并重启 sshd rm /etc/ssh/sshd_config.d/sad.conf systemctl restart sshd # 最后,切回 admin 用户并执行 ssh,使得 known hosts 被刷新 sudo -u client ssh client@localhost 'pwd'
题意 在本机的 apache web 服务器上存有一个密码文件,通过 zip 方式加密压缩。这个压缩文件通过 admin 用户无法直接访问,需要调用 apache 接口拿到,然而 apache 接口也是需要密码(配置: AuthUserFile)。因此需要通过密码破解工具 john 分别破解 apache 的密码 以及 压缩文件
本题主要是介绍暴力破解工具 john 的使用。它的原理简单来说是通过 预先准备的词典、哈希字典,对特定加密方式进行特定的暴力破解
参考-使用John the ripper破解密码
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 查看 httpd 配置,包括根路径和认证方式 cat /etc/apache2/sites-enabled/000-default.conf # 查看 apache 根路径下的压缩文件 ls -l /var/www/html # 破解 httpd 密码 /home/admin/john/run/john /etc/apache2/.htpasswd # 下载 webfile 文件 curl localhost/webfile -u "carlos:(这里替换成httpd密码)" --output secret # 解密 secret (格式: zip) 密码 /home/admin/john/run/zip2john secret > zip.hash /home/admin/john/run/john zip.hash
题意 Sad.class 是一段 java 程序,成功执行后,会打印一段题目需要的密码,但它目前是无法执行的,需要通过反编译等方法解决
参考: 使用fallocate命令创建swap分区
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 # 运行 class java Sad.class # 查看 /usr/bin/java 指向的实际的文件 readlink -f /usr/bin/java # 查看本机安装的其他版本 java ls -l /usr/lib/jvm | grep java # 将 /usr/bin/java 指向新的地址 sudo rm /usr/bin/java sudo ln -s /usr/lib/jvm/java-1.17.0-openjdk-amd64/bin/java /usr/bin/java # 反编译 Sad.class javap -c -l Sad.class # 重命名 mv Sad.class VerySad.class # 查看本地内存(总剩余内存只有 200m 左右,不足以运行进程) free -m # 查看本地磁盘(可以看到至少有4G空间) df -h # 以本地磁盘申请交换空间 # sudo fallocate -l 1G /swapfile # sudo chmod 600 /swapfile # sudo mkswap /swapfile # sudo swapon /swapfile # sudo swapon --show # free -m
题意 在本地的 rabbitmq-cluster-docker-master 目录下,提供了启动 rabbitmq 三节点集群的 compose 仓库(参考这个 serkodev/rabbitmq-cluster-docker ),需要正常启动集群,并先后运行 python3 ~/producer.py hello-lwc
和 python3 ~/consumer.py
,完成一个完整的生产和消费过程
注意: 虽然 consumer.py 和 producer.py 中确实有一些连接参数不太一致,但题目要求不能修改这两个代码文件,否则校验答案会失败,因此只能通过设置环境变量的方式来解决
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 重启 rabbitmq 集群 docker compose down --volumes docker compose up -d # 修改启动脚本最后一段,tail 之前创建一个空日志文件,解决 tail 执行失败导致卡住的问题 touch /var/log/rabbitmq/empty.log vim cluster-entrypoint.sh # 查看 .env 文件( compose 默认使用的环境变量 ),并将其修改为 consumer.py 中写死的用户名和密码 cat ./.env export RABBITMQ_DEFAULT_USER=username export RABBITMQ_DEFAULT_PASS=password # 设置环境变量并触发生产者 export RMQ_QUEUE=hello export RMQ_USER=username export RMQ_PASSWORD=password python3 ~/producer.py hello-lwc # 触发消费者 python3 ~/consumer.py
题意 本地 5000 端口上启动了一个 web 服务,通过 post 方法指定密码调用它的接口后,会返回一个密钥,需要把这个密钥写入 mysolution 文件中
根据提示,当前路径是一个代码工作路径,“那么”(虽然感觉这个推理有点牵强)可以推测是一个 git 路径,但是本地并没有代码,可以通过 git 指令恢复 webserver 的代码,确认其需要的密码是在哪个环境变量中定义的,并在已启动的 webserver 进程中,找到对应的环境变量
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 恢复 git 项目(即:重新拉取代码) # git status # git restore webserver_v1.py # git restore . # 查看 webserver 逻辑 cat webserver_v1.py # 查找 webserver_v1.py 配置的密码相关的环境变量,在已启动的 webserver 服务中定义的值 ps -ef | grep webserver.py | grep -v grep | awk '{print $2}' | xargs -I {} bash -c "cat /proc/{}/environ" | tr '\000' '\n' | grep "(环境变量名)" # 访问 webserver 并指定密码,并将得到的 secret 写入 mysolution 文件中 secret=`curl -X POST localhost:5000 --data-raw "password=(上一步得到的密码)" | sed 's/.* //g'` echo "$secret" > ~/mysolution
题意 /home/admin/app/docker-compose.yml 中定义了一个 nginx + nodejs + postgresql 的后台服务,其中 pgsql 作为数据库;api 服务进行数据库访问用户的初始化,以及提供接口;api_aggregator 对 pgsql 进行访问权限验证;nginx 服务提供代理和 https 接口。正常的启动和初始化顺序为: pgsql -> d
现在这些服务的启动和访问有问题,需要解决
相关指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 # 查看 compose 定义 cat ~/app/docker-compose.yml # 查看并修改 postgresql Dockerfile,以解决 postgresql 容器日志中的 "initdb: cannot be run as root" 问题 vim ~/app/postgresql/Dockerfile # 重启容器并重新构建镜像 docker compose down --volumes docker compose up --build -d # 添加 解决 api_aggregator 连接 pg 失败的问题 # sed -i "14iecho \"host all api_aggregator 0.0.0.0/0 md5\" >> /var/lib/postgresql/data/pg_hba.conf" ~/app/postgresql/entrypoint.sh # 解决 api_aggregator 容器日志提示连接 pgsql 失败问题 # cat ~/app/api/pkg/db/db.go # cat ~/app/docker-compose.yml | grep DATABASE_URL # # echo -n (上一步得到的密码)(上一步得到的用户名) | md5sum | awk '{print $1}' # sed -i "s/CREATE ROLE api_aggregator .*/CREATE ROLE api_aggregator LOGIN PASSWORD '(上一步计算得到的md5值)';/g" ~/app/api/migrations/1_users.up.sql # sed -i "10i\"time\"" ~/app/api/server.go sed -i "14itime.Sleep(10 * time.Second)" ~/app/api/server.go # sed -i "s#CMD node.*#CMD sleep 10; node /usr/src/app/index.js#g" ~/app/api_aggregator/Dockerfile # # 将 sadserver.local 域名映射到本地(for nginx) echo "127.0.0.1 sadserver.local" >> /etc/hosts # 重新生成证书,解决证书过期问题: curl: (60) SSL certificate problem: certificate has expired openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/certs/sadserver.key -out /etc/nginx/certs/sadserver.crt -subj "/C=CN/ST=SZ/L=NS/O=Internet/OU=Company/CN=sadserver.local" # 重启 nginx systemctl restart nginx # 验证接口访问 curl --cacert /etc/nginx/certs/sadserver.crt https://sadserver.local
总结 最近看了IT狂人这部剧,刚好想到,IT人平时的工作可能很杂,这里搞一点开发,那里做一点运维,甚至还没有程序那样有逻辑,但从外面来看,IT人确实是在做很多事情的,只是他们好像在一个锁上的礼物盒那样,不打开就不知道里面有什么
这部剧,和 sadserver 这个解题平台,都像是打开这个盒子的钥匙