ansible安装k8s步骤及注意事项(在线安装)

以下步骤都经本人实测,可以完美运行。

官方参考网址:https://github.com/gjmzj/kubeasz/

Ansible了解

ansible是个什么东西呢?官方的title是“Ansibleis Simple IT Automation”——简单的自动化IT工具。这个工具的目标有这么几项:

自动化部署APP;

自动化管理配置项;

自动化的持续交互;

自动化的(AWS)云服务管理;

所有的这几个目标从本质上来说都是在一个台或者几台服务器上,执行一系列的命令而已。通俗的说就是批量的在远程服务器上执行命令。当然,最主要的是它是基于 paramiko 开发的。这个paramiko是什么呢?它是一个纯Python实现的ssh协议库。因此fabric和ansible还有一个共同点就是不需要在远程主机上安装client/agents,因为它们是基于ssh来和远程主机通讯的。

未分类

由上面的图可以看到 Ansible 的组成由 5 个部分组成:

  • Ansible : 核心

  • Modules : 包括 Ansible 自带的核心模块及自定义模块

  • Plugins : 完成模块功能的补充,包括连接插件、邮件插件等

  • Playbooks : 剧本;定义 Ansible 多任务配置文件,由Ansible 自动执行

  • Inventory : 定义 Ansible 管理主机的清单

具体的命令可以查看参考(中文权威指南):http://www.ansible.com.cn/docs/intro_installation.html

安装k8s

1. 安装依赖工具

# 文档中脚本默认均以root用户执行# 安装 epel 源并更新
yum install epel-release –y(最好每台机器装一下)
yum install net-tools
yum update# 删除不要的默认安装
yum erase firewalld firewalld-filesystem python-firewall -y# 安装python
yum install python -y

2. 安装ansible

# CentOS 7
yum install git python-pip -y# pip安装ansible(国内如果安装太慢可以直接用pip阿里云加速)#pip install pip --upgrade#pip install ansible
pip install pip --upgrade -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
pip install --no-cache-dir ansible -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com

3. 在deploy节点配置免密码登陆

ssh-keygen -t rsa -b 2048 回车 回车 回车
ssh-copy-id $IPs #$IPs为所有节点地址包括自身,按照提示输入yes 和root密码
(即使是本机也需要配置)

4. 在deploy节点编排k8s安装

# 下载项目文件
git clone https://github.com/gjmzj/kubeasz.git
mv kubeasz /etc/ansible# 下载已打包好的binaries,并且解压缩到/etc/ansible/bin目录# 国内请从我分享的百度云链接下载 https://pan.baidu.com/s/1c4RFaA # 如果你有合适网络环境也可以按照/down/download.sh自行从官网下载各种tar包到 ./down目录,并执行download.sh
tar zxvf k8s.191.tar.gz
mv bin/* /etc/ansible/bin
cd /etc/ansible
cp example/hosts.m-masters.example hosts# 根据上文实际规划修改此hosts文件
vi hosts

验证ansible安装

在deploy 节点使用如下命令

ansible all -m ping

如果配置正确可以看到类似输出:

192.168.1.42 | SUCCESS => {
    "changed": false, 
    "failed": false, 
    "ping": "pong"
}
192.168.1.43 | SUCCESS => {
    "changed": false, 
    "failed": false, 
    "ping": "pong"
}
192.168.1.44 | SUCCESS => {
    "changed": false, 
    "failed": false, 
    "ping": "pong"
}

5. 安装集群

注意:

1)如何重复安装时,注意在执行完卸载命令后,将/etc/kubernetes目录删除

2)安装02.etcd时往往不能够一次性成功安装,如何执行过程中有错误应执行第二遍02.etcd.yml(不管使用90.setup.yml或是02.etcd.yml安装)

#ansible-playbook 01.prepare.yml
#ansible-playbook 02.etcd.yml
#ansible-playbook 03.kubectl.yml
#ansible-playbook 04.docker.yml
#ansible-playbook 05.kube-master.yml
#ansible-playbook 06.kube-node.yml
#ansible-playbook 07.calico.yml 或者 ansible-playbook 07.flannel.yml 只能选择一种网络插件
#ansible-playbook 90.setup.yml # 一步安装
#ansible-playbook 99.clean.yml # 一步安装

source /etc/profile(环境变量设置未生效,kubectl工具不能使用)

自动安装时没有环境变量,需要source一下;

单主单节点

发现问题:ansible1.8在重新安装并没有完全删除掉/etc/kubernetes中的东西,1.9没啥问题,所以1.8需要将/etc/kubernetes中的东西全部删掉再运行;否则在安装06node时csr验证会报错。

还有装02.etcd时配置文件读不到的问题,运行两次就好了;

ansible 1.9没有问题

安装DNS/dashboard时,注意dashboard-controller.yaml中deployment的版本为extensions/v1beta1

规律,可以先执行90.setup,然后执行2.etcd

卸载的时候注意删除掉/etc/kubernetes目录

注意:api-server是以https(用户名密码启动的),所以最后需要为用户配置角色才可以通过url进行访问:

绑定api-server的权限(否则是注册不上的)

kubectlcreate clusterrolebinding kubelet-node-clusterbinding1 --clusterrole=cluster-admin--user=admin

使用 Ansible 自动部署项目

未分类

如今部署代码真的是一件天大的事。

开发人员努力敲代码,基本没有时间登录服务器并逐个运行部署脚本。但是开发人员知道他们的部署所需要花费的大概时间,对部署各个工作的轻重缓急十分了解。

因此,开发人员与运维人员一起参与部署工作将真正有助于产品发布成功。

未分类

下面我们谈谈用Git的方式更轻松的部署应用。

使用 Git 来自动部署

如果让开发人员使用自己的 Git 仓库工具做部署,这必须在Git配置文件中添加额外的远程Git 仓库地址。

就像这样:

[remote "origin"]
    url = [email protected]:company/project.git
    url = git@remote-server:project.git
    fetch = +refs/heads/*:refs/remotes/origin/*

或者这样

[remote "deploy"]
    url = git@remote-server:project.git      

开发人员只需要在同一时间在推送代码到不同的地方就可以了。如代码示例,一个放在Codebase,另一个用于生产环境部署。

下面是具体操作

我们只需要配置我们的git服务器,使得每当 git push 事件触发时,Git 服务器就远程或本地自动运行部署脚本。

所以,我们需要先配置git 服务器。

首先在 git 服务器上安装 git-core

sudo apt-get install git-core

然后我们需要创建一个用于Git操作用户

sudo useradd git
passwd git

然后添加开发人员的主机的公钥文件,以便开发人员的电脑能够有权限访问 Git 服务器。

cat〜/ .ssh / id_rsa.pub | ssh git @ remote-server“mkdir -p〜/ .ssh && cat >>〜/ .ssh / authorized_keys”

然后在Git服务器上创建一个空的仓库

mkdir -p /home/user/project.git
cd /home/swapnil/project-1.git
git init --bare

这将创建一个git仓库,您可以看到名为 hooks文件夹,这里会存脚本用于接收文件后自动部署项目。

开发人员只需要在最开始的时候将 Git 服务器的项目仓库地址添加到他的项目中即可。

git remote add deploy git @ remote-server:project.git
git push deploy master

这样就直接部署了开发人员的代码。不过这似乎浪费了开发人员不必要的时间,所以我们应该使用 Ansible 来让部署更加轻松。

未分类

首先运行下列命令

ansible-galaxy install PrabhuVignesh.push_to_deploy

然后将开发机器的公钥保存在中即可。

$ cat publickeys.txt

ssh-rsa AAuyFUVFY65tYTFV567YTYTytytYTYTfyuV56FUYTVYTFYTVyTFYUG877686V767R76R67R76R6YTYTfyuVFUYTVYTFYTVyTFYUG877686V767R76R67R76R676V767676V76V768V876V76576V768V876V76Vv76V876VB76v7V76V76V6VBT9LLlNGtLo5pnEXIOPiz9X42ZdxBD721bG5XqDfPnz0JfgAYl6Zw4CXM0F6q0jiAQNJrtrfSNS92x1KMLY8CcVLKOZbpWdUHnouLdKCeM6dBHStpX7yjlb90fRKVZFch87eO0dyAoGWS3oBEYttFYL7s5dm/QV [email protected]

ssh-rsa AAuyFUVFY65tYTFV567YTYTytytYTYTfyuV56FUYTVYTFYTVyTFYUG877686V767R76R67R76R6YTYTfyuVFUYTVYTFYTVyTFYUG877686V767R76R67R76R676V767676V76V768rrtrt54t46Vv76V876VB76v7V76V76V6VBT9LLlNGtLo5pnEXIOPiz9X42ZdxBD721bG5XqDfPnz0JfgAYl6Zb6sevtrrtyyw4CXM0F6q0jiAQNJrtrfSNS92x1KMLY8CcVLKOZbpWdUHnouLdKCeM6dBHStpX7yjlb90fRKVZFch87eO0dyAoGWS3oBEYttFYL7s5dm/QV [email protected]

ssh-rsa AAuyFUVFY65tYTFV567YTYTytytYTYTfyuV56FUYTVYTFYTVyTFYUG877686V767R76R67R76R6YTYTfyuVFUYTVYTFYTVyTFYUG877686V767R76R67R76R63435343r4r3453434r34r34f34356trfhgjhgkjmnhi87t665rg6dvrdxdcser536c5456yvu675678br56er56v5v7y5y5gbr5576un8n78i8it0F6q0jiAQNJrtrfSNS92x1KMLY8CcVLKOZbpWdUHnouLdKCeM6dBHStpX7yjlb90fRKVZFch87eO0dyAoGWS3oBEYttFYL7s5dm/QV [email protected]

接着在环境变量中添加 path_for_authorized_keys,git_repository_path,post_receive_script,pre_receive_script 中的路径。

---
  roles:
    - role: PrabhuVignesh.push_to_deploy
      path_for_authorized_keys: /path/to/public_key/file
      git_repository_path: /home/path/to/your/repo.git
      post_receive_script: "script to deploy the code"
      pre_receive_script: "Prepare storing code"

git服务器的所有配置将通过 Ansible 来完成。

当开发人员将代码推送到 git 服务器时,应用程序就会执行用于后接收和预接收运行的脚本,如“puppet deploy script”,“cap deploy …”等,并且将自动部署。

另外使用这样的方式,只有在推送Master分支的时候才会运行部署脚本,这就避免不必要的部署。

#!/bin/bash
do
    if [[ $ref =~ .*/master$ ]];
    then
        echo "I am master branch push and i will run deployment script"
        # Run deployment script.....
    else
        echo "Ref $ref successfully received.  Doing nothing: only the master branch may be deployed on this server."
    fi
done

结论

GIT真的是一个令人赞叹的IT自动化工具,特别让开发和运维更好的协作,让发布新版本更加轻松。

Ansible 批量创建用户 密码注意事项

user模块是请求的是useradd, userdel, usermod三个指令,goup模块请求的是groupadd, groupdel, groupmod 三个指令。

user模块

home:指定用户的家目录,需要与createhome配合使用

groups:指定用户的属组

uid:指定用的uid

password:指定用户的密码

name:指定用户名

createhome:是否创建家目录 yes|no

system:是否为系统用户

remove:当state=absent时,remove=yes则表示连同家目录一起删除,等价于userdel -r

state:是创建还是删除(present,absent) 

shell:指定用户的shell环境

generate_ssh_key:是否为相关用户生成SSH密钥。 这不会覆盖现有的SSH密钥。 

ssh_key_bits:可选择指定要创建的SSH密钥中的位数。

ssh_key_passphrase:设置SSH密钥的密码。 如果没有提供密码,SSH密钥将默认没有密码。 

ssh_key_file:指定SSH密钥文件名(可选)。 如果这是一个相对的文件名,那么它将是相对于用户的主目录。 

ssh_key_type:指定要生成的SSH密钥的类型(可选)。 可用的SSH密钥类型将取决于目标主机上的实现。

使用ansibile创建用户有两种方法

方法1:使用user 模块,更简单,命令如下:

[root@ansible ~]# ansible ansible_group -m user -a 'name=test password=-18SyrVeFt/xU uid=1000 shell=/bin/bash home=/home/admin_group/test group=admin_group state=present' --sudo

验证创建的用户:

[root@ansible ~]# ansible ansible_group -m raw -a 'tail /etc/passwd|grep test'

online-web-asset-3 | SUCCESS | rc=0 >>
test:x:1000:501::/home/admin_group/test:/bin/bash
Shared connection to 192.168.5.1 closed.

注意:passwd的值不能是明文,passwd关键字后面应该是密文,且密文将被保存在/etc/shadow文件中,密文的生成命令为:

[root@ansible ~]# openssl passwd -salt -1 "you passwd"

然后将生成的密文,如: 填写到ansible ansible_group -m user -a ‘name=username password= ’ 中的passwd关键字后面即可。

方法2:使用playbook

创建playbook文件 useradd.yml,内容如下:

[root@ansible ansible]# more useradd.yml 

---
- hosts: "{{hosts}}"
  gather_facts: false
  user: centos
  sudo: yes
  vars:
#    user: test123
  tasks:
    - name: add user
      user: name={{user}} password={{passwd}} home=/home/admin_group/{{user}} group=admin_group shell=/bin/bash
      state: present
  tags:
    - user

保存后执行如下命令检测:

[root@ansible ~]# ansible-playbook --syntax-check useradd.yml -e "hosts=qsh_test user=test1 passwd=-11Fe1z.bZ5o."

然后运行playbook,使用 -e选项传入参数

[root@ansible ~]# ansible-playbook useradd.yml -e "hosts=qsh_test user=test1 passwd=-11Fe1z.bZ5o."

gitlab 403 forbidden 并发引起ip被封问题解决方法

步骤:

  • 打开/etc/gitlab/gitlab.rb文件。
  • 查找gitlab_rails[‘rack_attack_git_basic_auth’]关键词。
  • 取消注释
  • 修改ip_whitelist白名单属性,加入Gitlab部署的IP地址。
gitlab_rails[‘rack_attack_git_basic_auth’] = {
‘enabled’ => true,
‘ip_whitelist’ => [“127.0.0.1″,”Gitlab部署的IP地址”],
‘maxretry’ => 300,
‘findtime’ => 5,
‘bantime’ => 60
}
  • 配置好后,执行gitlab-ctl reconfigure即可。

修复群晖 GitLab 升级失败的问题

如果你最近使用群晖套件中心的 GitLab( 9.4.4-0050 ) ,并且勾选转换数据库( MariaDB => postgresql ),那么你很可能遇到丢失数据的问题( Version: 10.6.4-0051 ).表现为打开 GitLab 首页提示设置 root 密码,登录后台显示所有项目丢失.

故障的原因是群晖”借鉴”的 MySQL to PostgreSQL Converter 项目存在一些问题 .群晖内的具体路径为/volume1/@appstore/Docker-GitLab/mysql-postgresql-converter/db_converter.py,转换时会发生UnicodeDecodeError: ‘utf8’ codec can’t decode byte 0x9c错误.截至目前尚有 30 个 PR 未处理.群晖科技在自家稳定版产品中未确认方案普适性,原项目首页强调 use with care 被无视,置客户数据安全于不顾,最终导致悲剧发生.后续我会专门谈一谈为什么 synology NAS 是危险的产品,选购群晖是对数据的不负责行为.本篇只说怎么找回你宝贵的数据.

修改 docker 环境变量,使用原 MariaDB 数据库

1.使用 admin 组的账户登录群晖 SSH .警告: 如果你不知道这步怎么操作,本修复指南可能对你来说较复杂,极易出现数据丢失.请向技术人员求助.

2.如果之前没有执行过备份( bundle exec rake gitlab:backup:create RAILS_ENV=production ),那么执行下备份.路径类似/volume1/docker/gitlab/.

3.为 gitlab_user 授权.如果不熟悉 SQL 可以在套件中心中安装 phpMyAdmin ,然后在权限中增加用户账户的 host name 为 172.17.0.% .

4.测试数据库权限:

docker exec -it synology_gitlab bash  # 进入容器终端
mysql -u gitlab_user -p -h 172.17.0.1 -P 3307  # 测试数据库链接情况
exit  # 退出容器

5.套件中心 => 已安装 => GitLab => 停止.

6.docker => synology_gitlab => 编辑 => 环境变量

DB_PORT 3307
DB_TYPE mysql
DB_HOST 172.17.0.1

7.套件中心 => 已安装 => GitLab => 启动.

修复数据库故障

GitLab 官方明确表示不推荐使用 MariaDB / MySQL 数据库. 群晖由于某种原因仍然在使用 MariaDB 10 .本部分重点解决各类数据库问题.

1.停止 GitLab 服务service gitlab stop.
2.登录 MariaDB 或直接使用 phpMyAdmin .让数据库支持长索引.

use gitlab;
SET storage_engine=INNODB;
SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_large_prefix=1;
SET GLOBAL log_bin_trust_function_creators = 1;

3.转换原有数据使用 utf8mb4 编码.注意下面的命令每一条的回显都要作为 SQL 语句再执行.

SELECT CONCAT('ALTER TABLE `', TABLE_NAME,'` ROW_FORMAT=DYNAMIC;') AS 'Copy & run these SQL statements:' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA="gitlab" AND TABLE_TYPE="BASE TABLE" AND ROW_FORMAT!="Dynamic";

回显类似

ALTER TABLE `abuse_reports` ROW_FORMAT=DYNAMIC;
ALTER TABLE `appearances` ROW_FORMAT=DYNAMIC;
ALTER TABLE `application_settings` ROW_FORMAT=DYNAMIC;
ALTER TABLE `audit_events` ROW_FORMAT=DYNAMIC;
ALTER TABLE `award_emoji` ROW_FORMAT=DYNAMIC;
ALTER TABLE `boards` ROW_FORMAT=DYNAMIC;
ALTER TABLE `broadcast_messages` ROW_FORMAT=DYNAMIC;
ALTER TABLE `chat_names` ROW_FORMAT=DYNAMIC;
ALTER TABLE `chat_teams` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_build_trace_section_names` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_build_trace_sections` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_builds` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_group_variables` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_job_artifacts` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_pipeline_schedule_variables` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_pipeline_schedules` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_pipeline_variables` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_pipelines` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_runner_projects` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_runners` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_stages` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_trigger_requests` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_triggers` ROW_FORMAT=DYNAMIC;
ALTER TABLE `ci_variables` ROW_FORMAT=DYNAMIC;
ALTER TABLE `cluster_platforms_kubernetes` ROW_FORMAT=DYNAMIC;
ALTER TABLE `cluster_projects` ROW_FORMAT=DYNAMIC;
ALTER TABLE `cluster_providers_gcp` ROW_FORMAT=DYNAMIC;
ALTER TABLE `clusters` ROW_FORMAT=DYNAMIC;
ALTER TABLE `clusters_applications_helm` ROW_FORMAT=DYNAMIC;
ALTER TABLE `container_repositories` ROW_FORMAT=DYNAMIC;
ALTER TABLE `conversational_development_index_metrics` ROW_FORMAT=DYNAMIC;
ALTER TABLE `deploy_keys_projects` ROW_FORMAT=DYNAMIC;
ALTER TABLE `deployments` ROW_FORMAT=DYNAMIC;
ALTER TABLE `emails` ROW_FORMAT=DYNAMIC;
ALTER TABLE `environments` ROW_FORMAT=DYNAMIC;
ALTER TABLE `events` ROW_FORMAT=DYNAMIC;
ALTER TABLE `feature_gates` ROW_FORMAT=DYNAMIC;
ALTER TABLE `features` ROW_FORMAT=DYNAMIC;
ALTER TABLE `fork_network_members` ROW_FORMAT=DYNAMIC;
ALTER TABLE `fork_networks` ROW_FORMAT=DYNAMIC;
ALTER TABLE `forked_project_links` ROW_FORMAT=DYNAMIC;
ALTER TABLE `gcp_clusters` ROW_FORMAT=DYNAMIC;
ALTER TABLE `gpg_key_subkeys` ROW_FORMAT=DYNAMIC;
ALTER TABLE `gpg_keys` ROW_FORMAT=DYNAMIC;
ALTER TABLE `gpg_signatures` ROW_FORMAT=DYNAMIC;
ALTER TABLE `group_custom_attributes` ROW_FORMAT=DYNAMIC;
ALTER TABLE `identities` ROW_FORMAT=DYNAMIC;
ALTER TABLE `issue_assignees` ROW_FORMAT=DYNAMIC;
ALTER TABLE `issue_metrics` ROW_FORMAT=DYNAMIC;
ALTER TABLE `issues` ROW_FORMAT=DYNAMIC;
ALTER TABLE `keys` ROW_FORMAT=DYNAMIC;
ALTER TABLE `label_links` ROW_FORMAT=DYNAMIC;
ALTER TABLE `label_priorities` ROW_FORMAT=DYNAMIC;
ALTER TABLE `labels` ROW_FORMAT=DYNAMIC;
ALTER TABLE `lfs_objects` ROW_FORMAT=DYNAMIC;
ALTER TABLE `lfs_objects_projects` ROW_FORMAT=DYNAMIC;
ALTER TABLE `lists` ROW_FORMAT=DYNAMIC;
ALTER TABLE `members` ROW_FORMAT=DYNAMIC;
ALTER TABLE `merge_request_diff_commits` ROW_FORMAT=DYNAMIC;
ALTER TABLE `merge_request_diff_files` ROW_FORMAT=DYNAMIC;
ALTER TABLE `merge_request_diffs` ROW_FORMAT=DYNAMIC;
ALTER TABLE `merge_request_metrics` ROW_FORMAT=DYNAMIC;
ALTER TABLE `merge_requests` ROW_FORMAT=DYNAMIC;
ALTER TABLE `merge_requests_closing_issues` ROW_FORMAT=DYNAMIC;
ALTER TABLE `milestones` ROW_FORMAT=DYNAMIC;
ALTER TABLE `namespaces` ROW_FORMAT=DYNAMIC;
ALTER TABLE `notes` ROW_FORMAT=DYNAMIC;
ALTER TABLE `notification_settings` ROW_FORMAT=DYNAMIC;
ALTER TABLE `oauth_access_grants` ROW_FORMAT=DYNAMIC;
ALTER TABLE `oauth_access_tokens` ROW_FORMAT=DYNAMIC;
ALTER TABLE `oauth_applications` ROW_FORMAT=DYNAMIC;
ALTER TABLE `oauth_openid_requests` ROW_FORMAT=DYNAMIC;
ALTER TABLE `pages_domains` ROW_FORMAT=DYNAMIC;
ALTER TABLE `personal_access_tokens` ROW_FORMAT=DYNAMIC;
ALTER TABLE `project_authorizations` ROW_FORMAT=DYNAMIC;
ALTER TABLE `project_auto_devops` ROW_FORMAT=DYNAMIC;
ALTER TABLE `project_custom_attributes` ROW_FORMAT=DYNAMIC;
ALTER TABLE `project_features` ROW_FORMAT=DYNAMIC;
ALTER TABLE `project_group_links` ROW_FORMAT=DYNAMIC;
ALTER TABLE `project_import_data` ROW_FORMAT=DYNAMIC;
ALTER TABLE `project_statistics` ROW_FORMAT=DYNAMIC;
ALTER TABLE `projects` ROW_FORMAT=DYNAMIC;
ALTER TABLE `protected_branch_merge_access_levels` ROW_FORMAT=DYNAMIC;
ALTER TABLE `protected_branch_push_access_levels` ROW_FORMAT=DYNAMIC;
ALTER TABLE `protected_branches` ROW_FORMAT=DYNAMIC;
ALTER TABLE `protected_tag_create_access_levels` ROW_FORMAT=DYNAMIC;
ALTER TABLE `protected_tags` ROW_FORMAT=DYNAMIC;
ALTER TABLE `push_event_payloads` ROW_FORMAT=DYNAMIC;
ALTER TABLE `redirect_routes` ROW_FORMAT=DYNAMIC;
ALTER TABLE `releases` ROW_FORMAT=DYNAMIC;
ALTER TABLE `routes` ROW_FORMAT=DYNAMIC;
ALTER TABLE `schema_migrations` ROW_FORMAT=DYNAMIC;
ALTER TABLE `sent_notifications` ROW_FORMAT=DYNAMIC;
ALTER TABLE `services` ROW_FORMAT=DYNAMIC;
ALTER TABLE `snippets` ROW_FORMAT=DYNAMIC;
ALTER TABLE `spam_logs` ROW_FORMAT=DYNAMIC;
ALTER TABLE `subscriptions` ROW_FORMAT=DYNAMIC;
ALTER TABLE `system_note_metadata` ROW_FORMAT=DYNAMIC;
ALTER TABLE `taggings` ROW_FORMAT=DYNAMIC;
ALTER TABLE `tags` ROW_FORMAT=DYNAMIC;
ALTER TABLE `timelogs` ROW_FORMAT=DYNAMIC;
ALTER TABLE `todos` ROW_FORMAT=DYNAMIC;
ALTER TABLE `trending_projects` ROW_FORMAT=DYNAMIC;
ALTER TABLE `u2f_registrations` ROW_FORMAT=DYNAMIC;
ALTER TABLE `uploads` ROW_FORMAT=DYNAMIC;
ALTER TABLE `user_agent_details` ROW_FORMAT=DYNAMIC;
ALTER TABLE `user_custom_attributes` ROW_FORMAT=DYNAMIC;
ALTER TABLE `user_synced_attributes_metadata` ROW_FORMAT=DYNAMIC;
ALTER TABLE `users` ROW_FORMAT=DYNAMIC;
ALTER TABLE `users_star_projects` ROW_FORMAT=DYNAMIC;
ALTER TABLE `web_hook_logs` ROW_FORMAT=DYNAMIC;
ALTER TABLE `web_hooks` ROW_FORMAT=DYNAMIC;');

4.转换原有数据使用新的表空间( tablespace )格式.注意下面的命令每一条的回显都要作为 SQL 语句再执行.

SET foreign_key_checks = 0;
SELECT CONCAT('ALTER TABLE `', TABLE_NAME,'` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;') AS 'Copy & run these SQL statements:' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA="gitlab" AND TABLE_COLLATION != "utf8mb4_general_ci" AND TABLE_TYPE="BASE TABLE";

回显类似

ALTER TABLE `abuse_reports` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `appearances` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `application_settings` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `audit_events` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `award_emoji` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `boards` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `broadcast_messages` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `chat_names` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `chat_teams` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_build_trace_section_names` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_build_trace_sections` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_builds` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_group_variables` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_job_artifacts` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_pipeline_schedule_variables` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_pipeline_schedules` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_pipeline_variables` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_pipelines` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_runner_projects` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_runners` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_stages` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_trigger_requests` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_triggers` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `ci_variables` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `cluster_platforms_kubernetes` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `cluster_projects` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `cluster_providers_gcp` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `clusters` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `clusters_applications_helm` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `container_repositories` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `conversational_development_index_metrics` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `deploy_keys_projects` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `deployments` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `emails` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `environments` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `events` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `feature_gates` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `features` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `fork_network_members` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `fork_networks` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `forked_project_links` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `gcp_clusters` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `gpg_key_subkeys` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `gpg_keys` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `gpg_signatures` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `group_custom_attributes` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `identities` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `issue_assignees` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `issue_metrics` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `issues` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `keys` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `label_links` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `label_priorities` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `labels` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `lfs_objects` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `lfs_objects_projects` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `lists` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `members` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `merge_request_diff_commits` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `merge_request_diff_files` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `merge_request_diffs` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `merge_request_metrics` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `merge_requests` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `merge_requests_closing_issues` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `milestones` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `namespaces` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `notes` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `notification_settings` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `oauth_access_grants` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `oauth_access_tokens` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `oauth_applications` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `oauth_openid_requests` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `pages_domains` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `personal_access_tokens` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `project_authorizations` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `project_auto_devops` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `project_custom_attributes` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `project_features` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `project_group_links` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `project_import_data` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `project_statistics` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `projects` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `protected_branch_merge_access_levels` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `protected_branch_push_access_levels` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `protected_branches` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `protected_tag_create_access_levels` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `protected_tags` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `push_event_payloads` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `redirect_routes` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `releases` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `routes` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `schema_migrations` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `sent_notifications` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `services` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `snippets` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `spam_logs` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `subscriptions` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `system_note_metadata` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `taggings` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `tags` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `timelogs` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `todos` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `trending_projects` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `u2f_registrations` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `uploads` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `user_agent_details` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `user_custom_attributes` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `user_synced_attributes_metadata` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `users` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `users_star_projects` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `web_hook_logs` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE `web_hooks` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
SET foreign_key_checks = 1;

5.执行数据库限制任务.

bundle exec rake add_limits_mysql RAILS_ENV=production

6.修改数据库迁移任务.由于 GitLab 官方要求不允许跨多个小版本升级,所以我们会遇到一些奇怪的错误,这里给出一些修复方案,我只遇到了一个.

Mysql2::Error: Index column size too large. The maximum column size is 767 bytes.: CREATE UNIQUE INDEX `index_lfs_file_locks_on_project_id_and_path`  ON `lfs_file_locks` (`project_id`, `path`)

解决方案:

use gitlab;
DROP TABLE lfs_file_locks;
# db/migrate/20180116193854_create_lfs_file_locks.rb
# create_table :lfs_file_locks do |t| 修改为
create_table :lfs_file_locks, options: 'ROW_FORMAT=DYNAMIC'  do |t|

7.执行数据库迁移.

bundle exec rake db:migrate

预编译资源

bundle exec rake gettext:compile RAILS_ENV=production
bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
bundle exec rake cache:clear RAILS_ENV=production

备份与恢复

bundle exec rake gitlab:backup:create RAILS_ENV=production  # 你会找到一个类似 /home/git/data/backups/1531046569_2018_07_14_10.6.4_gitlab_backup.tar 的文件
bundle exec rake gitlab:backup:restore RAILS_ENV=production

cnpm+gitlab-ci 模块自动发布方案

为什么会探讨这个主题

在我司现有web前端项目构建过程中,一直使用gulp + rjs的方案进行项目打包,由于整站项目过于庞大,即使是以相当好的机器多核打包,也需要4分钟+的时间,遂产生了将模块全部使用npm管理,并拆分子项目,达到每个通用模块都可自动构建,单元测试,保证质量并稳步迭代的目的。

项目其余细节

公司内部内容,略过

cnpm与cnpmjs.org的部署

cnpm原理

为npm原有的一些命令设置了代理,让你可以方便的连接到自己的私有服务,并同时使用npm上的资源包,您可以选择使用命令直接同步npm官方包(实质是向您的cnpmjs.org私有服务发送命令,并同步线上资源,存储下来),也可以直接通过cnpm接洽的原生npm直接下载,取决于您的需求

部署

cnpm官方通过开源项目cnpmjs.org整合了cnpm.registry和cnpm.web两个服务,让用户可以通过cnpm指定registry使用私有服务。

这里只介绍使用sqlite3作为存储私有包的记录数据库,即官方wiki中的5分钟部署cnpmjs。

Deploy-a-private-npm-registry-in-5-minutes

直接根据以上链接阐述内容,您可以立刻配置起一个cnpmjs.org服务.

配置cnpmjs.org

值得注意的是,您如果没有配置云存储服务器,将会默认使用fs-cnpm的配置,默认将publish的包存放在~/.cnpmjs.org/downloads下

默认的sqlite数据库位于~/.cnpmjs.org/data.sqlite

cnpmjs.org相关配置位于cnpmjs.org的包的config/index.js中

注意:bindingHost默认为127.0.0.1,请置空字符串或已注册的具名地址或内网可访问的ip地址,以便在内网其他机器上访问该服务

如果需使用其他云存储配置,已有数据库,请按照官方wiki修改相应配置。

# 后台启动cnpmjs.org服务,如有需要,可使用其他守护进程
nohup cnpmjs.org start --admins='myname,othername' 
  --scopes='@my-company-name,@other-name' &

当安装好cnpmjs.org并配置好后,需要使用命令写入或者直接配置(至少运行一次)私有仓库作用域(scopes)以及私有仓库管理员名称。不明白的可以去npm官方文档了解一下

cnpmjs.org的registry服务和web服务默认位于7001,7002两个端口,7002即是cnpmjs.org的web网页,可以查看发布的相关私有包或同步的包

配置cnpm

cnpm set registry http://bindingHost:7001

将registry服务地址设置为你启动的cnpmjs.org registry服务地址

第一次登录

也就是注册。

原因是调用cnpm login的时候,走到cnpmjs.org服务,会通过以下路由

app.put('/-/user/org.couchdb.user::name', addUser);

输入下面内容开始创建

cnpm login myname

测试私有npm是否畅通

新建一个项目,cnpm init之

name设置为'@my-company-name/test'

cnpm publish尝试,上传成功

数据备份

相应包如果由于宕机或其他原因消失了是一件很麻烦的事情,请定时去默认数据存储文件夹备份一份(如果使用了其他已有的数据库,云存储,请自行备份),以便恢复。

gitlab的部署

这里使用的是ce-11.0.3版本,按照官方傻瓜式部署,直接可以部署上

请区分ce和ee两个不同版本

EXTERNAL_URL配置与上步骤配置bindingHost类似

其余邮件配置,其他数据库配置(如不用默认数据库),以及其他配置,请自行搜索或寻找官方文档。

gitlab首次启动

使用10 11等版本,首次启动的默认管理员用户密码不再如同以前一样是root 5iveL!fe了,

在新版本中,gitlab-rails web服务于auth中间件中判断了用户是否为第一次登陆,如果为第一次登陆,将会跳转到修改密码页,只需简单进入布置好的网页服务设置root密码即可。

gitlab首次进入修改不了密码?

。。。虽然我是个低级前端工程师,并且我chrome打开了f12,但是我没看到报错,查看了各种权限是否分配错误,查看了是不是编译出错,查看了web服务的auth中间件以及 changepassword服务,查看了数据库,各种可能都找了一遍,就差没直接改数据库,没找到原因。。。

然后我看到了这篇文章修改GitLab Web服务默认端口和初次登录密码报错问题 https://ykzm.in/archives/1010

尝试换了个浏览器,好了。。。没有继续深究为什么

安装gitlab-runner

请直接参考官方文档 在任意一台机器上无脑安装即可

配置gitlab-runner

也请直接参考官方文档

或参考我的渣文档最底部的资料链接

重头戏

gitlab-ci cnpm配合做到一键发布。

gitlab-ci cnpm配合前情提要

首先,用户需要保证runner机器上也装了cnpm,至于怎么装就随便了。

cnpm登录后并不会直接颁布给你一个_authtoken,所以想直接通过在gitlab中的ci配置里写入_authtoken私密变量并进行私有包的发布是不现实的。

cnpm的源码中将publish命令直接拿给原生npm执行了,自己只是对参数进行了一些包装,最终执行 npm publish argv命令

cnpm可以做到的就是修改配置路径(–userconfig参数配置项),读取用户cnpmrc配置。

在设置了–userconfig路径以后,cnpm会将一系列参数直接传递给npm , npm通过 core包中的Conf模块会将你的config文件解析掉,生成一个token并执行publish的put操作。

基于这点我们就可以做到测试完毕并且自动发布私有包了

具体实施步骤

如果您没有修改过指定的.cnpmrc客户配置,当您登陆过cnpm后,会在用户目录下,也就是~/下发现.cnpmrc配置文件。

他大概是这样的

registry=http://yourbindingRegistryServerHost
//yourbindingHost:always-auth=false
//yourbindingRegistryServiceHost:_password=加密过的密码串
//yourbindingRegistryServiceHost:username=myname
//yourbindingRegistryServiceHost:[email protected]

那么思路就很明白了,我们可以这样写我们的发布脚本脚本

// publish.js
const fs = require('fs');
const argv = process.argv;
const un = argv[2];
const pw = argv[3];
const em = argv[4];
const r = argv[5];
const exec = require('child_process').exec;

if (un && pw && em && r) {
    const or = r.replace(/^https?:/, '')
    const result = `
registry=${r}
${or}:always-auth=false
${or}:_password=${pw}
${or}:username=${un}
${or}:email=${em}
`;
    fs.writeFileSync('./.cnpmrc', result);
    exec('cnpm publish --userconfig=' + process.cwd() + '/.cnpmrc', (error, stdout, stderr) => {
        console.log(`stdout: ${stdout}`);
        console.log(`stderr: ${stderr}`);
        if (error) {
            console.error(`exec error: ${error}`);
            process.exit(1);
        }
    })
}

这样写我们的ci配置

before_script:
  - npm i
stages:
  - test
  - publish
job1:
  stage: test
  script:
    - npm run test
  only:
    - master
  tags:
    - qie
job2:
  stage: publish
  script:
    - 'node ./publish.js $CNPM_UN $CNPM_PW $CNPM_EM $CNPM_R'

$CNPM_UN $CNPM_PW $CNPM_EM $CNPM_R为在gitlab仓库中设置的CI私密变量

效果

未分类

未分类

未分类

Gitlab 8.x runner安装与配置

介绍

  Gitlab 8.x之后默认集成了Gitlab CI,意味着支持了持续集成相关功能。每一次集成操作都需要对应的runner来跑代码构建、测试、发布等操作。Runner实际上就是为Gitlab的持续集成指定一个环境。

安装

官方文档地址:https://docs.gitlab.com/runner/install/
  Gitlab Runner的版本需要跟Gitlab对应,这里有一个对照表。最新的版本对照表中并没有Gitlab8.X对应的Runner版本,查了一下Gitlab8.X对应的Runner版本为1.X,所以这里选择runner 1.11.2版本。

  这里运行Gitlab与Runner的环境均为CentOS,之前尝试在windows上安装runner,对接Linux上的Gitlab,发现在Gitlab runner运行的控制台出现乱码问题。

0. 准备

在opt下创建gitlab-runner目录并进入该目录,后续执行的操作与所有的资源都放在这个目录中

cd /opt
mkdir gitlab-runner
cd gitlab-runner/

1. 下载

下载安装资源到gitlab-runner目录中

sudo wget https://gitlab-ci-multi-runner-downloads.s3.amazonaws.com/v1.11.2/binaries/gitlab-ci-multi-runner-linux-386

2. 添加运行权限

sudo chmod +x gitlab-ci-multi-runner-linux-386

3. 创建用户

sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash

4. 安装

./gitlab-ci-multi-runner-linux-386 install --user=gitlab-runner --working-directory=/opt/gitlab-runner
sudo gitlab-ci-multi-runner-linux-386 start

配置

  经过上面的步骤,Runner就已经跑起来了,剩下的还需要Runner与项目对接起来。Runner的类型分为Shared, specific and group Runners。这里选择specific类型,即单独的项目使用。

  在Gitlab项目的setting-runner中,配置过程中会使用到url和token如下所示:

未分类

1.运行register命令

./gitlab-ci-multi-runner-linux-386 register

之后就按照提示就行了

2.输入url地址
3.输入token
4.输入描述,任意即可
5.输入标签,这里直接Enter跳过
6.选择Runner executor,这里选择shell

到这里就已经注册成功了,输入./gitlab-ci-multi-runner-linux-386 list就能看到上面的注册的条目。

官方文档地址:https://docs.gitlab.com/runner/register/index.html

其它

  上面两个步骤做完后,此时按理说Gitlab就能调用Runner跑持续集成了,实际当中还会碰到其它问题,整理如下。

权限问题

  如果在Gitlab的Build控制台上报无法创建文件夹、无法运行bash等,证明创建的GitLab Runner权限不够。
此时,我这里是修改GitLab Runner的权限跟root保持一致。

vim /etc/passwd

通过上面命令可以编辑用户对应的权限,我这里打开默认为gitlab-runner:x:601:601:GitLab Runner:/home/gitlab-runner:/bin/bash,权限组修改为跟root的一致gitlab-runner:x:0:0:GitLab Runner:/home/gitlab-runner:/bin/bash。(root的权限组名为0)

这里在另外一台机器上还碰到这样修改了也不好使的问题,最终gitlab-runner install的时候,直接指定为root,而不新创建用户。

环境问题

由于Runner运行需要环境支撑,比如git、node、npm等,需要在Runner所在的服务器上准备好所有的依赖。

Linux Node安装

# 下载
wget https://nodejs.org/dist/v8.11.3/node-v8.11.3-linux-x64.tar.xz
# 解压
tar -xf  node-v8.11.3-linux-x64.tar.xz
# 建立软链接,实现全局访问
ln -s /opt/gitlab-runner/node-v8.11.3-linux-x64/bin/node /usr/local/bin/node
ln -s /opt/gitlab-runner/node-v8.11.3-linux-x64/bin/npm /usr/local/bin/npm

此时,输入node -v就能看到node的版本了。

使用软连接方式可能对非root用户无效,可以转而使用配置环境变量的方式

# 修改配置文件
vim /etc/profile
#set for nodejs,新增NODE_HOME并放到PATH上
export JAVA_HOME=/opt/soft/java
export NODE_HOME=/opt/gitlab-runner/node-v8.11.3-linux-x64  
export CLASSPATH=$JAVA_HOME/lib:$JAVA_HOME/jre/lib
export PATH=$PATH:$JAVA_HOME/bin:$NODE_HOME/bin

在vim环境下点击i进入插入状态,编辑完成后按Esc键,然后输入 :wq 按回车保存退出。
备注:内外环境还需修改NPM的镜像源,比如修改为npm config set registry https://registry-npm.daojia-inc.com/

附录 部分GitLab-Runner常用命令

1.gitlab-runner帮助:gitlab-runner –help

2.gitlab-runner指定命令帮助:gitlab-runner –help

3.注册runner:gitlab-runner register

4.注销runner:gitlab-runner unregister

5.当前运行的runner:gitlab-runner list

6.启动runner:gitlab-runner start

7.停止runner:gitlab-runner stop

8.重启runner:gitlab-runner restart

9.查询runner状态:gitlab-runner status

docker compose实战部署django

第一步,因为应用将要运行在一个满足所有环境依赖的 Docker 容器里面,那么我们可以通过编辑 Dockerfile 文件来指定 Docker 容器要安装内容。内容如下:

FROM python:3

ENV PYTHONUNBUFFERED 1

RUN mkdir /code

WORKDIR /code

ADD requirements.txt /code/

RUN pip install -r requirements.txt

ADD . /code/

第二步,在 requirements.txt 文件里面写明需要安装的具体依赖包名。

Django>=1.8,<2.0

psycopg2

第三步, docker-compose.yml 文件将把所有的东西关联起来。它描述了应用的构成(一个web 服务和一个数据库)、使用的 Docker 镜像、镜像之间的连接、挂载到容器的卷,以及服务开放的端口。

version: "3"

services:

    db:

        image: postgres

    web:

        build: .

        command: python3 manage.py runserver 0.0.0.0:8000

        volumes:

            – .:/code

        ports:

            – "8000:8000"

        links:

            – db

现在我们就可以使用 docker-compose run 命令启动一个 Django 应用了。

$ docker-compose run web django-admin.py startproject django_example .

Compose 会先使用 Dockerfile 为 web 服务创建一个镜像,接着使用这个镜像在容器里运行django-admin.py startproject composeexample 指令。这将在当前目录生成一个 Django 应用。

$ ls

Dockerfile docker-compose.yml django_example manage.py requ

irements.txt

如果你的系统是 Linux,记得更改文件权限。

sudo chown -R $USER:$USER .

首先,我们要为应用设置好数据库的连接信息。用以下内容替换 django_example/settings.py文件中 DATABASES = … 定义的节点内容。

DATABASES = {

    'default': {

        'ENGINE': 'django.db.backends.postgresql',

         'NAME': 'postgres',

         'USER': 'postgres',

         'HOST': 'db',

         'PORT': 5432,

    }

}

这些信息是在 postgres 镜像固定设置好的。然后,运行 docker-compose up :

$ docker-compose up

django_db_1 is up-to-date

Creating django_web_1 …

Creating django_web_1 … done

Attaching to django_db_1, django_web_1

db_1 | The files belonging to this database system will be owned by user "postgres".

db_1 | This user must also own the server process.

db_1 |

db_1 | The database cluster will be initialized with locale "en_US.utf8".

db_1 | The default database encoding has accordingly been set to "UTF8".

db_1 | The default text search configuration will be set to "english".

web_1 | Performing system checks…

web_1 |

web_1 | System check identified no issues (0 silenced).

web_1 |

web_1 | November 23, 2017 – 06:21:19

web_1 | Django version 1.11.7, using settings 'django_example.settings'

web_1 | Starting development server at http://0.0.0.0:8000/

web_1 | Quit the server with CONTROL-C.

这个 Django 应用已经开始在你的 Docker 守护进程里监听着 8000 端口了。打开127.0.0.1:8000 即可看到 Django 欢迎页面。

你还可以在 Docker 上运行其它的管理命令,例如对于同步数据库结构这种事,在运行完docker-compose up 后,在另外一个终端进入文件夹运行以下命令即可:

$ docker-compose run web python manage.py syncdb

docker-compose up 时提示挂载目录open permission denied

docker-compose up 时提示挂载目录open permission denied

[root@localhost nginx-php]# docker-compose up
Creating php-fpm ... 
Creating php-fpm ... done
Creating nginx ... 
Creating nginx ... done
Attaching to php-fpm, nginx
php-fpm    | [15-Dec-2017 06:53:36] NOTICE: fpm is running, pid 1
php-fpm    | [15-Dec-2017 06:53:36] NOTICE: ready to handle connections
nginx      | nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (13: Permission denied)
nginx      | 2017/12/15 06:53:36 [emerg] 1#1: open() "/var/log/nginx/error.log" failed (13: Permission denied)
nginx exited with code 1
^CGracefully stopping... (press Ctrl+C again to force)
Stopping php-fpm ... done
[root@localhost nginx-php]#

原因是SELinux搞的鬼,禁用就好了

#查看SELinux状态(如果SELinux status参数为enabled即为开启状态)
/usr/sbin/sestatus -v 

#临时关闭
setenforce 0

#修改配置文件重启机器禁用(将SELINUX=enforcing改为SELINUX=disabled)
vim /etc/selinux/config

基于Redis实现分布式锁

背景

在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等。大部分的解决方案是基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。其次Redis提供一些命令SETNX,GETSET,可以方便实现分布式锁机制。

Redis命令介绍

使用Redis实现分布式锁,有两个重要函数需要介绍

SETNX命令(SET if Not eXists)
语法:
SETNX key value
功能:
当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。

GETSET命令
语法:
GETSET key value
功能:
将给定 key 的值设为 value ,并返回 key 的旧值 (old value),当 key 存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil。

GET命令
语法:
GET key
功能:
返回 key 所关联的字符串值,如果 key 不存在那么返回特殊值 nil 。

DEL命令
语法:
DEL key [KEY …]
功能:
删除给定的一个或多个 key ,不存在的 key 会被忽略。

兵贵精,不在多。分布式锁,我们就依靠这四个命令。但在具体实现,还有很多细节,需要仔细斟酌,因为在分布式并发多进程中,任何一点出现差错,都会导致死锁,hold住所有进程。

加锁实现

SETNX 可以直接加锁操作,比如说对某个关键词foo加锁,客户端可以尝试
SETNX foo.lock <current unix time>

如果返回1,表示客户端已经获取锁,可以往下操作,操作完成后,通过
DEL foo.lock

命令来释放锁。
如果返回0,说明foo已经被其他客户端上锁,如果锁是非堵塞的,可以选择返回调用。如果是堵塞调用调用,就需要进入以下个重试循环,直至成功获得锁或者重试超时。理想是美好的,现实是残酷的。仅仅使用SETNX加锁带有竞争条件的,在某些特定的情况会造成死锁错误。

处理死锁

在上面的处理方式中,如果获取锁的客户端端执行时间过长,进程被kill掉,或者因为其他异常崩溃,导致无法释放锁,就会造成死锁。所以,需要对加锁要做时效性检测。因此,我们在加锁时,把当前时间戳作为value存入此锁中,通过当前时间戳和Redis中的时间戳进行对比,如果超过一定差值,认为锁已经时效,防止锁无限期的锁下去,但是,在大并发情况,如果同时检测锁失效,并简单粗暴的删除死锁,再通过SETNX上锁,可能会导致竞争条件的产生,即多个客户端同时获取锁。

C1获取锁,并崩溃。C2和C3调用SETNX上锁返回0后,获得foo.lock的时间戳,通过比对时间戳,发现锁超时。
C2 向foo.lock发送DEL命令。
C2 向foo.lock发送SETNX获取锁。
C3 向foo.lock发送DEL命令,此时C3发送DEL时,其实DEL掉的是C2的锁。
C3 向foo.lock发送SETNX获取锁。

此时C2和C3都获取了锁,产生竞争条件,如果在更高并发的情况,可能会有更多客户端获取锁。所以,DEL锁的操作,不能直接使用在锁超时的情况下,幸好我们有GETSET方法,假设我们现在有另外一个客户端C4,看看如何使用GETSET方式,避免这种情况产生。

C1获取锁,并崩溃。C2和C3调用SETNX上锁返回0后,调用GET命令获得foo.lock的时间戳T1,通过比对时间戳,发现锁超时。
C4 向foo.lock发送GESET命令,
GETSET foo.lock
并得到foo.lock中老的时间戳T2

如果T1=T2,说明C4获得时间戳。
如果T1!=T2,说明C4之前有另外一个客户端C5通过调用GETSET方式获取了时间戳,C4未获得锁。只能sleep下,进入下次循环中。

现在唯一的问题是,C4设置foo.lock的新时间戳,是否会对锁产生影响。其实我们可以看到C4和C5执行的时间差值极小,并且写入foo.lock中的都是有效时间错,所以对锁并没有影响。
为了让这个锁更加强壮,获取锁的客户端,应该在调用关键业务时,再次调用GET方法获取T1,和写入的T0时间戳进行对比,以免锁因其他情况被执行DEL意外解开而不知。以上步骤和情况,很容易从其他参考资料中看到。客户端处理和失败的情况非常复杂,不仅仅是崩溃这么简单,还可能是客户端因为某些操作被阻塞了相当长时间,紧接着 DEL 命令被尝试执行(但这时锁却在另外的客户端手上)。也可能因为处理不当,导致死锁。还有可能因为sleep设置不合理,导致Redis在大并发下被压垮。最为常见的问题还有

GET返回nil时应该走那种逻辑?

第一种走超时逻辑
C1客户端获取锁,并且处理完后,DEL掉锁,在DEL锁之前。C2通过SETNX向foo.lock设置时间戳T0 发现有客户端获取锁,进入GET操作。
C2 向foo.lock发送GET命令,获取返回值T1(nil)。
C2 通过T0>T1+expire对比,进入GETSET流程。
C2 调用GETSET向foo.lock发送T0时间戳,返回foo.lock的原值T2
C2 如果T2=T1相等,获得锁,如果T2!=T1,未获得锁。

第二种情况走循环走setnx逻辑
C1客户端获取锁,并且处理完后,DEL掉锁,在DEL锁之前。C2通过SETNX向foo.lock设置时间戳T0 发现有客户端获取锁,进入GET操作。
C2 向foo.lock发送GET命令,获取返回值T1(nil)。
C2 循环,进入下一次SETNX逻辑

两种逻辑貌似都是OK,但是从逻辑处理上来说,第一种情况存在问题。当GET返回nil表示,锁是被删除的,而不是超时,应该走SETNX逻辑加锁。走第一种情况的问题是,正常的加锁逻辑应该走SETNX,而现在当锁被解除后,走的是GETST,如果判断条件不当,就会引起死锁,很悲催,我在做的时候就碰到了,具体怎么碰到的看下面的问题

GETSET返回nil时应该怎么处理?

C1和C2客户端调用GET接口,C1返回T1,此时C3网络情况更好,快速进入获取锁,并执行DEL删除锁,C2返回T2(nil),C1和C2都进入超时处理逻辑。
C1 向foo.lock发送GETSET命令,获取返回值T11(nil)。
C1 比对C1和C11发现两者不同,处理逻辑认为未获取锁。
C2 向foo.lock发送GETSET命令,获取返回值T22(C1写入的时间戳)。
C2 比对C2和C22发现两者不同,处理逻辑认为未获取锁。

此时C1和C2都认为未获取锁,其实C1是已经获取锁了,但是他的处理逻辑没有考虑GETSET返回nil的情况,只是单纯的用GET和GETSET值就行对比,至于为什么会出现这种情况?一种是多客户端时,每个客户端连接Redis的后,发出的命令并不是连续的,导致从单客户端看到的好像连续的命令,到Redis server后,这两条命令之间可能已经插入大量的其他客户端发出的命令,比如DEL,SETNX等。第二种情况,多客户端之间时间不同步,或者不是严格意义的同步。

时间戳的问题

我们看到foo.lock的value值为时间戳,所以要在多客户端情况下,保证锁有效,一定要同步各服务器的时间,如果各服务器间,时间有差异。时间不一致的客户端,在判断锁超时,就会出现偏差,从而产生竞争条件。
锁的超时与否,严格依赖时间戳,时间戳本身也是有精度限制,假如我们的时间精度为秒,从加锁到执行操作再到解锁,一般操作肯定都能在一秒内完成。这样的话,我们上面的CASE,就很容易出现。所以,最好把时间精度提升到毫秒级。这样的话,可以保证毫秒级别的锁是安全的。

分布式锁的问题

1:必要的超时机制:获取锁的客户端一旦崩溃,一定要有过期机制,否则其他客户端都降无法获取锁,造成死锁问题。
2:分布式锁,多客户端的时间戳不能保证严格意义的一致性,所以在某些特定因素下,有可能存在锁串的情况。要适度的机制,可以承受小概率的事件产生。
3:只对关键处理节点加锁,良好的习惯是,把相关的资源准备好,比如连接数据库后,调用加锁机制获取锁,直接进行操作,然后释放,尽量减少持有锁的时间。
4:在持有锁期间要不要CHECK锁,如果需要严格依赖锁的状态,最好在关键步骤中做锁的CHECK检查机制,但是根据我们的测试发现,在大并发时,每一次CHECK锁操作,都要消耗掉几个毫秒,而我们的整个持锁处理逻辑才不到10毫秒,玩客没有选择做锁的检查。
5:sleep学问,为了减少对Redis的压力,获取锁尝试时,循环之间一定要做sleep操作。但是sleep时间是多少是门学问。需要根据自己的Redis的QPS,加上持锁处理时间等进行合理计算。
6:至于为什么不使用Redis的muti,expire,watch等机制,可以查一参考资料,找下原因。

锁测试数据

未使用sleep

第一种,锁重试时未做sleep。单次请求,加锁,执行,解锁时间

未分类

可以看到加锁和解锁时间都很快,当我们使用

ab -n1000 -c100 'http://sandbox6.wanke.etao.com/test/test_sequence.php?tbpm=t'
AB 并发100累计1000次请求,对这个方法进行压测时。

未分类

我们会发现,获取锁的时间变成,同时持有锁后,执行时间也变成,而delete锁的时间,将近10ms时间,为什么会这样?
1:持有锁后,我们的执行逻辑中包含了再次调用Redis操作,在大并发情况下,Redis执行明显变慢。
2:锁的删除时间变长,从之前的0.2ms,变成9.8ms,性能下降近50倍。
在这种情况下,我们压测的QPS为49,最终发现QPS和压测总量有关,当我们并发100总共100次请求时,QPS得到110多。当我们使用sleep时

使用Sleep时

单次执行请求时

未分类

我们看到,和不使用sleep机制时,性能相当。当时用相同的压测条件进行压缩时

未分类

获取锁的时间明显变长,而锁的释放时间明显变短,仅是不采用sleep机制的一半。当然执行时间变成就是因为,我们在执行过程中,重新创建数据库连接,导致时间变长的。同时我们可以对比下Redis的命令执行压力情况

未分类

上图中细高部分是为未采用sleep机制的时的压测图,矮胖部分为采用sleep机制的压测图,通上图看到压力减少50%左右,当然,sleep这种方式还有个缺点QPS下降明显,在我们的压测条件下,仅为35,并且有部分请求出现超时情况。不过综合各种情况后,我们还是决定采用sleep机制,主要是为了防止在大并发情况下把Redis压垮,很不行,我们之前碰到过,所以肯定会采用sleep机制。