使用Vagrant在Ubuntu系统上搭建Kubernetes集群

安装依赖

Vagrant

目前 Vagrant (https://www.vagrantup.com/downloads.html) 最新为 v2.2.6,Kubernetes 为 v1.16

Kubectl

下载最新版的 kubectl:

1
`curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl`

VirtualBox

需要下载 Vagrant 所兼容的版本( https://www.vagrantup.com/docs/virtualbox/ )。我是直接用 apt-get 安装 5.2 的版本。

1
apt-get install virtualbox virtualbox-guest-additions-iso

运行 ubuntu 单实例

创建 Vagrantfile 配置文件,这里在 virtualbox 内运行的是 Ubuntu 18.04。
若 Vagrant 的 Box 文件下载太慢,可先提前下载好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
wget https://app.vagrantup.com/ubuntu/boxes/bionic64/versions/20191125.0.0/providers/virtualbox.box

cat > virtualbox.json <EOF
{
"name": "ubuntu/bionic64",
"versions": [
{
"version": "20191125.0.0",
"providers": [
{
"name": "virtualbox",
"url": "virtualbox.box"
}
]
}
]
}
EOF

vagrant box add ubuntu/bionic64 virtualbox.json

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
# -*- mode: ruby -*-
# vi: set ft=ruby :

$num_instances = 1
$instance_name_prefix = "ubuntu"
$vm_gui = false
$vm_memory = 1024
$vm_cpus = 1
$vb_cpuexecutioncap = 100
ip_prefix = '172.17.8.'

Vagrant.configure(2) do |config|
(1..$num_instances).each do |i|
config.vm.define vm_name = "%s-%02d" % [$instance_name_prefix, i] do |node|
node.vm.box = 'ubuntu/bionic64'
node.vm.box_version = "20191125.0.0"
node.vm.box_check_update = false
node.vm.hostname = vm_name
node.vm.synced_folder '.', '/vagrant', type: 'virtualbox'

ip = "#{ip_prefix}#{i+100}"
node.vm.network 'private_network', ip: ip
node.vm.provider :virtualbox do |vb|
vb.gui = $vm_gui
vb.memory = $vm_memory
vb.cpus = $vm_cpus
vb.customize ["modifyvm", :id, "--cpuexecutioncap", "#{$vb_cpuexecutioncap}"]
end
end
end
end

接着启动 ubuntu

1
vagrant up

登录 ubuntu 实例

1
vagrant ssh

至此 ubuntu 就是运行起来了。然后却只有一个 实例,但是在实际应用中可能是多个实例组成的集群。

运行 ubuntu 多实例

先停止刚刚的实例

1
vagrant halt

然后修改 Vagrantfile 文件,将 $num_instances 变量改为3。这里我就启动3个实例来组成集群。
接着启动这3个实例

1
vagrant up

查看实例状态

1
2
3
4
5
6
7
8
9
10
-> % vagrant status
Current machine states:

ubuntu-01 running (virtualbox)
ubuntu-02 running (virtualbox)
ubuntu-03 running (virtualbox)

This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.

然后登录到其中的某个实例

1
vagrant ssh ubuntu-02

安装 Kubernetes

目前的3个 ubuntu 实例如下:

1
2
3
172.17.8.101 (ubuntu-01)    master
172.17.8.102 (ubuntu-02) worker1
172.17.8.103 (ubuntu-03) worker2

因为是在本地学习 k8s,因此为了方便我就使用 Rancher 的 k3s 来进行安装。
先分别在各个实例中安装 docker,参考: https://docs.docker.com/install/linux/docker-ce/ubuntu/

先安装 master node

1
2
vagrant ssh ubuntu-01
curl -sfL https://get.k3s.io | sh -

接着分别进入另外两个实例内安装 worker node,其中 K3S_TOKEN 的值来自 /var/lib/rancher/k3s/server/node-token 文件。

1
2
vagrant ssh ubuntu-02
curl -sfL https://get.k3s.io | K3S_URL=https://172.17.8.101:6443 K3S_TOKEN=XXX sh -

最后可以将 master node 的 /etc/rancher/k3s/k3s.yaml 文件内容复制到 host 系统上来,这样就可以直接在 host 系统中用 kubectl 来操作集群。

1
2
3
4
5
-> % k get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
ubuntu-03 Ready <none> 4m57s v1.16.3-k3s.2 172.17.8.103 <none> Ubuntu 18.04.3 LTS 4.15.0-70-generic docker://19.3.5
ubuntu-02 Ready <none> 7m25s v1.16.3-k3s.2 172.17.8.102 <none> Ubuntu 18.04.3 LTS 4.15.0-70-generic docker://19.3.5
ubuntu-01 Ready master 8m5s v1.16.3-k3s.2 172.17.8.101 <none> Ubuntu 18.04.3 LTS 4.15.0-70-generic docker://19.3.5

当前安装的 k3s 是 v1.0.0,k8s 是 v1.16.3

最后我将完整的配置已经上传到 github 上了。大家有需要可以直接执行如下命令即可:

1
2
3
git clone https://github.com/wusuopu/kubernetes-vagrant-ubuntu
cd kubernetes-vagrant-ubuntu
vagrant up

使用TypeScript开发Express应用

首先创建新项目

1
yarn init

然后安装 express

1
2
yarn add express
yarn add @types/express @types/node --dev

安装 TypeScript

1
yarn add --dev typescript

配置 webpack

这是最麻烦的一步。先安装 webpack:

1
2
yarn add --dev webpack webpack-cli webpack-node-externals ts-loader
yarn add --dev nodemon webpack-shell-plugin

添加 webpack.config.js:

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
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const WebpackShellPlugin = require('webpack-shell-plugin');

const { NODE_ENV = 'production' } = process.env;
const rootPath = path.resolve(__dirname)

let plugins = []
if (NODE_ENV === 'development') {
plugins.push(new WebpackShellPlugin({
onBuildEnd: ['yarn run:dev']
}))
}

module.exports = {
entry: path.resolve(rootPath, './src/index.ts'),
mode: NODE_ENV,
target: 'node',
watch: NODE_ENV === 'development',
externals: [ nodeExternals() ],
output: {
path: path.resolve(rootPath, 'build', NODE_ENV === 'development' ? 'dev' : 'prod'),
filename: 'index.js'
},
resolve: {
extensions: ['.ts', '.js'],
},
plugins: plugins,
module: {
rules: [
{
test: /\.ts$/,
use: [
'ts-loader',
]
}
]
},
}

添加 tsconfig.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": true,
"outDir": "build"
},
"exclude": [
"node_modules",
"build",
"config",
"release"
]
}

package.json 中添加相关命令:

1
2
3
4
5
6
"scripts": {
"start": "node build/prod/index.js",
"start:dev": "NODE_ENV=development node node_modules/webpack/bin/webpack.js --config webpack.config.js",
"build": "NODE_ENV=production node node_modules/webpack/bin/webpack.js --config webpack.config.js",
"run:dev": "NODE_ENV=development nodemon build/dev/index.js",
}

这样在开发过程只需要执行 yarn run start:dev 启动开发服务器即可,在代码有改动之后会自己重新 build 并重启服务。
开发完成之后执行 yarn build 进行打包发布。

使用 jest 进行测试(可选)

安装 jest:

1
yarn add -D jest @types/jest ts-jest supertest

添加 jest.config.js:

1
2
3
4
5
6
module.exports = {
testEnvironment: 'node',
transform: {
"^.+\\.ts$": "ts-jest"
},
};

相应的测试文件的文件名以 .test.ts 结尾。

然后在 package.json 中添加相关命令:

1
2
3
"scripts": {
"test": "jest"
}

最终项目目录结构如下:

1
2
3
4
5
6
7
8
9
10
.
├── jest.config.js
├── package.json
├── src
│ └── index.ts
├── tests
│ └── index.test.ts
├── tsconfig.json
├── webpack.config.js
└── yarn.lock

参考源代码: https://github.com/wusuopu/baidu-pan-cli

记录神舟战神 Z7M-KP7GZ 安装黑苹果过程

前言

现在这台 MacBook Air 差不多用了4年,因此打算换台新的电脑。目前需要一台有独显的电脑,一来是跑一些程序,二来是方便打游戏。
另外由于 macOS 已经用习惯了,不想再换其他系统。因此才考虑的安装黑苹果。通过两周的调研,别人给的经验是最好选择已经有人安装成功过的电脑型号,免得有问题自己折腾。
于是乎就选择了性价比比较高的神舟。最后安装完成之后,系统还有些小问题,不过基本也能用。对于要求比较高的建议还是白苹果吧。

准备

  • 一个8G以上的U盘,用于烧写系统镜像
  • 一个U盘,大小不限,只要启动WinPE就行
  • 一个usb鼠标,安装过程中触摸板用不了

因为我打算安装3个系统,我的硬盘分区如下:

1
2
3
4
5
6
7
8
9
10
11
12
128G SSD:
ESP 600M 用于EFI引导
EXT4 127G 用于安装ubuntu

1TB机械硬盘:
ESP 200M 用于EFI引导
MSR 128M
NTFS 100G Windows C
NTFS 100G Windows D
SWAP 16G Linux交换分区
EXFAT32 320G 用于多个系统之间分享文件
APFS 440G 安装黑苹果

在安装过程中需要用苹果的硬盘工具进行分区,如果硬盘上有文件的话需要先进行备份。

安装过程我是参考的黑果小兵博客: https://blog.daliansky.net/Lenovo-Xiaoxin-Air-13-macOS-Mojave-installation-tutorial.html

下载镜像 ( https://blog.daliansky.net/tags/%E9%95%9C%E5%83%8F/ ) 然后使用镜像烧写工具将镜像烧写到8G的U盘。这里我用的是 Etcher 这个工具。
参考别人的说法,10.14 的系统没有 Nvidia 的显卡驱动,10.13 的系统才有。不过我还是下载的 10.14.4 的系统镜像。

下载 Z7M-KP7GZ 的相关驱动: https://github.com/kirainmoe/hasee-z7-kp7gz-macos

安装步骤

启动电脑进入 BIOS 禁用 Secure Boot。然后通过U盘启动进入WinPE,先按照上面的硬盘分区计划对硬盘进行分区。如果你是整块硬盘只用于安装 macOS,则可以忽略这上步。
然后重启换另一个U盘启动进入 macOS 的安装界面。进入 Clover 后按 o 键在 Boot 项后面添加 nv_disable=1 参数。如图:

然后回车开始引导 macOS。等几分钟之后就进入了安装程序,依次选择语言、同意用户协议、选择安装硬盘。
这里可以打开硬盘工具,然后进行分区。建议使用 APFS 格式的分区。注意,如果原来硬盘上有数据的记得先备份。如图:

安装过程中会重启,重启之后进入 Clover 再选择使用对应的硬盘分区,选择”Boot macOS install from Mac”启动项,不要选择Preboot的启动项。

如果出现Kernel Panic错误,重启再来试一遍吧。

安装过程中啥都干不了,只能等了。差不多等半个小时就安装完成。接下来进入设置向导,选择地区、添加几号等操作。这个过程中触摸板用不了,所以需要外接鼠标。
最后安装完成,进入系统。

系统安装完成之后,接下来就是安装EFI。因为现在EFI文件还是U盘里,否则就只能每次启动时都需要通过U盘来引导了。
在前面使用硬盘工具进行分区时,它会在当前硬盘的开始位置创建一个200M的ESP分区。我们只需要将U盘中的EFI复制到这个分区里即可。

打开终端执行命令:

1
2
3
4
5
6
# 查找分区情况
diskutil list

# 分别挂载本地硬盘的EFI分区各u盘的EFI分区
sudo diskutil mount disk1s1
sudo diskutil mount disk3s1

然后将u盘中EFI目录下的所有文件复制到硬盘中的EFI目录。如果之前已经有安装过其他的系统,那么只需要将u盘中的 EFI/CLOVER 复制到硬盘中的 EFI 目录下即可

安装驱动

现在系统安装好了,EFI也安装好了。应该就可以正常地进入系统了。不过目前还有这些问题:

  • 触摸板用不了
  • WiFi、蓝牙用不了
  • 显卡用不了,不用使用外接显示器

然后我们再接着安装驱动文件。先挂载 ESP 分区,备份 EFI 目录,以免出错之后进不了系统时可以恢复。

删掉 /EFI/CLOVER 文件夹中的所有文件,然后将刚刚下载的所有驱动文件复制到 /EFI/CLOVER 目录下。
重启电脑然后执行里面的 optimize.sh 脚本,注意不要启动HiDPI。驱动安装好之后触摸板应该就能用了。

如果声卡不能使用的话,执行命令 kextstat -list-only | grep AppleALC 看看AppleALC.kext 驱动已正确加载。如果没有正确加载,请将其安装在 /Library/Extensions 下后执行命令 sudo kextcache -i / 重建缓存并重启。

接下来搞定无线网卡,如果不想折腾直接用有线了行。Z7M-KP7GZ用的是Intel的无线网卡,这个在 macOS 下是无解的。
其他折腾过的网友建议是换Broadcom的。如果要使用AirDrop或者Handoff,要求有蓝牙4.0LE,需要特定型号的博通网卡,比如博通BCM94322HMP(有带蓝牙和不带蓝牙版本的,注意挑选)。

于是我就在淘宝上买了块 BCM94350 WiFi、蓝牙二合一。然后拆机将网卡换上去。网卡在风扇的旁边。

结果一开机系统就崩溃了,进入不了登录界面。后来联系卖家,他们说这个只有 macOS 10.13 的驱动,没有 10.14 的。而我安装主正是 10.14 的系统。
欲哭无泪啊,折腾了两天,BCM94350网卡的驱动实在搞不定。最后只得再买了个支持黑苹果的USB无线网卡。
另外显卡驱动目前在 10.14 下也是无解的。如果要外接显示器的话,可能需要再购买带显卡的 type-C 设备。

react storybook使用typescript

在前面的文章有介绍在storybook中使用antd
之前开发js是使用的flow来作类型检查,最近想尝试一下typescript。
如果是从头创建一个空的typescript项目则相对就比较简单,但是现在我们是需要将之前的create-react-app项目迁移到typescript。

在create-react-app中添加typescript

参考 https://facebook.github.io/create-react-app/docs/adding-typescript
react-scripts从2.1.0版本开始就支持typescript了,这里我们先将项目的react-scripts依赖升级到最新版本:

1
yarn add --exact react-scripts@2.1.8

然后再添加typescript依赖:

1
yarn add typescript @types/node @types/react @types/react-dom @types/jest

然后将 src/index.js 更名为 src/index.tsx
接着创建 tsconfig.json 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"compilerOptions": {
"module": "esnext",
"target": "es5",
"lib": ["es5", "es6", "es7", "es2017", "dom"],
"sourceMap": true,
"allowJs": true,
"moduleResolution": "node",
"forceConsistentCasingInFileNames": true,
"skipLibCheck": false,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
},
"include": [
"src/**/*"
]
}

在storybook中添加typescript

参考 https://storybook.js.org/docs/configurations/typescript-config/
同样也先将storybook升级到最新版:

1
yarn add --dev --exact @storybook/addons@5.0.1 @storybook/react@5.0.1

然后再添加依赖:
1
yarn add --dev awesome-typescript-loader @types/storybook__react @storybook/addon-info react-docgen-typescript-webpack-plugin ts-jest

接着修改webpack配置,对应.storybook/webpack.config.js文件:

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
const path = require("path");
const antdTheme = {
'@primary-color': '#846bc1',
}

module.exports = ({ config, mode }) => {
config.module.rules.push({
test: /\.less$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "less-loader",
options: {
modifyVars: antdTheme, // 如果要自定义主题样式
javascriptEnabled: true
}
}]
});
config.module.rules.push({
test: /\.(ts|tsx)$/,
loader: require.resolve('awesome-typescript-loader'),
options: { configFileName: path.resolve(__dirname, './tsconfig.json') }
});
config.resolve.extensions.push('.ts', '.tsx');

return config;
};

以及babel配置,对应.storybook/babelrc.js文件:

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
module.exports = function(api) {
api.cache.forever();
return {
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"targets": {
"browsers": [
">1%",
"last 4 versions",
"Firefox ESR",
"not ie < 11"
]
}
}
],
"@babel/preset-react"
],
"plugins": [
[
"import",
{
"libraryName": "antd",
"style": true
}
]
]
}
}

同时修改storybook配置.storybook/config.js,让其支持.tsx文件:
将之前的:

1
const req = require.context('../src/components', true, /\.stories\.js$/)

修改为:
1
const req = require.context('../src/components', true, /\.stories\.[jt]sx?$/)

最后再创建 .storybook/tsconfig.json 文件:

1
2
3
4
5
6
7
8
{
"extends": "../tsconfig",
"compilerOptions": {
"jsx": "react",
"isolatedModules": false,
"noEmit": false
}
}

至此一切都完成了。

运行storybook时若出现错误: Error: Cannot find module '@emotion/core/package.json',则手动安装一下: yarn add --dev @emotion/core

在树莓派3上运行Android6

最近需要在 android 上进行测试,无奈在电脑上使用模拟器运行速度太慢了。
正好我还有几个闲置的树莓派3设备,于是就尝试在树莓派上运行android看看。

刻录系统到SD卡

1.先下载 android6 的树莓派镜像: https://pan.baidu.com/s/1YHrmjN3be7UaLAdBJr-YhQ

2.下载完成后解压,然后开始执行写入操作: sudo dd if=andrpi3-20160626.img of=/dev/disk3 bs=4096000

这里我用的读卡器,被识别为 disk3。文件比较大,请耐心等待。我写入差不多花了一个小时的时间。

运行系统

将SD卡插回到树莓派中,并接上鼠标、键盘、显示器,然后开机。
这里稍微注意一下,树莓派3需要 5V/2A 的电源才能工作。
刚开始我试了一下开不了机,结果才发现是电源电压不足。

运行效果如图:

连接上网络之后使用 adb 进行操作: adb connect 192.168.0.101
这里我的树莓派的ip为 192.168.0.101

在storybook中使用antd

最近一个 react 的项目有用到了 antd 这个 ui 库。这里作个笔记记录一下如何在 storybook 中显示 antd 的组件。

项目是使用 create-react-app 创建的,项目目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
├── .storybook/
├── README.md
├── antd-theme.js
├── node_modules
├── package.json
├── public
└── src
├── App.js
├── components
│   ├── button.js
│   └── button.stories.js
├── index.js
└── registerServiceWorker.js

这里创建了一个 button 组件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/components/button.js
import React, { Component, Fragment } from 'react';
import { Button } from 'antd';

class ButtonGhost extends Component {
render() {
return (
<Fragment>
<h3 className="ex-title">Ghost Button</h3>
<div style={{ background: 'rgb(47, 45, 165)', padding: '26px 16px 16px' }}>
<Button type="primary">Primary</Button>
<Button className="ml20" ghost>Default</Button>
<Button className="ml20" type="dashed" ghost>Dashed</Button>
<Button className="ml20" type="danger" ghost>danger</Button>
</div>
</Fragment>
);
}
}

export default ButtonGhost;

对应的 storybook 案例如下:

1
2
3
4
5
6
7
// src/components/button.stories.js
import React from 'react';
import { storiesOf } from '@storybook/react';
import Button from './button';

storiesOf('General', module)
.add('Button', () => <Button />)

然后 storybook 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
// .storybook/config.js
import React from 'react'
import { configure } from '@storybook/react'
import { ThemeProvider } from 'styled-components'

const req = require.context('../src/components', true, /\.stories\.js$/)

function loadStories() {
req.keys().forEach((filename) => req(filename))
}

configure(loadStories, module)

然后运行 storybook : start-storybook -p 6006 -c .storybook,效果如下:

这是由于 antd 的 css 没有加载,因此所有按钮的样式都没有。
参考 https://ant.design/docs/react/use-with-create-react-app-cn 的说明,修改 babel 和 webpack 的配置。
参考 https://storybook.js.org/configurations/custom-webpack-config 的说明,修改 storybook 的 webpack 配置。

创建文件 .storybook/.babelrc

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
{
"presets": [
[
"env",
{
"modules": false,
"targets": {
"browsers": [
">1%",
"last 4 versions",
"Firefox ESR",
"not ie < 11"
]
}
}
],
"react",
"stage-3"
],
"plugins": [
[
"import",
{
"libraryName": "antd",
"style": true
}
]
]
}

创建文件 .storybook/webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "less-loader",
options: {
//modifyVars: antdTheme, // 如果要自定义主题样式
javascriptEnabled: true
}
}]
}
]
}
};

然后在 .storybook/config.js 文件添加 import 'antd/dist/antd.less';
最后的效果如下:

使用python编写游戏修改器

最近比较怀旧,在玩一个比较老的PC游戏。由于游戏难度太高了,于是就打算自己写一个修改器。
通过查阅资料,在 Windows 下的修改器主要需要用到四个函数:OpenProcess, CloseHandle, WriteProcessMemory, ReadProcessMemory。

这几个都是C++的函数,在Python中可以通过ctypes来直接调用。
然后接下来介绍一下基本的操作流程。

1.通过 任务管理器 或者其他方式得到需要修改的游戏进程。然后通过 OpenProcess 注入该进程。

1
2
3
4
5
6
7
8
9
PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_VM_OPERATION = 0x0008
PROCESS_VM_READ = 0x0010
PROCESS_VM_WRITE = 0x0020

hProcess = ctypes.windll.kernel32.OpenProcess(
PROCESS_QUERY_INFORMATION|PROCESS_VM_READ|PROCESS_VM_OPERATION|PROCESS_VM_WRITE,
False, pid
)

2.然后可以通过 ReadProcessMemory 来扫描游戏的内存找到需要修改的数值项的内存地址。

1
2
3
4
5
6
7
8
9
buf = ctypes.c_int32()
nread = ctypes.c_size_t()
ret = ctypes.windll.kernel32.ReadProcessMemory(
hProcess,
base_addr,
ctypes.byref(buf),
ctypes.sizeof(buf),
ctypes.byref(nread)
)

这里是读取 base_addr 地址之后的4个字节的内容。可以通过循环来遍历游戏的内存,找到需要修改的地址。
当然,为了方便也可以直接使用 Cheat Engine 之类的软件来查找,然后把找到的内存地址记录下来即可。

3.得到需要修改的内存地址之后,就可以 WriteProcessMemory 来修改该地址保存的值。

1
2
3
4
5
6
7
8
9
buf = ctypes.c_int32(value)
nread = ctypes.c_size_t()
ret = ctypes.windll.kernel32.WriteProcessMemory(
hProcess,
base_addr,
ctypes.byref(buf),
ctypes.sizeof(buf),
ctypes.byref(nwrite)
)

这里是往 base_addr 这个地址写入值为 value 的4字节内容。

4.最后如果不再需要修改了的话,就通过 CloseHandle 关闭该注入操作。

1
ctypes.windll.kernel32.CloseHandle(hProcess)

以上都是针对 Windows 系统的,对于 Linux 系统的话 可以通过 ptrace (http://man7.org/linux/man-pages/man2/ptrace.2.html) 操作实现。由于我没有 Linux 的游戏就没有研究了。

上面修改器的完整源代码,如有需要可通过以下链接获取:
https://github.com/wusuopu/cheat_engine_caesar3

使用pytest测试flask应用

python 本身就有 unittest 单元测试框架,但是觉得它并不是很好用,我更倾向于使用 pytest 。

下面通过一个例子来介绍如何使用 pytest 对 flask 应用进行单元测试。

首先新建一个 flask 应用,并针对根路径创建一条路由。代码如下:

1
2
3
4
5
6
# server.py
app = flask.Flask(__name__)

@app.route('/')
def home():
return 'ok'

然后针对首页编写单元测试,代码如下:

1
2
3
4
5
6
# tests/test_app.py

def test_home_page(client):
rv = client.get('/')
assert rv.status_code == 200
assert rv.data == b'ok'

然后执行命令运行该测试用例: pytest -s tests/test_app.py

在 pytest 中编写测试用例就只需要新建一个以 test_ 开头的函数即可。

以上是针对flask路由作的最基本测试。接下来编写一个新的路由,该页面只有用户登录之后才能访问。代码如下:

1
2
3
4
5
6
# server.py
@app.route('/member')
@flask_security.decorators.login_required
def member():
user = flask_security.core.current_user
return str(user.id)

要对该路由进行测试,则需要先创建一个用户。

1
2
3
4
5
6
7
8
9
# tests/test_app.py
def setup_module(module):
App.testing = True
fixture.setup()


def teardown_module(module):
"""
"""

上面的 setup_moduleteardown_module 函数分别是在所有的测试用例执行之前与执行之后执行。在这里我们通过 setup_module 在执行测试之前先创建一个用户。然后再创建一个 pytest 的 fixture:
1
2
3
4
5
6
7
8
# tests/conftest.py

@pytest.fixture
def auth_client(client):
with client.session_transaction() as sess:
sess['user_id'] = str(fixture.users[0].id)

yield client

这里创建了一个 auth_client fixture,之后所有以 auth_client 发起的请求都是登录状态的。

最后再针对 /member 路由编写两个测试用例,分别是未登录状态与登录状态下的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def test_member_page_without_login(client):
"""
没有登录则跳转到登录页面
"""
rv = client.get('/member')
assert rv.headers['Location'] == 'http://localhost/login?next=%2Fmember'
assert rv.status_code == 302


def test_member_page_with_login(auth_client):
"""
已经登录则返回当前用户id
"""
rv = auth_client.get('/member')
assert rv.status_code == 200
assert rv.data.decode('utf8') == str(fixture.users[0].id)

以上就是一个简单的 flask 应用了。但是有时一个稍微复杂一点的应用会用到一些第三方的api。这时针对这种情况编写测试用例时就需要用到 mock 功能了。再编写一个新的路由页面:

1
2
3
4
5
6
7
8
# server.py

@app.route('/movies')
def movies():
data = utils.fetch_movies()
if not data:
return '', 500
return flask.jsonify(data)

1
2
3
4
5
6
7
8
9
# utils.py

def fetch_movies():
try:
url = 'http://api.douban.com/v2/movie/top250?start=0&count=1'
res = requests.get(url, timeout=5)
return res.json()
except Exception as e:
return {}

请求该路由会返回豆瓣top250的电影信息。然后再编写两个测试用例分别模拟api调用成功与失败的情况。

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
# tests/test_app.py
def test_movies_api(client):
"""
调用豆瓣api成功的情况
"""
fetch_movies_patch = mock.patch('utils.fetch_movies')

func = fetch_movies_patch.start()
func.return_value = {'start': 0, 'count': 0, 'subjects': []}

rv = client.get('/movies')
assert rv.status_code == 200
assert func.called

fetch_movies_patch.stop()


def test_movies_api_with_error(client):
"""
调用豆瓣api出错的情况
"""
fetch_movies_patch = mock.patch('utils.fetch_movies')

func = fetch_movies_patch.start()
func.return_value = None

rv = client.get('/movies')
assert rv.status_code == 500
assert func.called

fetch_movies_patch.stop()

这里使用 python 的 mock 模块来模拟让某个函数返回固定的结果。

完整的代码请访问: https://github.com/wusuopu/flask-test-example

HAProxy+Nginx+gunicorn获取真实ip

之前在部署在 nginx + uwsgi 应用时都是通过如下方法来获取真实的客户端ip的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
upstream app_server {
server unix:///tmp/gunicorn.sock fail_timeout=0;
}

server {
listen 80;
server_name localhost;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;

proxy_pass http://app_server;
}
}

这样在 uwsgi 的应用程序中只需要读取 http headers 中的 X-Forwarded-For 字段即可。

但是最近由于运维架构的是采用 haproxy + nginx + uwsgi 是形式,导致了在 uwsgi 应用程序中获取到的 ip 都是 haproxy 的。
为了要获取到真实的ip地址,需要由 haproxy 将 ip 传给 nginx,再由 nginx 传给 uwsgi。
在网上搜索了半天 haproxy 的相关配置,感觉太复杂了。因此还是决定从 nginx 入手。

经过实验将 nginx 的配置改为如下即可:

1
2
3
proxy_set_header X-Forwarded-For $http_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;

在Docker中运行X11程序

如果是Linux系统的话,相对比较方便。先构建一个带gui各应的docker image,然后将
本机的X11 sock挂载到container内,
docker run -ti --rm -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix chrome

如果是mac OS系统的话,相对麻烦一些。

1.安装所需的软件:

1
2
brew install socat
brew cask install xquartz

2.依次运行刚刚安装的两个程序:

1
2
socat TCP-LISTEN:6000,reuseaddr,fork UNIX-CLIENT:\"$DISPLAY\"
open -a XQuartz

3.设置X11, XQuartz -> Preference -> Security -> Allow connections from network clients

4.docker run --rm -e DISPLAY=ifconfig | grep “inet\ “ | tail -1 | cut -d “ “ -f 2:0 chrome

对于已经启动了的container,可以在container内执行命令 export DISPLAY=<ip>:0 来设置 DISPLAY 从而使用本机的 X11 服务。

倘若当前你的 mac OS 没有连接网络,那么可能就没有ip地址供container内访问。
此时也许可以执行命令: sudo ifconfig lo0 alias 10.200.10.1/24 来手动设置一个ip。
然后在container内再设置 export DISPLAY=10.200.10.1:0