0%

域名解析

主机记录 记录类型 记录值
registry A x.x.x.x
registry-ui A x.x.x.x

安装 Docker Compose

Install Docker Compose

1
yum install -y docker-compose-plugin

配置 Nginx 反向代理

SSL Configuration Generator

Getting Started with NGINX (Part 1): Installation and Basic Setup

Getting Started with NGINX (Part 2): Advanced Configuration

Getting Started with NGINX (Part 3): Enable TLS/SSL for HTTPS

Getting Started with NGINX (Part 4): TLS Deployment Best Practices

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
.
├── certs
│   ├── dhparam2048.pem
│   ├── domain.crt
│   └── domain.key
└── nginx
   ├── conf.d
   │   ├── <domain>.conf
   │   ├── registry.<domain>.conf
   │   └── registry-ui.<domain>.conf
   ├── docker-compose.yml
   └── nginx.conf

nginx.conf

CSP Blocked Loading of Resources

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
60
61
62
63
64
65
66
67
68
69
70
user nginx;
# Use Multiple Worker Processes
worker_processes auto;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;

keepalive_timeout 65;

include /etc/nginx/conf.d/*.conf;

ssl_certificate /etc/nginx/certs/domain.crt;
ssl_certificate_key /etc/nginx/certs/domain.key;

# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;

ssl_session_timeout 1d;
# about 40000 sessions
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;

# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
ssl_dhparam /etc/nginx/certs/dhparam2048.pem;

# HSTS (ngx_http_headers_module is required) (31536000 seconds)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;

# verify chain of trust of OCSP response using Root CA and Intermediate certs
# ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;

# replace with the IP address of your resolver
resolver 127.0.0.1;

# Disable Content Sniffing
add_header X-Content-Type-Options nosniff;
# Limit or Disable Content Embedding
add_header X-Frame-Options SAMEORIGIN;
# Cross-Site Scripting (XSS) Filter
add_header X-XSS-Protection "1; mode=block";
# Referrer Policy
add_header Referrer-Policy strict-origin-when-cross-origin;
# Content Security Policy
add_header Content-Security-Policy "upgrade-insecure-requests;";

# Disable Server Tokens
server_tokens off;
}

conf.d/<domain>.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server {
# Serve Content Over IPv4 and IPv6
listen 80 default_server;
listen [::]:80 default_server;
server_name _;

location / {
return 301 https://$host$request_uri;
}
}

server {
# Serve Content Over IPv4 and IPv6
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name _;

location / {
return 403;
}
}

conf.d/registry.<domain>.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
# Serve Content Over IPv4 and IPv6
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name registry.<domain>;

location / {
proxy_pass https://registry-srv:5000;
proxy_ssl_certificate /etc/nginx/certs/domain.crt;
proxy_ssl_certificate_key /etc/nginx/certs/domain.key;

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

conf.d/registry-ui.<domain>.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
# Serve Content Over IPv4 and IPv6
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name registry-ui.<domain>;

location / {
proxy_pass https://registry-web:443;
proxy_ssl_certificate /etc/nginx/certs/domain.crt;
proxy_ssl_certificate_key /etc/nginx/certs/domain.key;

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

编辑 docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
version: "3.9"

services:
nginx:
image: nginx:1.20.2
ports:
- 80:80
- 443:443
volumes:
- /root/certs/:/etc/nginx/certs/
- /mnt/nginx-log/:/var/log/nginx/
- ./nginx.conf:/etc/nginx/nginx.conf
- ./conf.d/:/etc/nginx/conf.d/
restart: always
networks:
- registry-network

networks:
registry-network:
external: true

运行 Nginx 反向代理

1
2
3
docker compose up -d
# https://registry.<domain>/v2/busybox/tags/list
# https://registry-ui.<domain>/

更进一步

更新防火墙规则

来源 协议 端口
0.0.0.0/0 TCP 443
0.0.0.0/0 TCP 80

使用 user-defined bridge networks

Use bridge networks

1
docker network create -d bridge registry-network

使用 --expose 替换 -port

expose

Expose ports without publishing them to the host machine - they’ll only be accessible to linked services.

Publish or expose port (-p, --expose)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
docker run -d \
--expose 5000 \
--restart=always \
--name registry-srv \
-v "$(pwd)"/auth:/auth \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
-v /root/certs:/certs \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
-v /mnt/registry:/var/lib/registry \
--network=registry-network \
registry:2
1
2
3
4
5
6
7
8
9
10
11
docker run -d \
--name registry-web \
-e ENV_USE_SSL=yes \
-e ENV_DOCKER_REGISTRY_HOST=registry-srv \
-e ENV_DOCKER_REGISTRY_PORT=5000 \
-e ENV_DOCKER_REGISTRY_USE_SSL=1 \
-v /root/certs/domain.crt:/etc/apache2/server.crt:ro \
-v /root/certs/domain.key:/etc/apache2/server.key:ro \
--expose 443 \
--network=registry-network \
konradkleine/docker-registry-frontend:v2

SSL Server Test

https://www.ssllabs.com/ssltest/

A+

安装 Docker Engine

Install Docker Engine on CentOS

1
2
3
4
5
6
7
8
9
10
sudo yum install -y yum-utils
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y docker-ce docker-ce-cli containerd.io
sudo systemctl start docker
sudo docker run hello-world

sudo systemctl enable docker.service
sudo systemctl enable containerd.service

Registry as a pull through cache

1
2
3
4
5
6
7
8
9
10
cat <<EOF > /etc/docker/daemon.json
{
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com"
]
}
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker.service

注册域名

申请 SSL 证书

FreeSSL.cn

运行 Registry Server

Deploy a registry server

1
2
3
4
5
6
7
mkdir auth
docker run \
--entrypoint htpasswd \
httpd:2 -Bbn testuser testpassword > auth/htpasswd

mkdir certs
# copy domain.crt and domain.key to here
1
2
3
4
5
6
7
8
9
10
11
12
13
docker run -d \
-p 5000:5000 \
--restart=always \
--name registry-srv \
-v "$(pwd)"/auth:/auth \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
-v "$(pwd)"/certs:/certs \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
-v /mnt/registry:/var/lib/registry \
registry:2

拷贝镜像到 Registry Server

1
2
3
4
5
6
7
# 需要将 <domain> 替换为申请的域名
docker pull busybox:latest
docker tag busybox:latest <domain>:5000/busybox:latest
docker login <domain>:5000
docker push <domain>:5000/busybox:latest
# Registry API
curl https://testuser:testpassword@<domain>:5000/v2/busybox/tags/list

运行 Registry UI

docker-registry-frontend

1
2
3
4
5
6
7
8
9
10
11
12
13
docker run -d \
--name registry-web \
--link registry-srv:registry \
-e ENV_USE_SSL=yes \
-e ENV_DOCKER_REGISTRY_HOST=registry \
-e ENV_DOCKER_REGISTRY_PORT=5000 \
-e ENV_DOCKER_REGISTRY_USE_SSL=1 \
-v "$(pwd)"/certs/domain.crt:/etc/apache2/server.crt:ro \
-v "$(pwd)"/certs/domain.key:/etc/apache2/server.key:ro \
-p 8000:443 \
-p 0:80 \
konradkleine/docker-registry-frontend:v2
# https://<domain>:8000/

添加防火墙规则

来源 协议 端口
0.0.0.0/0 TCP 5000
0.0.0.0/0 TCP 8000

ClickHouse

clickhouse/clickhouse-server

1
docker run -d -p 8123:8123 -p 9000:9000 -v /mnt/clickhouse-data:/var/lib/clickhouse/ -v /mnt/clickhouse-log:/var/log/clickhouse-server/ -e CLICKHOUSE_DB=smartinvest -e CLICKHOUSE_USER=smartinvest -e CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 -e CLICKHOUSE_PASSWORD=smartinvest.123 --name smart-invest-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server:22.3-alpine

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sudo dnf groupinstall -y 'development tools'
sudo dnf install -y bzip2-devel expat-devel gdbm-devel ncurses-devel openssl-devel readline-devel sqlite-devel tk-devel xz-devel zlib-devel wget

cd /usr/local/src
wget https://www.python.org/ftp/python/3.8.13/Python-3.8.13.tgz
tar -zxvf Python-3.8.13.tgz

# ModuleNotFoundError: No module named 'xxx'
yum install -y openssl-devel bzip2-devel libffi-devel

# compile
cd Python-3.8.13/
./configure --prefix=/usr/local/python --with-ssl
./configure --enable-optimizations
make && make install

# upgrade pip
pip3 install --upgrade pip

ln -sf /usr/local/bin/python3 /usr/bin/python

AKShare

AKTools

Welcome to AKShare’s Online Documentation!

1
2
3
pip3 install --upgrade setuptools
pip3 install aktools -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host=mirrors.aliyun.com --upgrade
nohup python3 -m aktools > nohup.log 2>&1 &

Spring Cloud Data Flow

Batch-only Mode

10.2.2. MariaDB

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
# download Spring Cloud Data Flow Server
wget https://repo.spring.io/release/org/springframework/cloud/spring-cloud-dataflow-server/2.9.2/spring-cloud-dataflow-server-2.9.2.jar

# setup mariadb
docker run -d --name task-db --env MARIADB_RANDOM_ROOT_PASSWORD="1" --env MARIADB_DATABASE=task --env MARIADB_USER=task --env MARIADB_PASSWORD=task.123 -v /mnt/task-db-data:/var/lib/mysql -p 3306:3306 mariadb:10.7

# setup jdk & maven
yum install -y java-17-openjdk maven
sudo alternatives --config java

# setup environment variables
export SPRING_CLOUD_DATAFLOW_FEATURES_STREAMS_ENABLED=false
export SPRING_CLOUD_DATAFLOW_FEATURES_SCHEDULES_ENABLED=false
export SPRING_CLOUD_DATAFLOW_FEATURES_TASKS_ENABLED=true

# running the server
# http://<ip>:9393/dashboard
nohup java -jar spring-cloud-dataflow-server-2.9.2.jar \
--spring.datasource.url=jdbc:mariadb://localhost:3306/task?useMysqlMetadata=true \
--spring.datasource.username=task \
--spring.datasource.password=task.123 \
--spring.datasource.driver-class-name=org.mariadb.jdbc.Driver \
--spring.datasource.initialization.mode=always \
--spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDB102Dialect \
> ./nohup.log 2>&1 &

Register and Launch a Spring Cloud Task & Spring Batch

Batch Development

Multiple DataSources Sample Task

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
60
61
62
# build project
git clone https://github.com/csongyu/smart-invest.git

# install to local maven repository
mvn install:install-file -DgroupId=xyz.csongyu -DartifactId=smart-invest-task -Dversion=1.0.0 -Dpackaging=jar -DgeneratePom=true -Dfile=smart-invest-task-1.0.0.jar
mvn install:install-file -DgroupId=xyz.csongyu -DartifactId=smart-invest-job -Dversion=1.0.0 -Dpackaging=jar -DgeneratePom=true -Dfile=smart-invest-job-1.0.0.jar

# fund-name-em
# register applications
curl --location --request POST 'http://127.0.0.1:9393/apps/task/task-fund-name-em' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'uri=maven://xyz.csongyu:smart-invest-task:1.0.0' \
--data-urlencode 'force=true'
curl --location --request POST 'http://127.0.0.1:9393/apps/task/task-initialize-schema-fund-name-em' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'uri=maven://xyz.csongyu:smart-invest-task:1.0.0' \
--data-urlencode 'force=true'
curl --location --request POST 'http://127.0.0.1:9393/apps/task/job-fund-name-em' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'uri=maven://xyz.csongyu:smart-invest-job:1.0.0' \
--data-urlencode 'force=true'
# create task
curl --location --request POST 'http://127.0.0.1:9393/tasks/definitions' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'name=fne' \
--data-urlencode 'definition=task-fund-name-em && task-initialize-schema-fund-name-em && job-fund-name-em'

# fund-open-fund-info-em / unit-net-asset-value
# register applications
curl --location --request POST 'http://127.0.0.1:9393/apps/task/task-fund-open-fund-info-em-unit-net-asset-value' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'uri=maven://xyz.csongyu:smart-invest-task:1.0.0' \
--data-urlencode 'force=true'
curl --location --request POST 'http://127.0.0.1:9393/apps/task/task-initialize-schema-fund-open-fund-info-em-unit-net-asset-value' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'uri=maven://xyz.csongyu:smart-invest-task:1.0.0' \
--data-urlencode 'force=true'
curl --location --request POST 'http://127.0.0.1:9393/apps/task/job-fund-open-fund-info-em-unit-net-asset-value' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'uri=maven://xyz.csongyu:smart-invest-job:1.0.0' \
--data-urlencode 'force=true'
# create task
curl --location --request POST 'http://127.0.0.1:9393/tasks/definitions' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'name=fofiem' \
--data-urlencode 'definition=task-fund-open-fund-info-em-unit-net-asset-value && task-initialize-schema-fund-open-fund-info-em-unit-net-asset-value && job-fund-open-fund-info-em-unit-net-asset-value'

# fund-open-fund-daily-em
# register applications
curl --location --request POST 'http://127.0.0.1:9393/apps/task/task-fund-open-fund-daily-em' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'uri=maven://xyz.csongyu:smart-invest-task:1.0.0' \
--data-urlencode 'force=true'
curl --location --request POST 'http://127.0.0.1:9393/apps/task/job-fund-open-fund-daily-em' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'uri=maven://xyz.csongyu:smart-invest-job:1.0.0' \
--data-urlencode 'force=true'
# create task
curl --location --request POST 'http://127.0.0.1:9393/tasks/definitions' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'name=fofde' \
--data-urlencode 'definition=task-fund-open-fund-daily-em && job-fund-open-fund-daily-em'

Scheduling

Batch Job Scheduling

Task Executions

Restrict Size of the Buffer Cache in Linux

1
2
3
4
5
6
7
8
9
10
# restrict buffer/cache
sync
echo 3 > /proc/sys/vm/drop_caches
echo 500 > /proc/sys/vm/vfs_cache_pressure

# build project
git clone https://github.com/csongyu/smart-invest.git

# launch application
nohup java -jar smart-invest-scheduler-1.0.0.jar > ./scheduler.log 2>&1 &

Wildcard Certificate

generate csr and key file

1
2
3
4
export DOMAIN="csongyu.xyz"
export PASSCODE="changeit"
openssl genrsa -des3 -passout "pass:${PASSCODE}" -out "${DOMAIN}_private.key" 2048
openssl req -new -key "${DOMAIN}_private.key" -passin "pass:${PASSCODE}" -out "${DOMAIN}.csr" -subj "/C=CN/ST=Beijing/L=Beijing/O=csongyu/OU=csongyu/CN=*.${DOMAIN}"

verify the csr

1
openssl req -text -noout -verify -in "${DOMAIN}.csr"

extract the private key

1
openssl rsa -in "${DOMAIN}_private.key -check

SAN Certificate

In the SAN certificate, you can have multiple complete CN.

san.cnf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = req_ext
prompt = no
[ req_distinguished_name ]
countryName = CN
stateOrProvinceName = Beijing
localityName = Beijing
organizationName = csongyu
commonName = *.csongyu.xyz
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = www.csongyu.icu
DNS.2 = www.csongyu.tech

generate csr and key file

1
openssl req -out csongyu.xyz.csr -newkey rsa:2048 -nodes -keyout csongyu.xyz_private.key -config san.cnf

verify the csr

1
openssl req -text -noout -verify -in "csongyu.xyz.csr" | grep DNS

csongyu/byte-to-string

Text Data

1
2
3
4
5
6
7
8
9
10
11
public class CharacterByteToStringServiceImpl implements ByteToStringService {
@Override
public String encode(final byte[] bytes, final Charset charset) {
return new String(bytes, charset);
}

@Override
public byte[] decode(final String str, final Charset charset) {
return str.getBytes(charset);
}
}

Binary Data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BinaryByteToStringServiceImpl implements ByteToStringService {
@Override
public String encode(final byte[] bytes, final Encoding encoding) {
return switch (encoding) {
case HEX -> Hex.encodeHexString(bytes);
case BASE_64 -> Base64.getEncoder().encodeToString(bytes);
case ISO_8859_1 -> new String(bytes, StandardCharsets.ISO_8859_1);
};
}

@Override
public byte[] decode(final String str, final Encoding encoding) throws DecoderException {
return switch (encoding) {
case HEX -> Hex.decodeHex(str);
case BASE_64 -> Base64.getDecoder().decode(str);
case ISO_8859_1 -> str.getBytes(StandardCharsets.ISO_8859_1);
};
}
}

错误场景

1
2
3
4
5
6
7
8
9
10
// UTF-8 编码的字节 unique byte sequence 和字符 unique character sequence 不能一一对应
// 使用 Hex、Base64、ISO-8859-1 编码处理 Binary Data 场景
@Test
public void givenBinaryData_whenUseUtf8ConvertBytesToStringThenBackToBytes_thenIncorrect() throws IOException {
final byte[] source = Files.readAllBytes(Paths.get("src/test/resources/profile.png"));
// byteToStringService = new CharacterByteToStringServiceImpl();
final String str = this.byteToStringService.encode(source, StandardCharsets.UTF_8);
final byte[] target = this.byteToStringService.decode(str, StandardCharsets.UTF_8);
assertNotEquals(Arrays.toString(source), Arrays.toString(target));
}