一次Docker应用排查过程

背景

重新发布了一个应用,发现无法访问,把排查过程记录一下。

检查docker程序和开放端口

运行下面语句:

1
sudo docker ps

发现docker都在运行。

运行下面语句:

1
sudo netstat -tunpl

检查开放端口,端口开放正常。

检查frp的状态

运行下面语句:

1
sudo systemctl status frp

由于是通过frp穿透内网的,要看一下是否是frp问题,查看日志,没有报错。

检查内网网页状态

访问内网ip,报错:502 Bad Gateway

运行一下语句,查看docker日志:

1
sudo docker logs ee86b3ee2451

其中 ee86b3ee2451 为容器ID

运行下面语句,查看实时docker日志:

1
sudo docker logs -f ee86b3ee2451

进入Docker查看

如果还是有问题,运行下面语句,查看docker内部情况

1
sudo docker exec -it ee86b3ee2451 bash

总结

经过一番折腾,发现还是docker本身的问题,restart一下就好了,运行以下语句:

1
sudo systemctl restart docker

Django 的日志机制-4

进阶

其实,如果只是为了排错方便,记录一些日志,自定义类基本可以满足要求。但如果要记录访问系统的所有请求日志,那就无能为力了,因为不可能手动在每个接口代码加日志,也没必要。

这个时候,很自然就能想到 Django 中间件了。

Django 中间件

定义中间件类,包括 Filters 代码和 middleware 代码,在本地app文件夹中创建目录:middlewares,目录下创建文件 LogMiddleware.py :

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
49
50
51
52
53
54
55
56
57
58
59
from django.utils.deprecation import MiddlewareMixin
import threading
import logging
import json
import socket

local = threading.local()

class RequestLogFilter(logging.Filter):
"""
日志过滤器
"""

def filter(self, record):
record.sip = getattr(local, 'sip', 'none')
record.dip = getattr(local, 'dip', 'none')
record.body = getattr(local, 'body', 'none')
record.path = getattr(local, 'path', 'none')
record.method = getattr(local, 'method', 'none')
record.username = getattr(local, 'username', 'none')
record.status_code = getattr(local, 'status_code', 'none')
record.reason_phrase = getattr(local, 'reason_phrase', 'none')

return True

class RequestLogMiddleware(MiddlewareMixin):
"""
将request的信息记录在当前的请求线程上。
"""

def __init__(self, get_response=None):
self.get_response = get_response
self.apiLogger = logging.getLogger('web.log')

def __call__(self, request):

try:
body = json.loads(request.body)
except Exception:
body = dict()

if request.method == 'GET':
body.update(dict(request.GET))
else:
body.update(dict(request.POST))

local.body = body
local.path = request.path
local.method = request.method
local.username = request.user
local.sip = request.META.get('REMOTE_ADDR', '')
local.dip = socket.gethostbyname(socket.gethostname())

response = self.get_response(request)
local.status_code = response.status_code
local.reason_phrase = response.reason_phrase
self.apiLogger.info('system-auto')

return response
More...

算法练习:BM1 反转链表

题目内容

给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。

数据范围: 0 ≤ n ≤1000

要求:空间复杂度 O(1) ,时间复杂度 O(n) 。

如当输入链表{1,2,3}时,

经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。

我的思路

在遍历链表时,将当前节点的next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。借用网上一个图解:

More...

牛客网 面试必刷TOP101 算法题

介绍

发现一个除了Leecode以外比较好的刷算法的网站:

牛客网在线编程_算法篇_面试必刷TOP101 (nowcoder.com)

据说是收集了很多面试题精选的题目

题目例子

BM83 字符串变形

描述

对于一个长度为 n 字符串,我们需要对它做一些变形。

首先这个字符串中包含着一些空格,就像"Hello World"一样,然后我们要做的是把这个字符串中由空格隔开的单词反序,同时反转每个字符的大小写。

比如"Hello World"变形后就变成了"wORLD hELLO"。

数据范围: 1≤n≤1061≤n≤106 , 字符串中包括大写英文字母、小写英文字母、空格。

进阶:空间复杂度 O(n) , 时间复杂度 O(n)

输入描述:

给定一个字符串s以及它的长度n(1 ≤ n ≤ 10^6)

返回值描述:

请返回变形后的字符串。题目保证给定的字符串均由大小写字母和空格构成。

示例1

输入:
"This is a sample",16

返回值:
"SAMPLE A IS tHIS"

Python实现

参考了答案,我的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def trans(self, s: str, n: int) -> str:
word_list = list(s.split(" "))[::-1]
new_str = " ".join(word_list)
res = ""
for i in range(n):
if new_str[i] <= "Z" and new_str[i] >= "A":
res += chr(ord(new_str[i]) - ord("A") + ord("a"))
elif new_str[i] <= "z" and new_str[i] >= "a":
res += chr(ord(new_str[i]) - ord("a") + ord("A"))
else:
res += new_str[i]
print(res)
return res

其中以下写法值得借鉴:

  1. 倒序的实现为 list(s.split(" "))[::-1]
  2. 字母变为小写:chr(ord(new_str[i]) - ord(“A”) + ord(“a”))
  3. 另外 用 swapcase() 函数更简洁

界面和操作

界面如下:

操作还比较方便,有语法提示,有调试界面。

mermaid 插件语法错误解决

问题

之前安装的mermaid插件,用下来一直很好,但最近出了问题,脚本无法运行,报错:
Syntax error in graph mermaid version 8.14.0
error

解决方法

比较头疼的是,这个error没有具体错误信息,一开始解决起来无从下手。只好祭出替换大法,从少到多逐步替换,终于发现问题。
可能是由于这个插件为老外开发的,内容中出现全角的标点符号就会出错,包括“,”,“:” 等,把这些标点符号都换成半角的,就不会报错了。比较奇怪的是,汉字显示是没有问题的,可能是标点符号和正常的汉字不在一个编码范围。

Django 的日志机制-3

实现方式

其实最简单的方式就是直接在文件开头 import,然后程序中调用,像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# import the logging library
import logging
# Get an instance of a logger
logging.basicConfig(
format='%(asctime)s - %(pathname)s[%(lineno)d] - %(levelname)s: %(message)s',
level=logging.INFO)

logger = logging.getLogger(__name__)

def my_view(request, arg1, arg):
...
if bad_mojo:
# Log an error message
logger.error('Something went wrong!')

但这种方式并不好,如果在每个文件开头都这样写一遍,第一是麻烦,第二是如果哪天要改变输出日志格式,那每个文件都要改一遍,还不累死。

很显然,如果能封装成一个类,用的时候调用这个类,修改的时候也只需要修改这一个地方:

More...

Django 的日志机制-2

logging 结构

在 Django 中使用 Python 的标准库 logging 模块来记录日志,关于 logging 的配置,我这里不做过多介绍,只写其中最重要的四个部分:LoggersHandlersFilters 和 Formatters

Loggers

Logger 即记录器,是日志系统的入口。它有三个重要的工作:

  • 向应用程序(也就是你的项目)公开几种方法,以便运行时记录消息
  • 根据传递给 Logger 的消息的严重性,确定消息是否需要处理
  • 将需要处理的消息传递给所有感兴趣的处理器 Handler

每一条写入 Logger 的消息都是一条日志记录,每一条日志记录都包含级别,代表对应消息的严重程度。常用的级别如下:

  • DEBUG:排查故障时使用的低级别系统信息,通常开发时使用
  • INFO:一般的系统信息,并不算问题
  • WARNING:描述系统发生小问题的信息,但通常不影响功能
  • ERROR:描述系统发生大问题的信息,可能会导致功能不正常
  • CRITICAL:描述系统发生严重问题的信息,应用程序有崩溃的风险

当 Logger 处理一条消息时,会将自己的日志级别和这条消息配置的级别做对比。如果消息的级别匹配或者高于 Logger 的日志级别,它就会被进一步处理,否则这条消息就会被忽略掉。

当 Logger 确定了一条消息需要处理之后,会把它传给 Handler

More...

远程开发的VS Code方法

背景

远程开发并非仅仅是直接在服务器上编辑代码,远程开发侧重的不应该是是“远程”,而是“开发”,至于“远程”对用户而言应该是无感的,除了代码是在远程服务器上存储和运行,其他体验应该和本地开发一致,撰写代码时能享受到 IDE 带来的便利,运行代码时不必额外去上传下载——这似乎看起来很容易,但在VS Code Remote 出现之前,没有工具能做到。

使用 VS Code Remote,你可以无缝的从一台设备切换到另一台设备,开会前在台式机上写代码,开会时拿起笔记本连上远程服务器就能继续,下班可以直接关掉公司的电脑,回到家打开家里的电脑就可以继续,设备切换从来没有如此方便,你不必关心这一切是如何实现的,只要使用就好,因为大部分时间你并不会觉得这和以前的本地开发有什么区别。

安装及配置流程

远程开发必备扩展安装

在扩展中搜索 remote ,找到 Remote Development,安装即可,会为你安装包括 Remote-SSH 等全部远程开发使用的扩展。

安装完成后,你会发现左下角多了一个小图标,点击就可以看到用来连接远程开发的菜单了。

More...

免密远程登录Linux服务方法

问题

经常需要访问Linux服务器,每次都要输密码很麻烦,查询了资料,可以通过密钥对配置来实现免密登录,每次都查完就忘,这次记录一下。

客户端配置

如果你使用的是 macOS,那么直接打开终端,输入ssh-keygen -t rsa,回车确认即可,默认会存储在用户名录下的 .ssh 目录;

如果使用的是 Windows,那么需要安装 Git,然后在任意位置点击鼠标右键,选择“Git Bash Here”,然后输入ssh-keygen -t rsa,直接回车确认即可,默认也会存储在当前用户目录下的 .ssh 目录。

服务器端配置

使用ssh-copy-id root@服务器IP,需要输入密码进行确认,之后再登录就可以直接通过ssh root@服务器IP即可。

注意,如果你的帐号不是root,可以把root换为你平时登陆的帐号。

一些资料:

https://rem486.top/server/env/ssh-password-free-login.html#免密登录配置

https://www.jianshu.com/p/c80e063d61f6

https://www.xshellv.com/2021/04/28/mac免密登陆centos服务器/

Django 的日志机制-1

日志目的

日志文件在开发环境中以及在线上环境或者在测试环境中都可以很好的反应程序的执行情况,以及出现bug的时候,准确的定位bug的位置,请求方法,错误原因等。所以说日志对于程序猿来说是一个开发者必备的必须了解且精通的东西。

日志需要实现功能

日志要分等级

等级说明:debug调试信息;info用来收集关注的信息;warn警告信息;error错误信息

好多开发工程师记录日志总是喜欢用info级别来记录日志,一般的组件默认级别都是info,所有info默认都是会被记录的,而debug信息发布后,是不会被记录的。这是一种偷懒的做法,但这也是很普遍的做法。正确的方式应该根据日志本身的特性去设置日志的级别,其实规范的日志级别是非常重要的:

  • 正确的级别便于运维。便于统一调整系统日志级别,如特殊情况可以只记录error错误
  • 没有正确的级别,对后期日志分析和处理是留下很大的隐患。error是需要去关注,并且处理掉的问题。info是普通日志的记录,大部分时候是无需关注的。

error日志内容一定要详实

运营过大型系统的人都知道,除了数据库存储外,日志、图片、附件是存储的三大债主,他们是会占用非常非常大的空间,所有记录info的日志,要简洁易懂,避免空间浪费。 而对于error级别的错误,记录一定要详实,因为error的所有问题,是后期都要去解决的。

  • 请求的地址
  • 请求的参数
  • 请求的ip
  • 请求的用户
  • error具体信息
  • 输出的内容

为了能很好的反馈当时error产生场景,以上的这些内容都应该被记录,而且越详细越好。

全局统一收集日志

前文说过,error的日志,不仅是我们需要关注的,还是我需要解决掉的问题,所有error日志非常重要。错误日志的收集,必须是全局统一收集的。如果你发现你的errorr日志收集是在每个类中,这个一定要避免,不管你用那种语言,错误的处理,都是可以通过全局进行统一的处理,错误日志也要通过全局统一收集。

单个文件的大小要控制

因为大家都是通过日期方式保存的,但是因为有的人不重视日志,经常会看到有的系统单个日志文件上百M,有的甚至是几G,而实际大家处理问题关注的都是最近的日志,所以控制单个日志文件的大小,对日志的性能以及后期的运维都是非常便利的。

日志要便于浏览

日志文件小才便于浏览,日志最好能通过网址直接访问到,而不需要一波三折登录服务器,花10分钟下载下来,再来分析。

日志的安全性要得到保障

日志内容有时会包含敏感信息,特别是error日志,直接把系统的具体错误抛出来,所以日志除了查看方便,还需要确保日志文件的安全。

日志要定期清理

日志是非常占用存储的空间,日志太大对存储的性能也有一定的影响,所有日志要定期进行清理。

  • 空间充足可以保留半年
  • 空间不足最少也要保留3个月

理想的日志架构

graph LR
A(应用)-->B(日志)
B-->C(聚合)
C-->D(分析)

相关链接:
Django 的日志机制-2
Django 的日志机制-3
Django 的日志机制-4

请我喝杯咖啡吧~

支付宝
微信