0%

欢迎关注个人公众号 DailyJobOps

原文地址:Gitlab整理汇总(安装、非22端口克隆、升级及遇到问题、备份恢复)

1、安装

如果不采用自建数据库,而是gitlab自集成的数据库,那么安装很简单

1.1、采用外置自建数据库

  • 需要先成功安装数据库,确保数据库可以正常访问
  • 在数据库中创建对应的数据库、账号、密码,然后授权
  • 下载rpm包,执行yum install gitlab-ce.xxx.rpm
  • 修改配置文件 /etc/gitlab/gitlab.rb
  • 重载配置 gitlab-ctl reconfigure
  • 重启服务 gitlab-ctl restart

1.2、采用Gitlab自集成数据库

  • 下载rpm包,执行yum install gitlab-ce.xxx.rpm
  • 重载配置 gitlab-ctl reconfigure
  • 重启服务 gitlab-ctl restart

2、非22端口克隆

常规git仓库clone的方式为:

  • http方式

    1
    git clone http://gitlab.xxx.com/devops-group/devops-dbp-platform.git
  • ssh方式(默认22端口)

    1
    git clone git@gitlab.xxx.com:devops-group/devops-dbp-platform.git

如果gitlab安装的ssh端口(其实就是安装主机的ssh服务的启动端口)是非22端口,比如是2022端口,则ssh方式克隆地址变更为

1
git clone ssh://git@gitlab.xxx.com:2022/devops-group/devops-dbp-platform.git

注意和上面默认22端口的地址做对比,看看不一样的地方在哪里


3、升级

跨大版本升级的原则是:

先升级到下一个大版本中的最高版本,没有问题之后再如此继续直到升级到最后一个大版本中需要的版本

比如,实际环境中当前版本是 8.1.4 那么升级路线是 8.1.4 -> 8.17.8 -> 9.5.10 -> 10.8.7 -> 11.11.8 -> 12.0.12

📢 升级过程中需要注意一个数据库的问题,在 12.1 及之后版本,官方移除了对MySQL的支持,数据库使用PostgreSQL

image.png

升级步骤
1、先停止数据写入(当然如果在一个确定不会有写入的时候,比如下班之后,获取停止域名解析等可以不进行如下操作)

1
2
3
sudo gitlab-ctl stop unicorn 
sudo gitlab-ctl stop puma
sudo gitlab-ctl stop sidekiq

2、按照整理出来的升级路径下载对应的rpm包
这里推荐 https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7
3、更新升级

1
2
3
4
5
6
# 安装更新rpm包
rpm -Uvh gitlab-ce-10.8.7-ce.0.el7.x86_64.rpm
# 重新生效配置
gitlab-ctl reconfigure
# 重启服务
gitlab-ctl restart

4、验证当前版本是否正常
5、重复如上过程,升级到下一个版本


4、备份及恢复

备份配置

1
2
3
4
5
6
7
8
9
10
11
# 配置相关配置
# vim /etc/gitlab/gitlab.rb
# gitlab 访问域名
external_url 'http://192.168.8.127'
# 备份配置
gitlab_rails['manage_backup_path'] = true
gitlab_rails['backup_path'] = "/data/gitlab/backups"
gitlab_rails['backup_archive_permissions'] = 0644
# 备份有效期,单位为秒,比如如下7天 ,也可以根据自己实际情况来做异地转存
# 这里的有效期是针对本地存储,而非remote storage,比如阿里云的OSS
gitlab_rails['backup_keep_time'] = 604800

备份

gitlab-rake gitlab:backup:create

image.png

注意上述告警提示,需要自己备份如下两个文件,恢复的时候也确保这两个位置有对应的问题

  • /etc/gitlab/gitlab.rb
  • /etc/gitlab/gitlab-secrets.json

远端备份

另外从上述备份记录中注意到 Uploading backup archive to remote storage ... skipped, 从7.4版本支持直接转存本地备份到远程(Starting with GitLab 7.4 you can let the backup script upload the ‘.tar’ file it creates

举例

1
2
3
4
5
6
7
8
9
10
gitlab_rails['backup_upload_connection'] = {
'provider' => 'Aliyun',
'aliyun_accesskey_id' => 'Access Key',
'aliyun_secretekey_id' => 'Secret Key',
'aliyun_oss_endpoint' => 'http://oss-cn-beijing-internal.aliyuncs.com',
'aliyun_oss_bucket' => 'devops-backup',
'aliyun_oss_location' => 'beijing'
}
# 对象存储bucket中的子目录
gitlab_rails['backup_upload_remote_directory'] = 'gitlab'

注意:一般备份不用长久保存,备份在OSS,为了节约成本,可以配置阿里云OSS bucket的生命周期,让其保留最近30天的备份即可

具体参考
https://docs.gitlab.com/12.10/ee/administration/object_storage.html

恢复

注意事项:
1、恢复的文件必须是在配置的备份路径下,比如这里的 /data/gitlab/backups
2、恢复的时候指定备份的序号,比如 637134260_2021_11_17_12.0.12_gitlab_backup.tar 这里取637134260_2021_11_17_12.0.12
3、备份文件的权限必须是 644
4、检查恢复状态 gitlab-rake gitlab:check SANITIZE=true

1
2
3
4
5
6
7
sudo gitlab-ctl stop unicorn 
sudo gitlab-ctl stop puma
sudo gitlab-ctl stop sidekiq
# Verify
sudo gitlab-ctl status
# restore
gitlab-rake gitlab:backup:restore BACKUP=1629369869

5、内存消耗问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#进程超时时间
unicorn['worker_timeout'] = 60
#进程数
unicorn['worker_processes'] = 10
#进程最小内存 200KB
unicorn['worker_memory_limit_min'] = "200 * 1 << 20"
#进程最大内存 300MB
unicorn['worker_memory_limit_max'] = "300 * 1 << 20"
#并发数
sidekiq['concurrency'] = 16
#数据库缓存
postgresql['shared_buffers'] = "256MB"
#数据库并发数
postgresql['max_worker_processes'] = 8

6、升级过程问题整理:

1、mysql2 adapter 问题

报错信息:

NameError: uninitialized constant Mysql2::Client::SECURE_CONNECTION

解决方案:

升级到0.3.20 然后修改Gemfile.lock

-> https://github.com/brianmario/mysql2/issues/711
-> Use mysql2 0.3.17 or higher for MySQL 5.7 compatibility.

1
2
3
/opt/gitlab/embedded/bin/gem install -i/opt/gitlab/embedded/service/gem/ruby/2.1.0 mysql2 -v 0.3.20
vim /opt/gitlab/embedded/service/gitlab-rails/Gemfile.lock
mysql2 (0.3.20)

2、安装gitlab-ce之后,且在mysql2 adapter正常的情况,需要执行 gitlab-rake setup 进行数据库表安装

3、gitlab基于备份进行恢复报错

报错信息:

  • ERROR 1227 (42000) at line 27: Access denied; you need (at least one of) the SUPER privilege(s) for this operation

解决方案:

管理员登录数据库然后给gitlab用户赋权 `**grant super on *.* to 'gitlab'@'localhost';**`

报错信息

  • ERROR 1840 (HY000) at line 33: @@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_EXECUTED is empty.

解决方案:

管理员登录数据库 执行 **reset master;**  因为gitlab使用的MySQL是本地自建,单节点不需要主从,故reset不影响;
还有一种方式是注销掉 备份文件中涉及到 `@@GLOBAL.GTID_PURGED`

4、权限报错

报错信息

Failed asserting that mode permissions on "/data/git-data/repositories" is 2770

解决方案

chmod 2770 /data/git-data/repositories

5、Bundler::GemNotFound

报错信息

Bundler::GemNotFound: Your bundle is locked to mysql2 (0.3.20), but that version could not be found in any of the sources listed in your Gemfile. If you haven't changed sources, that means the author of mysql2 (0.3.20) has removed it. You'll need to update your bundle to a different version of mysql2 (0.3.20) that hasn't been removed in order to install

昨天按照

1
/opt/gitlab/embedded/bin/gem install -i/opt/gitlab/embedded/service/gem/ruby/2.1.0 mysql2 -v 0.3.20

已经安装0.3.20版本,但是升级之后提示不存在,在 /opt/gitlab/embedded/service/gem/ruby/2.1.0/extensions/x86_64-linux/2.1.0/也看到对应的版本存在。
猜测是因为升级之后安装方式是否不一样

  • /opt/gitlab/embedded/bin/gem list |grep mysql → 提示没有MySQL
  • 重新安装,只不过不指定安装目录,/opt/gitlab/embedded/bin/gem install mysql2 -v 0.3.20
  • 再次 /opt/gitlab/embedded/bin/gem list |grep mysql 提示安装成功
  • 通过 find /opt/gitlab/ -name ‘mysql2-0.3.20’ 查找发现,新版本应该是从 /opt/gitlab/embedded/lib/ruby/gems/2.3.0/extensions/x86_64-linux/2.3.0/ 下找扩展

image.png

6、升级到 9.5.10 报错

报错信息:

LoadError: cannot load such file -- peek-mysql2

解决方案:

1
2
3
4
5
6
7
[root@devops-gitlab-vpc ~]# /opt/gitlab/embedded/bin/gem install peek-mysql2
Fetching: peek-mysql2-1.2.0.gem (100%)
Successfully installed peek-mysql2-1.2.0
Parsing documentation for peek-mysql2-1.2.0
Installing ri documentation for peek-mysql2-1.2.0
Done installing documentation for peek-mysql2 after 0 seconds
1 gem installed

但是在安装了之后还是提示 cannot load such file -- peek-mysql2
最终经过排查和试验,在 Gemfile.lock 中 修改

gem ‘peek-mysql2’, ‘~> 1.1.0’, group: :mysql 为 gem ‘peek-mysql2’ 和 之前 mysql2 的保持一致,重新 gitlab:check 该错误消失

7、升级到 9.5.10 报错

报错信息:

Mysql2::Error: SELECT command denied to user 'gitlab'@'localhost' for table 'user': SHOW FULL FIELDS FROM `mysql`.`user`

解决方案

1
2
mysql> grant select on mysql.* to gitlab@'localhost'; 
Query OK, 0 rows affected, 1 warning (0.00 sec)

报错信息:

Mysql2::Error: Thread stack overrun:  14000 bytes used of a 131072 byte stack, and 128000 bytes needed.  Use 'mysqld --thread_stack=#' to specify a bigger stack.: UPDATE `web_hooks` SET `job_events` = `web_hooks`.`build_events` WHERE `web_hooks`.`id` >= 1 AND `web_hooks`.`id` < 2

thread_stack=256K

Mysql2::Error: Duplicate column name ‘job_events’: ALTER TABLE web_hooks ADD job_events tinyint(1)

解决方案:

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
mysql> select job_events from web_hooks ;
+------------+
| job_events |
+------------+
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
+------------+
18 rows in set (0.00 sec)

mysql> alter table web_hooks drop column job_events ;
Query OK, 0 rows affected (0.06 sec)
Records: 0 Duplicates: 0 Warnings: 0

报错信息:

Mysql2::Error: Trigger already exists: CREATE TRIGGER trigger_688beaaec90d_insert / Mysql2::Error: Trigger already exists: CREATE TRIGGER trigger_688beaaec90d_update

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> show triggers ;
+-----------------------------+--------+-----------+-------------------------------------------+--------+------------------------+-------------------+------------------+----------------------+----------------------+--------------------+
| Trigger | Event | Table | Statement | Timing | Created | sql_mode | Definer | character_set_client | collation_connection | Database Collation |
+-----------------------------+--------+-----------+-------------------------------------------+--------+------------------------+-------------------+------------------+----------------------+----------------------+--------------------+
| trigger_688beaaec90d_insert | INSERT | web_hooks | SET NEW.`job_events` = NEW.`build_events` | BEFORE | 2021-08-12 11:40:22.88 | STRICT_ALL_TABLES | gitlab@localhost | utf8 | utf8_general_ci | utf8_general_ci |
| trigger_688beaaec90d_update | UPDATE | web_hooks | SET NEW.`job_events` = NEW.`build_events` | BEFORE | 2021-08-12 11:40:22.88 | STRICT_ALL_TABLES | gitlab@localhost | utf8 | utf8_general_ci | utf8_general_ci |
+-----------------------------+--------+-----------+-------------------------------------------+--------+------------------------+-------------------+------------------+----------------------+----------------------+--------------------+
2 rows in set (0.00 sec)

mysql> drop trigger trigger_688beaaec90d_insert ;
Query OK, 0 rows affected (0.01 sec)

mysql> drop trigger trigger_688beaaec90d_update ;
Query OK, 0 rows affected (0.01 sec)

报错信息:

Mysql2::Error: Statement violates GTID consistency: CREATE TABLE ... SELECT.: CREATE TABLE issue_assignees AS
SELECT assignee_id AS user_id, id AS issue_id FROM issues WHERE assignee_id IS NOT NULL

问题分析:

MySQL5.6及以上的版本,开启了 enforce_gtid_consistency=true 功能导致的,MySQL官方解释说当启用 enforce_gtid_consistency 功能的时候,MySQL只允许能够保障事务安全,并且能够被日志记录的SQL语句被执行,像create table … select 和 create temporarytable语句,以及同时更新事务表和非事务表的SQL语句或事务都不允许执行。

解决方案:

1
2
3
# GTID_MODE = ON requires ENFORCE_GTID_CONSISTENCY = ON.
gtid_mode = 0
enforce_gtid_consistency = 0

8、升级 10.8.7 报错

报错信息

  • mkmf.rb can’t find header files for ruby at /opt/gitlab/embedded/lib/ruby/include/ruby.h

参考 https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/3635

解决方案

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
[root@devops-gitlab-vpc embedded]# cd /opt/gitlab/embedded/lib/ruby/include
-bash: cd: /opt/gitlab/embedded/lib/ruby/include: No such file or directory
[root@devops-gitlab-vpc embedded]# cd /opt/gitlab/embedded/lib/ruby/
[root@devops-gitlab-vpc ruby]# ll
total 12
drwxr-xr-x 31 root root 4096 Aug 12 12:18 2.3.0
drwxr-xr-x 3 root root 4096 Aug 12 12:19 gems
drwxr-xr-x 3 root root 4096 Jul 26 2018 site_ruby


# complie ruby 2.3.0
wget https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.0.tar.gz
tar -zxvf ruby-2.3.0.tar.gz
cd ruby-2.3.0
./configure
make

cp -ra include /opt/gitlab/embedded/lib/ruby

cp -ra .ext/include/x86_64-linux /opt/gitlab/embedded/lib/ruby/include/ruby/


# Error: /opt/gitlab/embedded/lib/ruby/include/ruby/ruby.h:24:25: fatal error: ruby/config.h: No such file or directory
cd /opt/gitlab/embedded/lib/ruby/include/
cp ruby/x86_64-linux/ruby/config.h ruby/


# Error: make: *** No rule to make target `/include/x86_64-linux/ruby/config.h', needed by `client.o'. Stop.
mkdir -p /include/x86_64-linux/ruby/
cp /opt/gitlab/embedded/lib/ruby/include/ruby/config.h /include/x86_64-linux/ruby/


最后在安装0.4.10
/opt/gitlab/embedded/bin/gem install mysql2 -v 0.4.10


# Error: LoadError: cannot load such file -- peek-mysql2
/opt/gitlab/embedded/bin/gem install peek-mysql2 -v 1.1.0
vim /opt/gitlab/embedded/service/gitlab-rails/Gemfile
gem 'peek-mysql2', '~> 1.1.0', group: :mysql -> gem 'peek-mysql2', '~> 1.1.0'

再次 gitlab-ctl reconfigure

报错信息:

ERROR: Encountered unsupported config key 'gitlab_git_http_server' in /etc/gitlab/gitlab.rb.

gitlab_git_http_server[‘repo_root’] = “xxx” → gitlab_workhorse[‘repo_root’] = “xxx”

Error: Mysql2::Error: Illegal mix of collations (utf8_general_ci,IMPLICIT) and     (utf8_unicode_ci,IMPLICIT) for operation '=':         INSERT INTO user_synced_attributes_metadata     (user_id, provider, email_synced)

报错分析:

user_synced_attributes_metadata 采用默认字符集和字符集排序 utf8 和  utf8_general_ci (show variables like '%collation%';)

users 表和很多字段都是  utf8_unicode_ci 字符集排序,

解决方案:

根据上面的报错修复 email_provider 字符集排序。
1
2
mysql> alter table users modify email_provider varchar(255) COLLATE utf8_general_ci  DEFAULT NULL; 
Query OK, 115 rows affected (0.06 sec) Records: 115 Duplicates: 0 Warnings: 0

然后有问题,就把 users 表整个字符集都修改了

1
mysql> alter table users convert to character set utf8 collate utf8_general_ci ;

最后还是存在字符集排序的问题,修改整个数据库中不是 utf8_general_ci 的所有表 字符集排序

9、升级11.11.8 报错

报错信息

1
2
3
4
5
6
compiling statement.c
linking shared-object mysql2/mysql2.so
/bin/ld: unrecognized option '--compress-debug-sections=zlib'
/bin/ld: use the --help option for usage information
collect2: error: ld returned 1 exit status
make: *** [mysql2.so] Error 1

排查ld依赖

1
2
3
4
5
[root@devops-gitlab-vpc ruby-2.5.0]# ld --help |grep compress
[root@devops-gitlab-vpc ruby-2.5.0]# ld -v
GNU ld version 2.25.1-32.base.el7_4.1
[root@devops-gitlab-vpc ~]# ld -v
GNU ld version 2.27-44.base.el7

注意:
1、提示无 xxx 包的时候:

1、建议采用 /opt/gitlab/embedded/bin/gem install xxxx -v x.y.z 安装
2、至于 x.y.z 应该是多少,建议先从 Gemfile.lock 中获取 
grep xxx /opt/gitlab/embedded/service/gitlab-rails/Gemfile.lock

2、过程中需要不同版本的ruby相关头文件
wget https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.0.tar.gz
wget https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.0.tar.gz
这里在不同的版本安装的时候,清理之前版本的,重新copy最新版本

3、批量更新表字符集排序

4、Gemfile 文件中mysql2 和 peek-mysql2 的配置都不带后续的group
gem ‘peek-mysql2’, ‘~> 1.1.0’, group: :mysql -> gem ‘peek-mysql2’, ‘~> 1.1.0’

10、Mysql2::Error: Incorrect string value 乱码

报错信息:

Mysql2::Error: Incorrect string value: '\xF0\x9F\x94\xA8\xE6\x89...' for column 'commit_title' at row 1: INSERT INTO `push_event_payloads` (`event_id`, `commit_count`, `ref_type`, `action`, `commit_from`, `commit_to`, `ref`, `commit_title`) VALUES (259421, 1, 0, 2, x'a2ad3a918aa4d9aac226c430b365bb5cb05f5619', x'1041d9cdfaed711af45455b65497284194aed02c', 'dev_test1', '1,修改🔨手机空布局不居中问题。')

Fix:

解决方案

1
alter table push_event_payloads change commit_title commit_title varchar(70) character set utf8mb4 collate utf8mb4_general_ci ;

11、gitlab-ctl reconfigure 卡住不动

报错信息:

Error: gitlab-ctl reconfigure 过程中如果卡主不动

解决方案:

systemctl restart gitlab-runsvdir

背景

已经通过管理账号配置好了ansible管理机到被管理机器的ssh免密登录,且sudo免密码 ,但是ansible推送时依然报错 "msg": "Missing sudo password"

错误信息如下

1
2
3
4
Escalation requires password
devops-baseimage-02-vpc | FAILED! => {
"msg": "Missing sudo password"
}

排查过程

1、因为是使用另外一个SA账号A 管理另外一个SA账号B的sa权限(ssh免密登录,且sudo免密)推送,所以确定不是ansible本身的问题

2、单独测试SA账号B的ssh权限和sudo权限都没有问题

ansible-missing-sudo-password

3、有个特殊的情况是,该SA账号B,之前使用Jumpserver的系统账号中的动态用户名 功能,该功能会根据登录用户在Linux主机上面动态创建Jumpserver系统的登录用户同名账号,该SA账号B,就是这种情况,但是在有问题的目标主机删除用户B之后, 用 SA账号A 推送 SA账号B也是成功没有问题的。但是依然报错

4、采用debug排查ansible推送过程

1
ansible devops-baseimage-02-vpc -m ping -vvvv

发现如下信息:

1
2
3
4
5
6
7
... ...
<devops-baseimage-02-vpc> ESTABLISH SSH CONNECTION FOR USER: None
<devops-baseimage-02-vpc> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=5d -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=30 -o ControlPath=/home/liuchao/.ansible/cp/b1ea8649fc -tt devops-baseimage-02-vpc '/bin/sh -c '"'"'sudo -H -S -n -u root /bin/sh -c '"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-hpnwhdajnmoqqqnewfbzhjdabvqgchqw ; /usr/bin/python /home/liuchao/.ansible/tmp/ansible-tmp-1635757129.64-233629084265576/AnsiballZ_ping.py'"'"'"'"'"'"'"'"' && sleep 0'"'"''
Escalation requires password
devops-baseimage-02-vpc | FAILED! => {
"msg": "Missing sudo password"
}

其中有个 ControlPath=/home/liuchao/.ansible/cp/b1ea8649fc 引起关注,发现这是个socket文件

1
2
[xxxx@baolei-sa-vm ~]$ ls -l /home/xxxx/.ansible/cp/b1ea8649fc
srw------- 1 xxxx xxxx 0 Nov 1 13:54 /home/xxxx/.ansible/cp/b1ea8649fc

既然是socket文件,肯定是有进程引用

1
2
3
4
[xxxx@baolei-sa-vm ~]$ ps -ef|grep b1ea8649fc
xxxx 12814 1 0 13:55 ? 00:00:00 ssh: /home/xxxx/.ansible/cp/b1ea8649fc [mux]
yyyy 28301 1 0 14:28 ? 00:00:00 ssh: /home/yyyy/.ansible/cp/b1ea8649fc [mux]
xxxx 28635 4716 0 16:59 pts/3 00:00:00 grep --color=auto b1ea8649fc

发现有两个用户共同使用这个socket文件,那么kill掉这两个进程然后测试,推送成功

1
2
3
4
5
6
7
[xxxx@baolei-sa-vm ~]$ ansible devops-baseimage-02-vpc -m ping
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details

devops-baseimage-02-vpc | SUCCESS => {
"changed": false,
"ping": "pong"
}

扩展

  • ControlMaster

是否开启单一网络共享多个 session,值可以为 no(default)/yes/ask/auto。需要和 ControlPath 配合使用,当值为 yes 时,ssh 会监听该路径下的 control socket,多个 session 会去连接该 socket,它们会尽可能的复用该网络连接而不是重新建立新的。

  • ControlPath

指定 control socket 的路径,主要是保证该参数的唯一性

  • ControlPersist

结合 ControlMaster 使用,指定连接打开后后台保持的时间。值可以为 no/yes/整数,单位 s。如果为 no,最初的客户端关闭就关闭。如果 yes/0,无限期的,直到杀死或通过其它机制

参考

SSH Config 那些你所知道和不知道的事

日常备份数据库大家都习惯使用MySQL自带的mysqldump,如果是中小数据体量的数据库,mysqldump没有问题,但是如果数据库体量很大,使用mysqldump备份就特别慢,所以这里推荐一个带指定线程数进行并发备份的工具mydumper,官网参考http://www.mysqldumper.org/, 目前已经到 0.9.1版本

1、安装

1
2
3
4
5
6
7
8
9
10
# download
wget http://mp-weixin.colinspace.com/mydumper/mydumper-0.9.1.tar.gz
# untar
tar -zxf mydumper-0.9.1.tar.gz
# cmake
cd mydumper-0.9.1
cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/mydumper

# install
make && make install

2、安装问题

本次安装是在二进制安装Percona MySQL的环境,在进行 cmake 的时候报错,提示找不到 libperconaserverclient

mydumper-cmake-error

但是该文件所在路径已经在ldconfig中配置,如下

1
2
# cat /etc/ld.so.conf.d/mysql-x86_64.conf
/opt/app/mysql/lib

且 执行 ldconfig 使其生效,但是cmake依旧报错。

这个时候有两种方式修复:
2.1、安装mysql-devel,安装之后会在系统/usr/lib64/下生成 mysql 目录,里面有 libmysqlclient.so 文件(在后面编译成功的时候就可以看到依赖这个)

1
2
3
4
5
6
7
8
9
yum install mysql-devel

[root@devops-walle-vpc mydumper-0.9.1]# ls -l /usr/lib64/mysql
total 32356
-rw-r--r-- 1 root root 23379846 Sep 7 15:50 libmysqlclient.a
lrwxrwxrwx 1 root root 20 Oct 29 09:54 libmysqlclient.so -> libmysqlclient.so.20
lrwxrwxrwx 1 root root 25 Oct 29 09:54 libmysqlclient.so.20 -> libmysqlclient.so.20.3.23
-rwxr-xr-x 1 root root 9703280 Sep 7 15:50 libmysqlclient.so.20.3.23
-rw-r--r-- 1 root root 46558 Sep 7 15:50 libmysqlservices.a

2.2、受上面的启发,知道Cmake的时候会找/usr/lib64 或者 /lib64,所以在这两个目录下,做/opt/app/mysql/lib/libperconaserverclient.so.20.3.16的软连

1
2
ln -s /opt/app/mysql/lib/libperconaserverclient.so.20.3.16 /lib64/libperconaserverclient.so
ln -s /opt/app/mysql/lib/libperconaserverclient.so.20.3.16 /usr/lib64/libperconaserverclient.so

之后cmake编译成功

mydumper-cmake-success

最后安装

mydumper-make-install

3、用法介绍

可以通过mydumper --helpmyloader --help 查看具体的配置选项,这里不做赘述,只说明日常用到的参数

mydumper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-h/--host 
-u/--user
-p/--password
-P/--port

-B/--database 需要备份的数据库
-T/--tables-list 逗号分割的表清单

-m/--no-schemas 不备份表的结构
-d/--no-data 不备份表的数据

-G/--triggers 备份触发器
-E/--events 备份events事件
-R/--routines 备份存储过程和函数

-c/--compress 压缩备份结果文件
-v/--verbose 备份结果日志输出 默认是2 warnings,建议是3 info
-t/--threads 备份启用的线程数 默认是4
-o/--outputdir 备份存放的目录,该目录下每个表单独一个文件,如果启用-c则为独立的压缩文件
-L/--logfile 备份过程的日志文件
myloader

-h/-u/-p/-P/-v/-t 与上面的一致

1
2
- -d/--directory 数据库备份的目录,从这个目录进行数据恢复
- -o/--overwrite-tables 如果表存在,则先清理表

4、实例案例

针对实际的数据库做备份和恢复,太简单这里就不写实际的脚本了

备份数据库
  • 常规备份

mydumper -h localhost -u root -p passwd -B dbanme -c -t 6 -v 3 -o /data/backup/dbname -L /data/backup/dbname-mydumper.log

  • 备份所有数据库

mydumper -h localhost -u root -p passwd -c -t 6 -v 3 -o /data/backup/dbinstance -L /data/backup/dbinstance-mydumper.log

  • 备份指定的表(或者多表, 多个表之间逗号分割)

mydumper -h localhost -u root -p passwd -B dbanme -T table1,table2 -c -t 6 -v 3 -o /data/backup/dbname -L /data/backup/dbname-mydumper.log

  • 带存储过程触发器的备份

mydumper -h localhost -u root -p passwd -B dbanme -G -R -c -t 6 -v 3 -o /data/backup/dbname -L /data/backup/dbname-mydumper.log

恢复数据库
  • 常规恢复

myloader - localhost -u root -p passwd -B dbanme -d /data/backup/dbname -o -v 3

  • 恢复到指定的数据库,备份的是dbsource,恢复到 dbtarget

myloader - localhost -u root -p passwd -B dbtarget –source-db=dbsource -d /data/backup/dbname -o -v 3

附加

mydumper 原理图

mydumper-原理图

etcd介绍

etcd是CoreOS团队于2013年6月发起的开源项目,它的目标是构建一个高可用的分布式键值(key-value)数据库。etcd内部采用raft协议作为一致性算法,etcd基于Go语言实现。

etcd的有点有以下:

  • 简单:安装配置简单,而且提供了HTTP API进行交互,使用也很简单
  • 安全:支持SSL证书验证
  • 快速:根据官方提供的benchmark数据,单实例支持每秒2k+读操作
  • 可靠:采用raft算法,实现分布式系统数据的可用性和一致性

etcd 项目详见:https://github.com/coreos/etcd/

安装之前

一般不建议root安装业务中间件服务,故这里需要先进行环境初始化

主机IP etcd name
192.168.2.213 etcd1
192.168.2.214 etcd2
192.168.2.215 etcd3

新增系统用户etcd

1
2
groupadd --system etcd
useradd -s /sbin/nologin -m /var/lib/etcd --system -g etcd etcd

新增配置文件路径

1
2
mkdir /etc/etcd
chown -R etcd:etcd /etc/etcd

修改 SELinux 为 disabled

1
2
3
setenforce 0

sed -i 's/^SELINUX=.*/SELINUX=disabled/g' /etc/selinux/config

安装

下载二进制安装包

1
curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest | grep browser_download_url | grep linux-amd64 |cut -d '"' -f 4 | wget -qi -

或者百度网盘下载

链接: https://pan.baidu.com/s/1xOeIYWTAs0VMcLoCuT6BTg 提取码: 5kmf

如下进入安装

1
2
3
4
tar -zxvf etcd-v3.2.32-linux-amd64.tar.gz
cd etcd-v3.2.32-linux-amd64
cp etcd etcdctl /usr/local/bin
chown -R etcd:etcd /usr/local/bin/etcd*

验证安装

1
2
3
4
5
6
7
8
9
root@pts/1 $ etcd --version
etcd Version: 3.2.32
Git SHA: 7dc07f2a9
Go Version: go1.12.17
Go OS/Arch: linux/amd64

root@pts/1 $ etcdctl --version
etcdctl version: 3.2.32
API version: 2

配置和启动

1、三台主机分别配置/etc/hosts如下

1
2
3
4
# etcd
192.168.2.213 etc1
192.168.2.214 etc2
192.168.2.215 etc3

2、三台主机配置新增配置文件/etc/etcd/etcd.conf 这里注意 etcd1 配置中 ETCD_INITIAL_CLUSTER_STATEnew 其余两个为exist
另外参数ETCD_LISTEN_CLIENT_URLSETCD_ADVERTISE_CLIENT_URLSETCD_LISTEN_PEER_URLSETCD_INITIAL_ADVERTISE_PEER_URLS 分别为当前主机的IP,ETCD_NAME 根据开始的定义配置

1
2
3
4
5
6
7
8
9
10
11
12
# member
ETCD_NAME=etcd1
ETCD_DATA_DIR=/var/lib/etcd
ETCD_LISTEN_CLIENT_URLS=http://192.168.2.213:2379,http://127.0.0.1:2379
ETCD_ADVERTISE_CLIENT_URLS=http://192.168.2.213:2379

# cluster
ETCD_LISTEN_PEER_URLS=http://192.168.2.213:2380
ETCD_INITIAL_ADVERTISE_PEER_URLS=http://192.168.2.213:2380
ETCD_INITIAL_CLUSTER=etcd1=http://192.168.2.213:2380,etcd2=http://192.168.2.214:2380,etcd3=http://192.168.2.215:2380
ETCD_INITIAL_CLUSTER_STATE=new
ETCD_INITIAL_CLUSTER_TOKEN=k8s_etcd

3、三台主机配置systemctl启动方式

/usr/lib/systemd/system/etcd.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Unit]
Description=etcd key-value store
Documentation=https://github.com/etcd-io/etcd
After=network.target

[Service]
User=etcd
Type=notify
WorkingDirectory=/var/lib/etcd/
EnvironmentFile=/etc/etcd/etcd.conf
ExecStart=/usr/local/bin/etcd
Restart=on-failure
RestartSec=10s
LimitNOFILE=40000

[Install]
WantedBy=multi-user.target

启动服务

1
2
3
systemctl enable etcd.service
systemctl start etcd.service
systemctl status etcd.service

测试和验收

1、检查各节点

1
2
3
4
root@pts/1 $ etcdctl member list
59b0ee3829e2b866: name=etcd2 peerURLs=http://192.168.2.214:2380 clientURLs=http://192.168.2.214:2379 isLeader=false
a8b07bac1693e30e: name=etcd1 peerURLs=http://192.168.2.213:2380 clientURLs=http://192.168.2.213:2379 isLeader=false
cf682ab5655702b8: name=etcd3 peerURLs=http://192.168.2.215:2380 clientURLs=http://192.168.2.215:2379 isLeader=true

2、检查集群状态

1
2
3
4
5
root@pts/1 $ etcdctl cluster-health
member 59b0ee3829e2b866 is healthy: got healthy result from http://192.168.2.214:2379
member a8b07bac1693e30e is healthy: got healthy result from http://192.168.2.213:2379
member cf682ab5655702b8 is healthy: got healthy result from http://192.168.2.215:2379
cluster is healthy

3、进行测试

1
2
3
4
5
root@pts/1 $ etcdctl set /demo 'hello etcd'
hello etcd

root@pts/1 $ etcdctl get /demo
hello etcd

故障演练

停掉Leader节点

1
2
3
4
root@pts/1 $ etcdctl member list
59b0ee3829e2b866: name=etcd2 peerURLs=http://192.168.2.214:2380 clientURLs=http://192.168.2.214:2379 isLeader=false
a8b07bac1693e30e: name=etcd1 peerURLs=http://192.168.2.213:2380 clientURLs=http://192.168.2.213:2379 isLeader=true
cf682ab5655702b8: name=etcd3 peerURLs=http://192.168.2.215:2380 clientURLs=http://192.168.2.215:2379 isLeader=false

看到Leader从etcd3转移到etcd1

查看集群状态

1
2
3
4
5
6
root@pts/1 $ etcdctl cluster-health
member 59b0ee3829e2b866 is healthy: got healthy result from http://192.168.2.214:2379
member a8b07bac1693e30e is healthy: got healthy result from http://192.168.2.213:2379
failed to check the health of member cf682ab5655702b8 on http://192.168.2.215:2379: Get http://192.168.2.215:2379/health: dial tcp 192.168.2.215:2379: connect: connection refused
member cf682ab5655702b8 is unreachable: [http://192.168.2.215:2379] are all unreachable
cluster is healthy

看到etcd3连接失败,这个时候在执行如下命令进行测试,发现请求正常

1
2
root@pts/1 $ etcdctl get /demo
hello etcd

然后启动故障节点之后检查,集群状态恢复

1
2
3
4
5
root@pts/1 $ etcdctl cluster-health
member 59b0ee3829e2b866 is healthy: got healthy result from http://192.168.2.214:2379
member a8b07bac1693e30e is healthy: got healthy result from http://192.168.2.213:2379
member cf682ab5655702b8 is healthy: got healthy result from http://192.168.2.215:2379
cluster is healthy

今天在做数据分析的时候,需要对原始文件做排重处理,网上看到一个方式如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
root@localhost cc]# cat 2.txt
adc 3 5
a d a
a 3 adf
a d b
a 3 adf

去重第一列重复的行:

[root@localhost cc]# cat 2.txt |awk ‘!a[$1]++{print}‘
adc 3 5
a d a

重复的行取最上面一行记录

去重以第一列和第二列重复的行:

[root@localhost cc]# cat 2.txt |awk ‘!a[$1" "$2]++{print}‘
adc 3 5
a d a
a 3 adf

但是按照awk模式理解,上面的语法是没有用到BEGINEND, 那就是主体block, 但是主体block 的语法为 /pattern/ {command} ,有感觉不符合


分析

1、上面语句中的{print} 默认是打印所有符合条件的列, 是 print $0 的简写,

2、a[$1]++ 拆分开来看,a[$1] 是把第一列作为数组下标,也就是 a[$1] 的结果是个数字 这样才能执行后面的++ 操作

3、既然如此,那就改造语句执行如下

1
2
3
4
5
6
7
8
9
10
11
root@pts/0 $ cat repeat.txt |awk '{print a[$1]++, $0}'
0 adc 3 5
0 a d a
1 a 3 adf
2 a d b
3 a 3 adf

root@pts/0 $ cat repeat.txt |awk 'a[$1]++{print}'
a 3 adf
a d b
a 3 adf

4、从上面的对比结果发现,非0的结果被输出,都是第一列出现重复的,也就是存在 条件判断 ? 做以下改造验证

1
2
3
4
root@pts/0 $ cat repeat2.txt |awk '{if (a[$1]++ > 0)print}'
a 3 adf
a d b
a 3 adf

和第3步的结果对比发现,确实做了条件判断

5、所以问题现在定位在两个方面,最主要的是数组中第一次出现的元素的值为何是0

6、进过排查,针对awk数组,有个特性是:

1、直接引用一个数组中不存在的元素时,awk会自动创建这个元素,并且为其赋值为"空字符串"

2、awk中,当变量a的值为字符串时,也可以进行加法运算,如果字符串参与运算,字符串将被当做数字0进行运算

3、空字符串也是字符串,参与运算时,也会被当做数字0进行运算

验证:

直接引用一个数组中不存在的元素时,awk会自动创建这个元素,并且为其赋值为”空字符串”

awk-not-exist-default-null.png

awk中,当变量a的值为字符串时,也可以进行加法运算,如果字符串参与运算,字符串将被当做数字0进行运算

awk-str-compute-as-zero.png

空字符串也是字符串,参与运算时,也会被当做数字0进行运算

awk-str-not-exist-and-compute.png

综上所述

1、条件判断是可以放到主体block{} 外面的
2、a[$1]++ 遇到第一个出现的记录时,其实就是数组中不存在的一个元素,其默认是为空,计算是被当做0

所以上面的最上面例子中的语句等价于

1
2
3
4
5
6
7
8
9
root@pts/0 $ awk 'a[$1]++>0{print}' repeat.txt
a 3 adf
a d b
a 3 adf

root@pts/0 $ awk '{if(a[$1]++>0) print}' repeat.txt
a 3 adf
a d b
a 3 adf

refer:
[1] http://www.mamicode.com/info-detail-1616775.html
[2] https://www.jianshu.com/p/cae3cccd2ee6

有时候在开发博客或者内部系统的时候,想在首页展示一些动态信息,比如在自己博客的首页每天展示一句不同的英语,是不是很有意思呢

今天这个demo带领大家熟悉常见的几个提供英语每日一句 的API, 目前支持 有道词典扇贝单词

知识点:

  • 熟悉了解 有道词典扇贝单词获取每日一句的开放API
  • 用户可以选择获取 有道词典扇贝单词 或者其他,可以封装一个统一的类,比如 OneStatementOfEnglish
  • 设计到选择,除了if...else... 之外,就是switch...case... 但是Python不支持switch...case... ,我们可以通过类的 调度方法 来实现
  • 附加: switch...case... 可以通过 字典 来实现,自己思考哈

话不多述,直接上代码

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/8/24 5:15 下午
# @Author : Colin.Liu
# @Email : colin50631@gmail.com
# @File : demo.py
# @Software : PyCharm


import arrow
import requests


class OneStatementOfEnglish:
"""
英语每日一句
目前支持:
+ 有道
+ 扇贝
"""
_today = arrow.now().format('YYYY-MM-DD')

def switchToCase(self, case):
# print("case: ", case)
func_name = "case_" + str(case)
# print("func_name: ", func_name)
method = getattr(self, func_name, self.switchToCase)
return method

def case_youdao(self):
yd_url = "https://dict.youdao.com/infoline?mode=publish&date=" + self._today + "&update=auto&apiversion=5.0"
result = {}
for record in requests.get(yd_url).json()[self._today]:
if record['type'] == '壹句':
result['date'] = self._today
result['content'] = record['title']
result['translation'] = record['summary']

break
return result

def case_shanbay(self):
sb_url = "https://apiv3.shanbay.com/weapps/dailyquote/quote/?date=" + self._today
result = {}
record = requests.get(sb_url).json()

result['date'] = self._today
result['content'] = record['content']
result['translation'] = record['translation']

return result


if __name__ == "__main__":
ones = OneStatementOfEnglish()
youdao = ones.switchToCase("youdao")()
print("有道词典: ", youdao)

shanbay = ones.switchToCase("shanbay")()
print("扇贝单词:", shanbay)

结果呈现

1
2
有道词典:  {'date': '2020-08-24', 'content': 'Work smarter, not harder.', 'translation': '做事得聪明点,不能光靠一股蛮力苦干。'}
扇贝单词: {'date': '2020-08-24', 'content': 'Encourage yourself, believe in yourself and love yourself. Never doubt who you are.', 'translation': '你要鼓励自己、相信自己、爱自己。永远别怀疑你是谁。'}

前置

之前在Linux下shell编程的时候,为了控制报错和告警等信息显示不同的颜色示警,使用终端颜色控制,例如如下所示

'\033[4;31;47m 带下划线的白色背景红色提示\033[0m'

效果如下

shell-terminal-color-control


在Python应用颜色控制的时候,开始也是用的shell条用方式,比如 print('\033[4;31;47m 带下划线的白色背景红色提示\033[0m')
后来想着自己写一套这样的公共类,又怕重复造轮子,网上找了找发现已经有线程的,那就哪来使用

安装

pip install colorama

Fore是针对字体颜色,Back是针对字体背景颜色,Style是针对字体格式

Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET.
Back: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET.
Style: DIM, NORMAL, BRIGHT, RESET_ALL

demo

demo 脚本下载

demo1

发现第二行开始把前面的颜色控制也集成过来了,不是我们想要的结果

demo2

发现最终添加 Style.RESET_ALL 之后虽然执行 reset_all 后面的是恢复了默认,但是之前的还是集成了。 不行

demo3

最后发现 脚本前面添加 init(autoreset=True) 可以完美解决

参考


附加

shell终端颜色控制说明,拿上面的例子说明:

结尾的 \033[0m 是恢复终端默认
开头的 \033[ 是颜色控制的开始
介于两种中间的分别代表的是 前景色背景色显示的方式

'\033[5;31;47m综合打印\033[0m'

前景色和背景色说明

前景色 背景色 颜色
30 40 黑色
31 41 红色
32 42 绿色
33 43 黃色
34 44 洋红
36 46 青色
37 47 白色

控制方式说明

显示方式 意义
0 终端默认设置
1 高亮显示
22 非高亮显示
4 使用下划线
24 去下划线
5 闪烁
25 去闪烁
7 反白显示
27 非反显
8 不可见
28 可见

Linux 下颜色控制使用可以参考
https://www.jianshu.com/p/ba1b8aded634

说明:

location 中的 root 和 alias

  • root 指令只是将搜索的根设置为 root 设定的目录,即不会截断 uri,而是使用原始 uri 跳转该目录下查找文件
  • aias 指令则会截断匹配的 uri,然后使用 alias 设定的路径加上剩余的 uri 作为子路径进行查找

location 中的 proxy_pass 的 uri

  • 如果 proxy_pass 的 url 不带 uri

    • 如果尾部是”/“,则会截断匹配的uri
    • 如果尾部不是”/“,则不会截断匹配的uri
  • 如果proxy_pass的url带uri,则会截断匹配的uri

阅读全文 »

在实际工作中日志检索,简单数据分析等会遇到记录中存在Tab键\t的情况,默认grep 'xxx\tyyy input-filename` 会失效


解决办法有以下两种方式

-P perl模式

root@pts/4 $ grep -P '28\t1\tchr01' search.txt
28    1    chr01    280000    3.052

‘$’ 模式

其实是用'$'\t' 代替了 \t

root@pts/4 $ grep '28'$'\t1'$'\tchr01'$'\t' search.txt
28    1    chr01    280000    3.052

通过观察比较,采用-P的方式更简洁方便些