> tar -zxf redis-5.0.5.tar.gz -C /data/redis/
> cd /data/redis/redis-5.0.5/
> make
> vim redis.conf :
bind 0.0.0.0(远程机器访问需要)
port 6377
protected-mode no(是否保护模式,如果需要远程机器访问则no)
daemonize yes(是否后台进程启动)
requirepass pwd123456(设置redis访问密码)
database 16(修改db数目,默认16个)
> ./src/redis-server ./redis.conf
> ./src/redis-client -h 127.0.0.1 -p 6377
127.0.0.1:6377> KEYS *
(error) NOAUTH Authentication required.
127.0.0.1:6377> AUTH pwd123456
ok
centos下:
> cp /data/redis/redis-5.0.5/utils/redis_init_script /etc/init.d/redis
> cd /etc/init.d
> vim redis (只改动几个属性配置,其它不变,以后redis升级只需要修改REDIS_DIR即可)
...
REDIS_DIR=/data/redis/redis-5.0.5
REDISPORT=6379
EXEC=$REDIS_DIR/src/redis-server
CLIEXEC=$REDIS_DIR/src/redis-cli
PIDFILE=/var/run/redis_${REDISPORT}.pid
CONF="$REDIS_DIR/redis.conf"
...
> chmod +x redis (默认文件复制过去有可执行权限可以不用执行)
> chkconfig redis on
> service redis start
> service redis stop
> chkconfig --list | grep redis 查看redis服务是否有自启动开关
> ./src/redis-client
# 查询server信息
127.0.0.1:6379> INFO
# 查询client连接列表
127.0.0.1:6379> CLIENT LIST
# 选择db
127.0.0.1:6379> SELECT 5
# 查看db5下所有键
127.0.0.1:6379[5]> KEYS
# 获取KEY值
127.0.0.1:6379[5]> GET "My_CACHE_USER:N1:M1"
# 查看失效时间(秒)
127.0.0.1:6379[5]> TTL "My_CACHE_USER:N1:M1"
# 删除key
127.0.0.1:6379[5]> DEL "My_CACHE_USER:N1:M1"
> ./src/redis-client -n 5 --scan --pattern My_CACHE_USER* | xargs redis-cli -n 5 del
如果有密码:
127.0.0.1:6379> SELECT 5
127.0.0.1:6379> AUTH yourpasswd
清除当前select的DB 5 下的所有数据:
127.0.0.1:6379> SELECT 5
127.0.0.1:6379> FLUSHDB
清除redis所有DB的数据:
127.0.0.1:6379> FLUSHALL
关闭所有客户端停止服务端(源码安装的redis只能通过该方式停止redis服务)
127.0.0.1:6379> SHUTDOWN
自动安装(CentOS推荐):
http://nginx.org/en/linux_packages.html#RHEL-CentOS
开机自启动(centos7)
> systemctl enable nginx.service
> systemctl status nginx
# 查看是否支持多个https到一台机器
nginx -V
TLS SNI support enabled
# www域名跳转到非www域名
server {
listen 80;
server_name www.test.com;
return 301 https://test.com$request_uri;
}
server {
listen 80;
listen 443 ssl;
server_name test.com;
ssl_certificate /data/webserver/letsencrypt/certbotcfg/live/test.com/fullchain.pem;
ssl_certificate_key /data/webserver/letsencrypt/certbotcfg/live/test.com/privkey.pem;
ssl_trusted_certificate /data/webserver/letsencrypt/certbotcfg/live/test.com/chain.pem;
ssl_session_tickets on;
location ^~ /.well-known/acme-challenge/ {
alias /data/webserver/letsencrypt/test.com/www/challenges/;
try_files $uri =404;
}
if ($scheme = http) {
rewrite ^/(.*)$ https://test.com/$1 permanent;
}
location / {
root /data/nginx/html;
index index.html index.htm;
if_modified_since before;
etag off;
access_log off;
expires 1d;
}
}
# 强制跳转HTTPS的(301永久:permanent,302临时:redirect)
server{
listen 80;
...
location / {
rewrite ^/(.*)$ https://mytest.com/$1 permanent;
}
...
}
server{
listen 443;
...
}
# 同时支持HTTP和HTTPS的
server{
listen 80;
listen 443;
...
location / {
rewrite ^/(.*)$ https://mytest.com/$1 permanent;
}
...
}
#允许请求head参数里使用下划线
> vim /etc/nginx/nginx.conf
http {
...
underscores_in_headers on;
...
}
# 允许最大文件上传1M
> vim /etc/nginx/nginx.conf
http {
...
client_max_body_size 1m;
...
}
> vim /etc/nginx/conf.d/www.mytest.com.conf
server {
listen 80;
listen 443 ssl;
server_name www.mytest.com;
error_page 413 =200 @413;
location @413 {
default_type application/json;
return 413 '{"code":"EF0001","msg":"文件超过1M"}';
}
location / {
root /data/www/dist;
index index.html index.htm;
if_modified_since before;
access_log off;
etag off;
expires 1h;
}
...
}
#禁用ip访问
> vim /etc/nginx/conf.d/default.conf
server{
listen 80 default;
server_name _;
return 403;
}
> vim /etc/nginx/conf.d/mytest.com.conf:
server{
listen 80;
#listen 443 ssl;
server_name test.com www.test.com;
error_page 502 =502 @502;
error_page 503 =503 @503;
error_page 504 =504 @504;
#if ($scheme = http) {
# rewrite ^/(.*)$ https://www.test.com/$1 permanent;
#}
location @502 {
default_type application/json;
return 502 '{"code":"502","msg":"Bad Gateway","success":false,"result":null}';
}
location @504 {
default_type application/json;
return 504 '{"code":"504","msg":"Gateway Time-out","success":false,"result":null}';
}
location @503 {
default_type application/json;
return 503 '{"code":"503","msg":"Service Temporarily Unavailable","success":false,"result":null}';
}
location ~ ^/(WEB-INF)/ {
deny all;
}
#location ~ \.(htm|html|gif|jpg|jpeg|png|ico|rar|css|js|zip|txt|flv|swf|doc|ppt|xls|pdf)$ {}
location / {
root /data/www/myhtml/dist;
index index.html index.htm;
if_modified_since before;
access_log off;
etag off;
expires 1d;
}
location /test/m-demo {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 10s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
}
}
#选择操作系统与http服务,比如ubuntu16-nginx
https://certbot.eff.org/lets-encrypt/ubuntuxenial-nginx
$ sudo apt-get update
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install python-certbot-nginx
# centos6-nginx
https://certbot.eff.org/lets-encrypt/centos6-nginx
# 多个域名共用生成到/data/webserver/letsencrypt/live/my-test目录下,公用名是第一个-d的域名(新加域名从这里开始即可)
# 生成证书-ubuntu
> certbot --cert-name my-test -d mytest.com -d b.com -d c.com --config-dir /data/webserver/letsencrypt --register-unsafely-without-email --nginx certonly
# 生成证书-centos6,
> ./certbot-auto --cert-name my-test -d mytest.com -d b.com -d c.com --config-dir /data/webserver/letsencrypt --register-unsafely-without-email --nginx certonly
# 生成证书-centos7
> yum install certbot
> certbot --cert-name my-test -d mytest.com -d b.com -d c.com --config-dir /data/webserver/letsencrypt/certbotcfg --register-unsafely-without-email --nginx certonly
(
如果出现ImportError: No module named 'requests.packages.urllib3'
尝试运行> pip install --upgrade --force-reinstall 'requests==2.6.0' urllib3
如果出现ImportError: cannot import name UnrewindableBodyError
尝试重装urllib3> pip2 uninstall urllib3 && pip2 install urllib3
如果出现The requested nginx plugin does not appear to be installed
尝试安装> yum install python3-certbot-nginx 或 yum install python2-certbot-nginx
)
a,多个域名逗号分隔1,2,4
#出现下面提示成功,并且证书地址复制到nginx:
Congratulations! Your certificate and chain have been saved at:
> vim /etc/nginx/conf.d/mytest.com.conf
server{
listen 80;
listen 443 ssl;
server_name mytest.com www.mytest.com;
ssl_certificate /etc/letsencrypt/live/mytest.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mytest.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/mytest.com/chain.pem;
ssl_session_tickets on;
if ($scheme = http) {
rewrite ^/(.*)$ https://mytest.com/$1 permanent;
}
location / {
root /data/www/mytest.com/dist;
index index.html index.htm;
if_modified_since before;
etag off;
access_log off;
expires 1d;
}
location /backend {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 10s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
access_log off;
}
location ~ ^/(WEB-INF)/ {
deny all;
}
....
}
> nginx -s reload
#重新生成之前的所有证书,执行dry-run模拟测试是否成功:
> (ubuntu) certbot renew --config-dir /data/webserver/letsencrypt --dry-run
> (centos6) ./certbot-auto renew --config-dir /data/webserver/letsencrypt --dry-run
> (centos7) certbot renew --config-dir /data/webserver/letsencrypt/certbotcfg --dry-run
(
如果出现'ascii' codec can't decode byte 0xe6 in position...
则先执行检查目录下是否有非ASCII字符,去掉或修改(中文转ASCII:https://tool.oschina.net/encode?type=3):
> grep -r -P '[^\x00-\x7f]' /data/webserver/letsencrypt /etc/nginx
)
# 成功后加入真正的定时任务执行
> certbot renew --config-dir /data/webserver/letsencrypt
> vim /data/webserver/letsencrypt/flush_certbot.sh
#!/bin/bash
(ubuntu:)
certbot renew --config-dir /data/webserver/letsencrypt
(centos6:)
/data/webserver/letsencrypt/certbot-auto renew --config-dir /data/webserver/letsencrypt
(centos7:)
certbot renew --config-dir /data/webserver/letsencrypt/certbotcfg
nginx -s reload
> crontab -e (无效则使用vim /etc/crontab)
0 0 1 * * root /data/webserver/letsencrypt/flush_certbot.sh >/dev/null 2>&1
0 23 15 * *(每月15号晚上23点0分执行)
> vim /etc/nginx/conf.d/my.test.com.conf
server {
listen 80;
#listen 443 ssl;
server_name my.test.com;
#ssl_certificate /data/webtest/letsencrypt/my.test.com/ssl/chained.pem;
#ssl_certificate_key /data/webtest/letsencrypt/my.test.com/ssl/domain.key;
ssl_session_tickets on;
location ^~ /.well-known/acme-challenge/ {
alias /data/webtest/letsencrypt/my.test.com/www/challenges/;
try_files $uri =404;
}
#if ($scheme = http) {
# rewrite ^/(.*)$ https://my.test.com/$1 permanent;
#}
location / {
root /data/www/static;
index index.html index.htm
if_modified_since before;
etag off;
access_log off;
expires 1d;
}
location /backend/m-test {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 10s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
access_log off;
}
location ~ ^/(WEB-INF)/ {
deny all;
}
}
> nginx -s reload
> vim /usr/local/aegis/globalcfg/letsencrypt.sh (使用 ./letsencrypt.sh my.test.com)
#!/bin/bash
# /etc/ssl/openssl.cnf(ubuntu) or /etc/pki/tls/openssl.cnf (centos)
if [ $# -eq 0 ];then
echo "domain is null!!"
exit 0
fi
domain=$1
letsencrypt_dir=/data/webtest/letsencrypt
openssl_cnf=/etc/pki/tls/openssl.cnf
ssl_dir=$letsencrypt_dir/$domain/ssl
challenges_dir=$letsencrypt_dir/$domain/www/challenges/
mkdir -p $ssl_dir
mkdir -p $challenges_dir
cd $ssl_dir
openssl genrsa 4096 > account.key
openssl ecparam -genkey -name secp384r1 | openssl ec -out domain.key
openssl req -new -sha256 -key domain.key -subj "/" -reqexts SAN -config <(cat $openssl_cnf <(printf "[SAN]\nsubjectAltName=DNS:$domain")) > domain.csr
wget https://raw.githubusercontent.com/diafygi/acme-tiny/master/acme_tiny.py
echo $$challenges_dir
# vim nginx config
# location ^~ /.well-known/acme-challenge/ {
# alias $challenges_dir;
# try_files $uri =404;
# }
python acme_tiny.py --account-key ./account.key --csr ./domain.csr --acme-dir $challenges_dir > ./signed.crt
wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem
cat signed.crt intermediate.pem > chained.pem
wget -O - https://letsencrypt.org/certs/isrgrootx1.pem > root.pem
cat intermediate.pem root.pem > full_chained.pem
echo ssl_certificate $ssl_dir/chained.pem;
echo ssl_certificate_key $ssl_dir/domain.key;
echo ssl_session_tickets on;
# vim nginx config
#ssl_certificate $ssl_dir/chained.pem;
#ssl_certificate_key $ssl_dir/domain.key;
#ssl_session_tickets on;
> vim /etc/nginx/conf.d/my.test.com.conf 去掉ssl注释#
> nginx -s reload
参考:
https://imququ.com/post/letsencrypt-certificate.html
https://imququ.com/post/my-nginx-conf.html
https://github.com/diafygi/acme-tiny
https://www.liaosam.com/use-cron-service-and-certbot-for-renewal-of-letsencrypt-ssl-certificates.html
https://www.vpser.net/build/letsencrypt-certbot.html
> mkdir -p /data/dev/letsencrypt/mytest.com/ssl
> cd /data/dev/letsencrypt/mytest.com/ssl
#创建私钥
> openssl genrsa 4096 > account.key
#创建 CSR 文件
> openssl ecparam -genkey -name secp384r1 | openssl ec -out domain.key
> openssl req -new -sha256 -key domain.key -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:mytest.com,DNS:www.mytest.com")) > domain.csr
(
如果出现cat: /etc/ssl/openssl.cnf: 没有那个文件或目录,
则先find / -name openssl.cnf,再替换命令里的openssl.cnf文件目录(centos:/etc/pki/tls/openssl.cnf)
)
# acme-tiny
> wget https://raw.githubusercontent.com/diafygi/acme-tiny/master/acme_tiny.py
> mkdir -p /data/dev/letsencrypt/mytest.com/www/challenges
> vim /etc/nginx/conf.d/mytest.com.conf
server {
listen 80;
server_name mytest.com www.mytest.com;
location ^~ /.well-known/acme-challenge/ {
alias /data/dev/letsencrypt/mytest.com/www/challenges/;
try_files $uri =404;
}
#if ($scheme = http) {
# rewrite ^/(.*)$ https://pay.dmpzg.com/$1 permanent;
#}
location / {
rewrite ^/(.*)$ https://mytest.com/$1 permanent;
#rewrite ^(.*)$ https://$host$1 permanent;(当server_name为多个域名的时候)
}
}
server {
#listen 443 default ssl;
listen 443 ssl;
server_name mytest.com www.mytest.com;
ssl on;
#ssl_certificate /data/dev/letsencrypt/mytest.com/ssl/chained.pem;
#ssl_certificate_key /data/dev/letsencrypt/mytest.com/ssl/domain.key;
ssl_session_tickets on;
#location ~ \.(htm|html|gif|jpg|jpeg|png|ico|rar|css|js|zip|txt|flv|swf|doc|ppt|xls|pdf)$ {
location / {
root /data/project-frontend/dist;
index index.html index.htm
access_log off;
expires 1d;
}
location /myproject/m-test {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location ~ ^/(WEB-INF)/ {
deny all;
}
}
> nginx -s reload
# 获取签名证书
> python acme_tiny.py --account-key ./account.key --csr ./domain.csr --acme-dir /data/dev/letsencrypt/mytest.com/www/challenges/ > ./signed.crt
(
如果出现ImportError: No module named argparse,
则需要安装python-argparse模块
yum install python-argparse 或者 apt-get install python-argparse
)
# 安装证书
> wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem
> cat signed.crt intermediate.pem > chained.pem
# 为了后续启用OCSP Stapling把根证书和中间证书合在一起
> wget -O - https://letsencrypt.org/certs/isrgrootx1.pem > root.pem
> cat intermediate.pem root.pem > full_chained.pem
> vim /etc/nginx/conf.d/mytest.com.conf
(
去掉证书目录#注释
ssl_certificate
ssl_certificate_key
)
> nginx -s reload
由于证书签发只有90天,到期后需更新证书脚本(注意:每个域名每个证书每周只能签发限制5次,不要多次刷新证书,否则会出现ERR_SSL_PROTOCOL_ERROR)
> vim /data/dev/letsencrypt/mytest.com/flush_cert.sh:
#!/bin/bash
domain=mytest.com
cd /data/dev/letsencrypt/$domain/ssl
python acme_tiny.py --account-key ./account.key --csr ./domain.csr --acme-dir /data/dev/letsencrypt/$domain/www/challenges/ > ./signed.crt || exit
wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem
cat signed.crt intermediate.pem > chained.pem
nginx -s reload
> chmod 700 /data/dev/letsencrypt/mytest.com/flush_cert.sh
使用crontab定时任务每月自动更新证书:
> crontab -e (无效则使用vim /etc/crontab)
0 0 1 * * root /data/dev/letsencrypt/mytest.com/flush_cert.sh >/dev/null 2>&1
使用undertow时文件上传必须设置如下值,否则parse multipart error
spring.servlet.multipart.location=/data/tmp
test目录的包路径需要完全脱离src/main/java:
如 src/main/java下包为com.github.mydemo
则 src/test/java下包为test.github.mydemo或任意与主包不同的包路径即可
这种模式下是完全脱离src/main里的自动注解,要么需要独立写组件,
要么使用@Import加载main里组件且不能过度复杂依赖,否则需要@Import很多依赖类,
好处在于是独立的环境加载启动测试快,如果模块依赖比较复杂建议使用第二个模式
TestSpringbootMain.java:
package test.github.mydemo;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringbootTestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestSpringbootMain{
}
SpringbootTestApplication.java:
package test.github.mydemo;
@SpringBootApplication
public class SpringbootTestApplication {
}
test目录的包路径需要保持与src/main/java一致:
如 src/main/java下包为com.github.mydemo
则 src/test/java下包也必须以com.github.mydemo开始
这种模式下是依赖src/main/java里组件的,适合复杂依赖场景的单元测试,避免模式1下重复写太多依赖
TestSpringbootMain.java:
package com.github.mydemo;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringbootTestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestSpringbootMain{
}
由于在一个包路径下会启动主@SpringBootApplication文件,此时需要排除,避免双重配置
SpringbootTestApplication.java:
package com.github.mydemo;
@SpringBootApplication
@ComponentScan(excludeFilters = {
#这里可以排除不需要进入单元测试的组件,比如定时任务或其它不需要测试的组件
@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {MainApplication.class,XxxComponent.class}),
})
#其它这里可以和MainApplication里保持一致,比如启用事务,JPA审计之类的测试所必须依赖的注解
//@EnableTransactionManagement
//@EnableJpaAuditing
public class SpringbootTestApplication {
}
TestSpringbootMain.java:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringbootTestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestSpringbootMain{
}
SpringbootTestApplication.java:
@SpringBootConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
#按需手动添加XxxxAutoConfiguration.class
@Import({ServletWebServerFactoryAutoConfiguration.class})#web必须
public class SpringbootTestApplication {
}
/usr/local/deploy/xxx/my-boot-project
部署目录结构:
deploy/xxx/my-boot-project
deploy/
xxx/
my-boot-project/
config/
application-mytest.yml
log4j2.xml
my-boot-project-0.0.1.conf
my-boot-project-0.0.1.jar
logs/
start.sh
stop.sh
启动脚本:
> vim start.sh
#!/bin/bash
cd /usr/local/deploy/xxx/my-boot-project
nohup /usr/bin/java -jar -Djava.security.egd=file:/dev/urandom my-boot-project-0.0.1.jar --spring.profiles.active=mytest > ./logs/my-boot-project.log 2>&1 &
exit 0
停止脚本:
> vim stop.sh
#!/bin/bash
cd /usr/local/deploy/xxx/my-boot-project
kill `pgrep -f my-boot-project-0.0.1-SNAPSHOT.jar` 2>/dev/null
ps -ef |grep my-boot-project-0.0.1-SNAPSHOT.jar | grep -v grep |awk '{print $2}'|xargs kill -9 1>/dev/null 2>&1
exit 0
日志config:
> vim config/application-mytest.yml
logging:
config: config/log4j2.xml
level:
root: DEBUG
com.xxx.demo: DEBUG
xboot:
auth:
log-analysis:
enabled: true #是否开启json日志分析
日志目录:
> vim config/log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<properties>
<property name="project-name">service-my-boot</property>
<property name="logfile-dir">./logs/</property>
<property name="console-pattern">%d{yyyyMMdd HH:mm:ss.SSS} [%level:%thread] %logger{36} - %msg%n</property>
<property name="logfile-pattern">%d{yyyyMMdd HH:mm:ss.SSS} [%level:%thread] %logger - %msg%n</property>
</properties>
<Appenders>
<Console name="stdout" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${console-pattern}" />
</Console>
<RollingFile name="${project-name}" fileName="${logfile-dir}${project-name}.log" filePattern="${logfile-dir}${project-name}.%d{yyyyMMdd}-%i.log.gz" bufferedIO="true" immediateFlush="true">
<PatternLayout pattern="${logfile-pattern}" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="1 GB" />
</Policies>
</RollingFile>
<RollingFile name="${project-name}-info" fileName="${logfile-dir}${project-name}-info.log" filePattern="${logfile-dir}${project-name}-info.%d{yyyyMMdd}-%i.log.gz" bufferedIO="true" immediateFlush="false">
<PatternLayout pattern="${logfile-pattern}" />
<filters>
<ThresholdFilter level="FATAL" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</filters>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="1 GB" />
</Policies>
</RollingFile>
<RollingFile name="${project-name}-error" fileName="${logfile-dir}${project-name}-error.log" filePattern="${logfile-dir}${project-name}-error.%d{yyyyMMdd}-%i.log.gz" bufferedIO="true" immediateFlush="true">
<PatternLayout pattern="${logfile-pattern}" />
<filters>
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
</filters>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="1 GB" />
</Policies>
</RollingFile>
<!-- analysis-log-file -->
<RollingFile name="XbootLogAnalysis-File" fileName="${logfile-dir}${project-name}-x-analysis.log" filePattern="${logfile-dir}${project-name}-x-analysis.%d{yyyyMMdd}-%i.log.gz" bufferedIO="true" immediateFlush="true">
<PatternLayout pattern="%msg%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="1 GB" />
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="XbootLogAnalysis" level="info" additivity="true">
<AppenderRef ref="XbootLogAnalysis-File" />
</Logger>
<Root level="info">
<AppenderRef ref="stdout" />
<AppenderRef ref="${project-name}" />
<AppenderRef ref="${project-name}-info" />
<AppenderRef ref="${project-name}-error" />
</Root>
</Loggers>
</Configuration>
部署目录结构:
deploy/xxx/spring-boot-project
deploy/xxx/run
deploy
xxx
run
config
application-prod.properties
spring-boot-project-0.0.1.conf
spring-boot-project-0.0.1.jar
log4j2_test.xml
log4j2_prod.xml
start.sh
stop.sh
spring-boot-project
src
> cd xxx/spring-boot-project && mvn clean package && mv target/spring-boot-project-0.0.1.jar run/
> vim run/spring-boot-project-0.0.1.conf
LOG_FOLDER=/data/logs
JAVA_HOME="/usr/local/java/jdk8202"
JAVA_OPTS="-Xms250m -Xmx500m -Xmn256m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -Dfile.encoding=UTF8 -Dsun.jnu.encoding=UTF8"
RUN_ARGS="--spring.profiles.active=test --server.port=8080"
> run/log4j2_test.xml
<property name="logfile-dir">/data/logs/xxx/</property>
> vim run/start.sh
#!/bin/bash
cd deploy/xxx/run
nohup ./spring-boot-project-0.0.1.jar >/dev/null 2>&1 &
tail -1f /data/logs/my-boot-prod/my-boot.log | sed '/Undertow started on port/Q'
exit 0
> vim run/stop.sh
#!/bin/bash
cd deploy/xxx/run
kill `pgrep -f spring-boot-project-0.0.1.jar` 2>/dev/null
ps -ef |grep spring-boot-project-0.0.1.jar | grep -v grep |awk '{print $2}'|xargs kill -9 1>/dev/null 2>&1
exit 0
注意:如果是自定义config/application-prod.properties配置,则需要改变日志路径:
logging.config=classpath:log4j2.xml -> logging.config=log4j2.xml
> vim deploy/xxx/deploy.sh
#!/bin/sh
run_dir=deploy/xxx/run
project_dir=deploy/xxx/spring-boot-project
jar_name=spring-boot-project-0.0.1.jar
cd $project_dir
if [ $# -eq 0 ];then
echo "分支名不能为空!"
git branch -la
exit 0
fi
$run_dir/stop.sh
branch_name=test
git pull
git reset --hard origin/$branch_name
git checkout $branch_name
git pull
rm -rf target/*
rm -rf $run_dir/*.jar
mvn clean package -Dmaven.test.skip=true -e
error_code=$?
if [ $error_code -eq 0 ];then
error_msg="jenkins-deploy-backend-success!"
else
error_msg="jenkins-deploy-backend-fail!"
fi
cp -rf $project_dir/target/$jar_name $run_dir/
$run_dir/start.sh
echo $error_msg
curl 'https://oapi.dingtalk.com/robot/send?access_token=xxx' \
-H 'Content-Type: application/json' \
-d '
{"msgtype": "text",
"text": {
"content":"'$error_msg'"
}
}'
exit 0
> vim deploy/xxx/frontend/deploy.sh
project_dir=deploy/xxx/frontend/my-frontend
branch_name=test
cd $project_dir
git pull
git reset --hard origin/$branch_name
git checkout $branch_name
git pull
rm -rf dist/*
npm install
npm run build:test
error_code=$?
if [ $error_code -eq 0 ];then
error_msg="jenkins-deploy-frontend-success!"
else
error_msg="jenkins-deploy-frontend-fail!"
fi
echo $error_msg
curl 'https://oapi.dingtalk.com/robot/send?access_token=xxx' \
-H 'Content-Type: application/json' \
-d '
{"msgtype": "text",
"text": {
"content":"'$error_msg'"
}
}'
exit 0
> vim /data/deploy/mytest/target/config/application-prod.properties
server.port=8080
spring.devtools.restart.enabled=false
logging.config=log4j2_prod.xml
> vim /data/deploy/mytest/target/log4j2_prod.xml
> ln -s /data/deploy/mytest/target/xxx-mytest-0.01.jar /etc/init.d/mytest
> vim /data/deploy/mytest/target/xxx-mytest-0.01.conf:
LOG_FOLDER=/data/logs
JAVA_HOME="/data/java/jdk1.8.0_181"
JAVA_OPTS="-Xms250m -Xmx500m -Xmn256m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -Dfile.encoding=UTF8 -Dsun.jnu.encoding=UTF8"
RUN_ARGS="--spring.profiles.active=prod --server.port=8080"
> chkconfig --del mytest
> chkconfig --add mytest
> chkconfig --list mytest
service mytest start/stop/status
tail -f /data/logs/mytest.log
> vim /etc/systemd/system/mytest.service
[Unit]
Description=service-mytest
After=syslog.target
[Service]
ExecStart=/home/jdk1.8.0_181/bin/java -Dspring.profiles.active=prod -Dserver.port=8080 -jar /home/mytest/target/mytest-0.01.jar
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
> systemctl daemon-reload
启动或停止服务
> systemctl start/stop mytest.service
> vim deploy.sh
#!/bin/sh
mvn_bin=/data/maven/apache-maven-3.5.4/bin/mvn
project_config_dir=/data/deploy/config
project_dir=/data/deploy/mytest
cd $project_dir
git remote prune origin
if [ $# -eq 0 ];then
echo "分支名不能为空!"
git branch -la
exit 0
fi
service mytest stop
branch_name=$1
git pull
git reset --hard origin/$branch_name
git checkout $branch_name
git pull
rm -rf target/*
$mvn_bin clean package -Dmaven.test.skip=true -e
error_code=$?
if [ $error_code -eq 0 ];then
error_msg="部署成功!"
else
error_msg="部署失败!"
fi
cp $project_config_dir/log4j2_prod.xml $project_dir/target/
cp $project_config_dir/mytest-0.01.conf $project_dir/target/
mkdir -p $project_dir/target/config
cp $project_config_dir/application-prod.properties $project_dir/target/config/
service mytest start
service mytest status
tail -1f /data/logs/mytest.log | sed '/Started MyTestSpringBootApplication in/Q'
echo $error_msg
curl 'https://oapi.dingtalk.com/robot/send?access_token=xxxxxx' \
-H 'Content-Type: application/json' \
-d '
{"msgtype": "text",
"text": {
"content":"'$error_msg'"
}
}'
exit 0
Jenkins - 自由风格 - 构建 - Send files or execute commands over SSH
Exec command:sh /data/mytest/deploy.sh master
下载Jetty9
https://www.eclipse.org/jetty/download.html
解压jetty到jetty9,部署war文件到jetty9/webapps目录,没有ROOT可以手动创建(demo-base目录可以删除)
修改配置文件:
> vim ./start.ini
--module=ext
--module=server
--module=jsp
--module=resources
--module=deploy
--module=jstl
#--module=websocket(没有用到websocket就禁用)
--module=http
jetty.http.port=8080
jetty.http.connectTimeout=35000
启动/停止/重启/调试 jetty:
> ./bin/jetty.sh start
> ./bin/jetty.sh stop
> ./bin/jetty.sh restart
> ./bin/jetty.sh run (相当于tomcat catalina.sh run)
同一服务器跑多个jetty:
1. 修改配置文件:
> vim ./start.ini
jetty.http.port=8080(修改这里的端口)
2. 重命名启动脚本:
> mv ./bin/jetty.sh ./bin/jetty01.sh
以服务方式启动:
> ln -s /jetty9/bin/jetty01.sh /etc/init.d/myJettyService01
> vim /jetty9/bin/jetty01.sh
#!/usr/bin/env bash
JETTY_HOME=/jetty9
> service myJettyService01 check
(检查服务启动参数如果正常则会打印相关jetty启动参数)
> service myJettyService01 start
> chkconfig myJettyService01 on
oss权限分离:新建用户子账户(不要添加策略权限),新建Bucket设置选择子账户读写相关权限即可
测试代码TestAliYunOSS.java:
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.auth.CredentialsProvider;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.model.BucketReferer;
import com.aliyun.oss.model.PutObjectResult;
public class TestAliYunOSS {
private String imgURL = "https://mybucket.oss-cn-shanghai.aliyuncs.com";
private String endpoint = "http://oss-cn-shanghai.aliyuncs.com";
private String accessKeyId = "xxxx";
private String accessKeySecret = "xxxxx";
private String bucketName = "mybucket";
private OSSClient ossClient = null;
// 设置防盗链白名单
@Test
public void testAddReferer() throws Exception {
try {
List<String> refererList = new ArrayList<String>();
refererList.add("https://*.console.aliyun.com");
refererList.add("http://*.mogkh.com");
refererList.add("https://*.mogkh.com");
BucketReferer br = new BucketReferer(true, refererList);
ossClient.setBucketReferer(bucketName, br);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
// 获取防盗链白名单
@Test
public void testGetReferer() throws Exception {
try {
BucketReferer br = ossClient.getBucketReferer(bucketName);
List<String> refererList = br.getRefererList();
for (String referer : refererList) {
System.out.println(referer);
}
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
@Before
public void init() {
try {
System.out.println("init...");
CredentialsProvider credsProvider = new DefaultCredentialProvider(accessKeyId, accessKeySecret);
ClientConfiguration config = new ClientConfiguration();
config.setConnectionTimeout(5 * 1000);// 5秒
config.setConnectionRequestTimeout(5 * 1000);// 5秒
config.setSocketTimeout(15 * 1000);// 15秒
config.setRequestTimeout(1 * 60 * 1000);// 1分钟
config.setRequestTimeoutEnabled(true);
this.ossClient = new OSSClient(endpoint, credsProvider, config);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
@After
public void stop() {
try {
if (this.ossClient != null) {
System.out.println("stop...");
this.ossClient.shutdown();
}
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
protected String putImageFile(String uId, String fileName, File localFile, InputStream inputStream) {
String key = "userdir/" + uId + "/" + fileName;
PutObjectResult putObjectResult = null;
if (localFile != null) {
putObjectResult = ossClient.putObject(bucketName, key, localFile);
} else if (inputStream != null) {
putObjectResult = ossClient.putObject(bucketName, key, inputStream);
}
if (putObjectResult == null) {
return null;
}
String etag = putObjectResult.getETag();
if (etag == null || etag.isEmpty()) {
return null;
}
return imgURL + "/" + key + "?t=" + etag;
}
}
> mvn clean package dockerfile:build
本地运行
> docker run --name 容器名称 -p 8081:8080 -d 镜像:标签
> docker login --username=用户名 registry.cn-hangzhou.aliyuncs.com
> docker tag [本地容器镜像ID] registry.cn-hangzhou.aliyuncs.com/命名空间/仓库名:版本号
> docker push registry.cn-hangzhou.aliyuncs.com/命名空间/仓库名:版本号
1.容器服务 -> 集群 -> 创建Serverless Kubernetes(公测)
2.从https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG.md下载最新的kubectl客户端
3.复制$HOME/.kube/config安装和设置kubectl客户端ln -s /usr/local/bin/kubectl指向下载kubectl文件
4.应用 - 无状态 - 使用镜像创建 - 选择私有镜像(打包上传的springboot)
5.路由服务(访问方式以供外网访问,内部访问方便容器地址使用内网) - 负载均衡公网访问 - 容器端口(springboot启动端口) - 服务端口(外网开放端口)
6.完成创建,外网访问即可
7. 复制更多查看yaml文件myapp.yaml,把多余自动创建的时间等状态信息去掉即可成为自动打包部署的yaml模板文件。
# 查看java相关进程
> top $(ps -e | grep java | awk '{print $1}' | sed 's/^/-p/')
# 根据pid查看GC相关
> jstat -gc [pid]
# 根据pid查看服务安装路径
> ll /proc/[pid]/cwd
> lsof -p [pid]
# 保持JAVA线程栈日志
> jstack [pid] > /tmp/jstack.log
# 统计文件中java.lang.Thread.State出现次数
> grep 'java.lang.Thread.State' jstack.log | wc -l
> grep -A 1 'java.lang.Thread.State' jstack.log | grep -v 'java.lang.Thread.State' | sort | uniq -c |sort -n
# 打印JAVA线程堆日志,下载到本地用使用JProfiler或开源的MAT(Memory Analyzer 1.10.0 Release)分析jvm heap
> jmap -dump:format=b,file=/tmp/jheap.hprof [pid]