0%

感谢Let’s Encrypt让互联网更安全。

Let’s Encrypt的客户端刚刚更名为certbot,以前叫letsencrypt。certbot可以自动化的申请,安装和更新证书,让生活更美好。
不过当前nginx插件尚不成熟,而且certbot自身尚处于beta阶段,当前版本0.7.0,debian源里的版本也比较陈旧。

因此还是需要一些简单的手工配置。

安装

从官方仓库克隆certbot

1
$ sudo git clone https://github.com/certbot/certbot /opt/certbot

执行certbot-auto会自动安装发行版依赖和python依赖:

1
2
$ cd /opt/certbot
$ ./certbot-auto

配置nginx

申请证书时,let’s encrypt需要访问域名的特定目录来确认域名的所有权,由certbot配合来完成验证。

需要访问的目录为${webroot-path}/.well-known/acme-challenge/,申请证书时,certbot会写入认证所需信息,由let’s encrypt来验证。

将此目录映射到/var/www/letsencrypt,先建立目录:

1
2
3
$ cd /var/www
# mkdir letsencrypt
# chgroup www-data letsencrypt

然后修改站点配置文件:

1
2
3
4
5
6
7
8
9
server {
listen 80 default_server;
server_name my-domain;

location /.well-known/acme-challenge {
root /var/www/letsencrypt;
}
...
}

最后重新加载nginx配置文件:

1
# nginx -t && sudo nginx -s reload

配置certbot

新建一个配置文件/etc/letsencrypt/configs/my-domain.conf来指定要申请证书的域名等相关信息:

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
# This is an example of the kind of things you can do in a configuration file.
# All flags used by the client can be configured here. Run Certbot with
# "--help" to learn more about the available options.

# Use a 4096 bit RSA key instead of 2048
rsa-key-size = 2048 # or 4096

# Uncomment and update to register with the specified e-mail address
email = xxx@gmail.com

# Uncomment and update to generate certificates for the specified
# domains.
domains = my-domain, www.my-domain

# Uncomment to use a text interface instead of ncurses
text = True

# Uncomment to use the standalone authenticator on port 443
# authenticator = standalone
# standalone-supported-challenges = tls-sni-01

# Uncomment to use the webroot authenticator. Replace webroot-path with the
# path to the public_html / webroot folder being served by your web server.
authenticator = webroot
webroot-path = /var/www/letsencrypt/

可以使用webroot插件来减轻验证配置,自动修改web server来完整验证。

申请证书

因为let’s encrypt限制每个站点在一定时间内申请的证书数量,所以可以用--test-cert选项进行测试,此时申请的证书是无效的,
但操作步骤是完全一样的,测试通过后去掉此选项就是申请正式证书了。

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
$ ./certbot-auto --test-cert --config /etc/letsencrypt/configs/mydomain.conf certonly
Checking for new version...
Requesting root privileges to run certbot...
sudo CERTBOT_AUTO=./certbot-auto /home/xxx/.local/share/letsencrypt/bin/letsencrypt
--test-cert --config /etc/letsencrypt/configs/mydomain certonly

-------------------------------------------------------------------------------
Please read the Terms of Service at https://letsencrypt.org/documents/LE-
SA-v1.0.1-July-27-2015.pdf. You must agree in order to register with the ACME
server at https://acme-staging.api.letsencrypt.org/directory
-------------------------------------------------------------------------------
(A)gree/(C)ancel: A

IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/mydomain/fullchain.pem. Your cert
will expire on 2016-08-12. To obtain a new version of the
certificate in the future, simply run Certbot again.
- If you lose your account credentials, you can recover through
e-mails sent to xxx@gmail.com.
- Your account credentials have been saved in your Certbot
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Certbot so
making regular backups of this folder is ideal.

申请的证书位于/etc/letsencrypt/live/mydomain/目录下

安装证书

1
2
3
4
5
6
7
8
9
server {
listen 443 ssl default_server;
server_name my-domain;

ssl_certificate /etc/letsencrypt/live/my-domain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/my-domain/privkey.pem;

...
}

更多ssl优化设置参见[4]

更新证书

let’s encrypt发行的证书只有90天的有效期,到期需要更新证书。如果参数没有变化,更新证书只需简单的执行:

1
$ ./certbot-auto renew

certbot-auto会使用上次申请证书时使用的参数来更新证书。
如果要测试证书更新,添加选项--dry-run,此时不会更改系统当前设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ./certbot-auto renew --dry-run
Checking for new version...
Requesting root privileges to run certbot...
sudo CERTBOT_AUTO=./certbot-auto /home/guoqiang/.local/share/letsencrypt/bin/letsencrypt renew --dry-run

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/cucc.tazzfdc.com.conf
-------------------------------------------------------------------------------
** DRY RUN: simulating 'certbot renew' close to cert expiry
** (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
/etc/letsencrypt/live/cucc.tazzfdc.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
** (The test certificates above have not been saved.)

自动更新证书

可以添加cron脚本来自动更新证书,当证书无需更新时,renew命令并不会去更新证书,所以crontab设置的时间间隔并无强制要求,但一般无需设置太过频繁的调度。
let’s encrypt证书大约还剩一个月有效期时,可以进行更新。

自动更新脚本renew‑letsencrypt.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

cd /opt/certbot/
./certbot-auto renew
if \[ $? -ne 0 \]; then
ERRORLOG=\`tail /var/log/letsencrypt/letsencrypt.log\`
echo -e "The Let's Encrypt cert has not been renewed! \\n \\n" \\
$ERRORLOG
else
nginx -s reload
fi

exit 0

将脚本添加到crontab自动运行即可。比如,每月1号执行此脚本:

1
0 0 1 * * /usr/local/bin/renew-letsencrypt.sh

未来

以上安装配置仍然十分繁琐,nginx插件成熟并且进入官方源后,只要几条指令就可以安装和更新证书了:

安装certbot:

1
# apt install certbot certbot-nginx

安装证书:

1
$ certbot --nginx

更新证书:

1
$ certbot renew

References:
[1]Getting Started
[2]User Guide
[3]Using Free SSL/TLS Certificates from Let’s Encrypt with NGINX
[4]Configuring HTTPS servers

===
[erq]

Dataguard配置中因为网络故障出现了日志gap,修复gap后,恢复自动同步。

备库看一切正常:

1
2
3
4
SQL> SELECT max(sequence#), applied FROM v$archived_log GROUP BY applied;
MAX(SEQUENCE#) APPLIED
-------------- ---------
20114 YES

但主库确显示日志并未应用:

1
2
3
4
5
SQL> SELECT max(sequence#), applied FROM v$archived_log GROUP BY applied;

MAX(SEQUENCE#) APPLIED
-------------- ---------
20114 NO

其实日志已经正确的同步并应用了,只是primary没有更新applied列的值而已,这是个bug, 编号1369630.1

主库上有一个指定的ARCn进程与远程的RFS进程通讯来更新applied列的值,如果此进程挂起,无法与远程通讯也就无法更新v$archived_log的applied列值。

解决方案

主库ALERT_sid.LOG文件查找最新的,包含以下类似信息的行:

1
ARCn: Becoming the heartbeat ARCH

说明ARCn进程/线程出问题挂起了,n是数字,比如ARC0

然后查找ARC0的进程/线程号,*nix平台是进程,windows平台是线程:

windows平台输出:

1
2
3
4
SQL> SELECT spid, osuser, s.program FROM v$process p, v$session s WHERE p.addr=s.paddr and p.program like '%ARC0%'
SPID OSUSER PROGRAM
---------- ----------------- -----------------------
2616 SYSTEM ORACLE.EXE (ARC0)

将对应的进程/线程kill掉就可以了,oracle会重新启动相应的进程/线程。

*nix平台:

1
# kill -9 processid 

windows平台:

1
cmd> orakill sid threadid

sid是oracle实例名

或者更简单粗暴的重新启动oracle实例

alert_sid.log文件中可能会出现错误提示:

1
ORA-16401: archivelog rejected by RFS

sid_arc0_xxxx.trc文件中也会有:

1
2
Error 16401 creating standby archive log file at host 'xxx'
ORA-16401: archivelog rejected by RFS

这是出现重复归档的错误提示,因为ARC0想重新归档已经应用到备库的日志,可以安全的忽略掉这些信息,ARC0会更新已经传输到备库的日志的APPLIED列。

ORA-16401也可能出现在多个主库和/或备库向同一个备库归档的情形下,这种情况一般是因为LOG_ARCHIVE_DEST_n参数配置错误。

References:
[1]Oracle Data-guard Issues - ‘APPLIED’-Column not updated in v$archived_log table
[2]Bug Note: 1369630.1
[3]ORACLE在windows上使用orakill结束oracle会话的线程

===
[erq]

因为网络故障或资源紧张,会使standby出现日志同步间隙(archive gap)。虽然设置好了FAL_CLIENT和FAL_SERVER参数,有时候仍然无法自动解决日志间隙问题。

比如由于CONTROL_FILE_RECORD_KEEP_TIME设置导致,较旧的主库归档日志记录被循环覆盖,standby无法自动获取相应的日志文件:

1
2
3
4
5
6
7
8
9
10
11
12
Fetching gap sequence in thread 1, gap sequence 19767-19866
Thu May 12 09:45:47 2016
FAL\[client\]: Failed to request gap sequence
GAP - thread 1 sequence 19767-19866
DBID 1276927241 branch 874659493
FAL\[client\]: All defined FAL servers have been attempted.
-------------------------------------------------------------
Check that the CONTROL_FILE_RECORD_KEEP_TIME initialization
parameter is defined to a value that is sufficiently large
enough to maintain adequate log switch information to resolve
archivelog gaps.
-------------------------------------------------------------

如果standby缺少的归档日志尚未删除,可以拷贝缺少的日志到备库,然后在备库上注册,备库会apply这些归档日志。
如果归档日志已经被删除,则需要从主库做增量备份,然后在备库进行恢复来修复日志间隙。

手工拷贝归档日志

首先查看日志间隙:

1
2
3
4
sql> select * from v$archive_gap;
THREAD# LOW_SEQUENCE# HIGH_SEQUENCE#
-------- ---------------- ----------------------
1 19767 19866

将对应的归档日志拷贝到备库端

最后在备库端注册这些归档日志:

1
sql> alter database register physical logfile '\\path\\to\\archive_log'

备库会立即应用这些归档日志。如果日志数量过多,也可以使用以下语句来自动恢复数据库:

1
SQL> ALTER DATABASE RECOVER AUTOMATIC STANDBY DATABASE;

创建备库导致的日志不同步问题

最近遇到的一个案例是这样的,rman dupicate创建完备库后,因为没有使用dorecovery恢复增量备份,主库上的部分归档日志又被清除掉了,导致主备库无法进行同步,备库有类似如下错误提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
Media Recovery Waiting for thread 1 sequence 23943
Fetching gap sequence in thread 1, gap sequence 23943-24042
Sun Jul 24 13:02:27 2016
FAL\[client\]: Failed to request gap sequence
GAP - thread 1 sequence 23943-24042
DBID 1276927241 branch 874659493
FAL\[client\]: All defined FAL servers have been attempted.
-------------------------------------------------------------
Check that the CONTROL_FILE_RECORD_KEEP_TIME initialization
parameter is defined to a value that is sufficiently large
enough to maintain adequate log switch information to resolve
archivelog gaps.
-------------------------------------------------------------

主库有类似如下错误提示:

1
2
3
4
FAL Redo Shipping Client Established Network Login
Failed to queue the whole gap
GAP - thread 1 sequence 23943-24042
DBID 1276927241 branch 874659493

此时备库查询v$archive_gap表是没有任何记录的,幸好还有一个备库有完整的归档日志,将相应的归档日志完整拷贝到新建的物理备库上,然后执行以下语句自动恢复数据库:

1
SQL> ALTER DATABASE RECOVER AUTOMATIC STANDBY DATABASE;

所有可用日志全部恢复完毕后,会有如下提示:

1
2
3
4
5
6
7
8
9
10
11
ALTER DATABASE RECOVER AUTOMATIC STANDBY DATABASE
*
ERROR at line 1:
ORA-00279: change 7255663631 generated at 07/25/2016 08:51:20 needed for thread 1
ORA-00289: suggestion : D:\\ARCHIVED_LOG\\ARC24380_0874659493.001
ORA-00280: change 7255663631 for thread 1 is in sequence #24380
ORA-00278: log file 'D:\\ARCHIVED_LOG\\ARC24380_0874659493.001' no longer needed for this recovery
ORA-00308: cannot open archived log 'D:\\ARCHIVED_LOG\\ARC24380_0874659493.001'
ORA-27041: unable to open file
OSD-04002: unable to open file
O/S-Error: (OS 2) 系统找不到指定的文件。

这是因为已经没有日志可用于恢复了,此时重新打开实时日志应用就可以开始自动同步了:

1
SQL> alter database recover managed standby database disconnect from session;

如果归档日志已经找不到了,则可以采用以下增量备份方式修复备库。

增量备份修复备库

  1. 查找恢复点SCN
    备库端:
    首先查看日志间隙:
    1
    2
    3
    4
    sql> select * from v$archive_gap;
    THREAD# LOW_SEQUENCE# HIGH_SEQUENCE#
    -------- ---------------- ----------------------
    1 19767 19766

主库端:
然后主库上查找日志间隙中低端日志序号的上一个日志的first_change#

1
2
3
4
sql> select SEQUENCE#, FIRST_CHANGE# from v$archived_log where SEQUENCE#=19766;
SEQUENCE# FIRST_CHANGE#
--------- -------------
19766 7008518015
  1. 备库停止apply log
    备库端:
    1
    sql> alter database recover managed standby database cancel;
  2. 主库增量SCN备份
    主库端:
    1
    2
    $ rman target /
    RMAN> backup incremental from scn 7008518015 database format '/path/to/stdby_%U.bak';
  3. 生成备库控制文件
    主库端:
    1
    RMAN> backup current controlfile for standby format '/path/to/stdby_%U.ctl';
  4. 拷贝备份和控制文件到备库
  5. 备库使用新生成的控制文件启动
    备库端:
    1
    2
    3
    4
    rman> shutdown immediate;
    rman> startup nomount;
    rman> restore standby controlfile from '/path/to/stdby_xxx.ctl';
    rman> alter database mount
  6. 备库进行数据恢复
    备库端:
    1
    2
    RMAN> catalog start with '/path/to_bak';
    RMAN> recover database noredo;
  7. 备库恢复日志应用
    备库端:
    1
    SQL> alter database recover managed standby database disconnect from session;

References:
[1]DataGuard 中处理archive gap的方法
[2]Roll Forward Physical Standby Database using RMAN incremental backup
[3]Oracle Physical DataGuard使用RMAN增量备份修复GAP
[4]Using RMAN Incremental Backups to Refresh a Standby Database

===
[erq]

查询表空间状态:
[sql]
sql> select tablespace_name,status from dba_tablespaces;
[/sql]

修改表空间状态为READ ONLY:
[sql]
sql> alter tablespace tablespace_name read only;
[/sql]

表空间置为READ ONLY之后,不再发生任何变化,只需保存一份有效的备份即可,可以使用RMAN,也可以使用OS直接拷贝数据文件来备份只读表空间。

之后日常备份时就可以skip readonly来忽略掉只读表空间的备份,加速备份速度。

但恢复数据库时记得要check readonly,即使没有只读表空间,恢复仍然会成功,但open数据库会出现错误。

如果不使用check readonly,记得要将只读表空间的数据文件拷贝到相应的位置之后再recover数据库。

References:
[1]Oracle Read-only Tablespace(只读表空间)
[2](09)常被人遗忘的只读表空间
[3]Database Backup and Recovery Basics
[4]只读表空间的备份与恢复
[5]READ ONLY Tablespace Restore and Recovery
[6]Backing up, Restoring and Recovering Read Only tablespaces with RMAN
[7]Read Only Tablespaces and BACKUP OPTIMIZATION
[8]Rman管理命令

===
[erq]

python脚本在shell下运行的好好的,作为crontab任务运行就出错,import cx_Oracle的时候出错:

1
ImportError: libclntsh.so.12.1: cannot open shared object file: No such file or directory

明显是库路径的问题。

crontab运行程序时,其环境变量与用户的环境变量是不同的,有自己的变量环境,因此需要为crontab设置正确的环境变量,脚本才能正确运行。

python脚本中使用os.environ或者os.putenv来设置环境变量是没用的,反正在py脚本中正确设置了LD_LIBRARY_PATH变量,仍然无法解决问题。

有这么几个方法来设置crontab的环境变量:

第一种

可以在crontab配置文件中添加环境变量,但是不能用变量,类似$PATH这种,只能照实写。
类似如下:

1
2
3
4
5
6
ORACLE_HOME=/opt/oracle/instantclient_12_1
LD_LIBRARY_PATH=/opt/oracle/instantclient_12_1
PATH=/opt/oracle/instantclient_12_1
TNS_ADMIN=/opt/oracle/instantclient_12_1
SQLPATH=/opt/oracle/instantclient_12_1
NLS_LANG=AMERICAN_AMERICA.AL32UTF8

第二种

写一个bash脚本来中转一下,bash脚本中再调用py脚本,类似如下:

1
2
3
4
#!/bin/bash
export ORACLE_HOME=/opt/oracle/instantclient_12_1
source /home/xxx/.mybashrc
/home/xxx/py/business_notify.py "$@"

第三种

直接在crontab配置任务要执行的命令行上添加环境变量,类似如下:

1
*/10 * * * * LD_LIBRARY_PATH=/opt/oracle/instantclient_12_1 TNS_ADMIN=/opt/oracle/instantclient_12_1 /home/xxx/py/business_notify.py

推荐第一种

一般crontab运行的脚本中尽量不要依赖环境变量,使用绝对路径为佳。

===
[erq]

最近版本的即时客户端已经没有了tnsping这个命令,可以从相应版本的客户端或服务器bin目录中拷贝此文件,或者用脚本简单的替代:

1
2
3
4
tnsping >/dev/null 2>&1 
tnsping() {
sqlplus -L -S x/x@$1 </dev/null grep ORA- (grep -v ORA-01017 echo OK)
}

将此段脚本放入.profile或.bashrc即可。

又或者直接测试一下服务器的1521端口是否正常开放:

1
2
3
4
$ telnet a.b.c.d 1521
Trying a.b.c.d...
Connected to a.b.c.d.
Escape character is '^\]'.

这样就说明oracle监听是正常的。

===
[erq]

当ssh,vnc都不能访问客户机时,serial console可以提供另一种访问客户机的途径。

客户机serial console配置

/etc/inittab文件打开或添加如下行:

1
T0:23:respawn:/sbin/getty -L ttyS0 38400 vt100

/etc/securetty文件中确保列出了ttyS0:

1
ttyS0

/etc/default/grub文件添加:

1
2
3
GRUB_CMDLINE_LINUX='console=tty0 console=ttyS0,38400n8'
GRUB_TERMINAL=serial
GRUB_SERIAL_COMMAND="serial --speed=38400 --unit=0 --word=8 --parity=no --stop=1"

波特率是38400,没有奇偶校验,停止位是1

使用virsh来连接客户机串行控制台比较简单,应该也可以重定向客户机的串行端口到主机。

References:
[1]Debian Linux: Set a Serial Console
[2]Working with the serial console
[3]qemu(-kvm) monitor and serial console over sockets with minicom

===
[erq]

主机通过virtio上的9p文件系统以及文件系统设备,可以将主机上的文件系统导出给客户机来挂载使用

v9fs是plan 9 9p远程文件系统协议的实现

主机配置

在客户机启动命令上新添加fsdev和device选项

1
2
-fsdev local,security_model=passthrough,id=fsdev0,path=/mnt/share 
-device virtio-9p-pci,id=fs0,fsdev=fsdev0,mount_tag=hostshare

这样导出了主机的/mnt/share目录供客户机来存取

客户机配置

客户机需要在内核中开启9P文件系统相关选项,可以这样查看:

1
2
3
4
5
6
7
8
9
$ cat /boot/config-$(uname -r) grep 9P
CONFIG_NET_9P=m
CONFIG_NET_9P_VIRTIO=m
CONFIG_NET_9P_RDMA=m
# CONFIG_NET_9P_DEBUG is not set
CONFIG_9P_FS=m
CONFIG_9P_FSCACHE=y
CONFIG_9P_FS_POSIX_ACL=y
CONFIG_9P_FS_SECURITY=y

可以看到9P配置成了内核模块的形式,然后就可以挂载主机的目录来使用了:

1
# mount -t 9p -o trans=virtio\[,version=9p2000.L\] hostshare /mnt/point

hostshare就是主机导出的挂载点的名称,此处将其挂载到客户机的/mnt/point。
version选项是可选的。

References:
[1]Example Sharing Host files with the Guest
[2]Documentation/9psetup
[3]v9fs: Plan 9 Resource Sharing for Linux

===
[erq]

通过rlwrap可以使sqlplus具有具有命令历史回溯和命令行编辑功能,通过提供自动完成字典,可以更进一步使sqlplus具有tab自动补全/完成功能。

德国一个哥们写了rlwrap_ext for oralce可以使sqlplus自动补全sql关键字,oracle视图、表、包等等,比较全面。

下载

可以直接下载最新的版本

1
$ wget http://www.linuxification.at/download/rlwrap-extensions-V12-0.03.tar.gz

安装

1
2
$ sudo tar zxvf rlwrap-extensions-V12-0.03.tar.gz -C /usr/local/share/rlwrap/completions
$ sudo cp /usr/local/share/rlwrap/completions/sql+ /usr/local/bin/

然后使用sql+替代sqlplus就可以了。

References:
[1]rlwrap_ext for oracle

===
[erq]

最常见的原因是process和session数量设置过低。

查看修改process参数

1
2
3
4
5
6
7
8
9
10
> show parameter process

NAME TYPE VALUE
------------------------------------ --------------------------------- -------------------
aq_tm_processes integer 0
db_writer_processes integer 3
gcs_server_processes integer 0
job_queue_processes integer 20
log_archive_max_processes integer 2
processes integer 150

偏低,修改process参数

1
> alter system set processes=1000 scope = spfile;

这里无法直接修改内存值,也就是不能使用scope=both,否则会有提示:

1
ORA-02095: specified initialization parameter cannot be modified

查看修改session参数

查看

1
2
3
4
5
6
7
8
9
10
11
12
> show parameter session
NAME TYPE VALUE
------------------------------------ --------------------------------- --------------------
java_max_sessionspace_size integer 0
java_soft_sessionspace_limit integer 0
license_max_sessions integer 0
license_sessions_warning integer 0
logmnr_max_persistent_sessions integer 1
session_cached_cursors integer 20
session_max_open_files integer 10
sessions integer 170
shared_server_sessions integer

修改

1
> alter system set sessions=1105 scope = spfile;

sessions是个派生值,由processes的值决定,公式sessions=1.1*process + 5

因为修改的是spfile,所以并不会立即生效,只有重新启动oracle,设置才会生效。

===
[erq]