Git bare repository 的概念

要搞清这个概念,网上搜索一堆网页,中文的网页教程,没一个说得让人明白。而英文的,一看就明白。

这是一份很容易看懂的英文网页:What is a bare git repository?   地址:http://www.saintsjd.com/2011/01/what-is-a-bare-git-repository/

大意:

git init 创建的是一个工作目录,以下简称 A;git init –bare 创建的是一个空目录,以下简称 B。这两个目录的区别是:

A 里面有你工作用的文件,working tree,是指你工作用的文件的树目录结构。在 A 底下,还有一个 .git 子目录,里面就是 git 帮你管理的文件的历史版本信息。你在工作目录里编辑你的文件后,用 git 的 add, delete, commit,就是提交到这个子目录下。也就是说,git 帮你做的版本控制信息,其实就是保存在当前工作目录下的一个 .git 的子目录下。

B 里面只有 git 的版本控制信息,没有你工作用的文件,也就是没有 working tree。因为它不包含工作用的文件,只有 git 自己需要的文件,所以这个目录下没有 .git 子目录,而是直接把 git 需要的文件放根目录底下了。但是,这个 bare 的目录本身的目录名,有一个 .git 的后缀。

另外,用 git clone 从别的仓库克隆过来的本地文件夹,是工作目录,包含了工作用的文件。

而一个 bare repository 是用来“分享”的。如果有几个人要访问同一个项目的文件,在一个中心点上创建一个 bare 仓库,所有项目成员可以 push 自己编辑的文件到这个仓库。

因此,概念上,一个 bare 仓库,可以看成是一个 git 的文件服务器。一个人的项目,其实也可以建一个 bare 仓库,用做文件备份。这样自己的工作电脑彻底完蛋,你换台电脑,把项目从服务器上 clone 下来,就可以继续干活了。

所以,bare 仓库类似版本管理服务器,只存储版本管理的相关文件,不存储工作文件,因此里面没有工作目录。你要干活,必须从 bare 仓库克隆一个到本地的目录,然后在这个克隆的目录里面工作。

另外,如果你已经有了一个工作目录,但还没有被 git 管理起来,也是可以用 git 命令将它变成工作目录的。然后,你还可以把这个工作目录,和一个 bare 仓库,建立联系,方便将来 push 东西进去。

从 git 的角度来看,你提交的,是你对工作目录下的文件的改变,这个提交,其实只是把相关的内容,写入了你的工作目录下的那个被 git 管理的 .git 目录底下的文件里面。如果要把这些内容提交到一个 bare 仓库(通常是在一台服务器上,不过你要在工作电脑上另外建一个 bare 仓库,也不是不可以),就做一个 push 的操作,至于 push 到哪里,是访问那个 bare 仓库的 URL 路径问题。理论上,可以把一个工作目录,push 到几个不同的 bare 仓库里面,并不是一个工作目录只能对应一个远端的服务器。

———————

本文来自 pcplayer 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/pcplayer/article/details/72784096?utm_source=copy

服务器上的 Git – 在服务器上部署 Git

在服务器上部署 Git

开始架设 Git 服务器前,需要先把现有仓库导出为裸仓库 — 即一个不包含当前工作目录的仓库。做法直截了当,克隆时用 --bare 选项即可。裸仓库的目录名一般以 .git 结尾,像这样:

$ git clone --bare my_project my_project.git
Cloning into bare repository 'my_project.git'...
done.

该命令的输出或许会让人有些不解。其实 clone 操作基本上相当于 git init 加 git fetch,所以这里出现的其实是 git init 的输出,先由它建立一个空目录,而之后传输数据对象的操作并无任何输出,只是悄悄在幕后执行。现在 my_project.git 目录中已经有了一份 Git 目录数据的副本。

整体上的效果大致相当于:

$ cp -Rf my_project/.git my_project.git

但在配置文件中有若干小改动,不过对用户来讲,使用方式都一样,不会有什么影响。它仅取出 Git 仓库的必要原始数据,存放在该目录中,而不会另外创建工作目录。

把裸仓库移到服务器上

有了裸仓库的副本后,剩下的就是把它放到服务器上并设定相关协议。假设一个域名为 git.example.com 的服务器已经架设好,并可以通过 SSH 访问,我们打算把所有 Git 仓库储存在 /opt/git 目录下。只要把裸仓库复制过去:

$ scp -r my_project.git user@git.example.com:/opt/git

现在,所有对该服务器有 SSH 访问权限,并可读取 /opt/git 目录的用户都可以用下面的命令克隆该项目:

$ git clone user@git.example.com:/opt/git/my_project.git

如果某个 SSH 用户对 /opt/git/my_project.git 目录有写权限,那他就有推送权限。如果到该项目目录中运行 git init 命令,并加上 --shared 选项,那么 Git 会自动修改该仓库目录的组权限为可写(译注:实际上 --shared 可以指定其他行为,只是默认为将组权限改为可写并执行 g+sx,所以最后会得到 rws。)。

$ ssh user@git.example.com
$ cd /opt/git/my_project.git
$ git init --bare --shared

由此可见,根据现有的 Git 仓库创建一个裸仓库,然后把它放上你和同事都有 SSH 访问权的服务器是多么容易。现在已经可以开始在同一项目上密切合作了。

值得注意的是,这的的确确是架设一个少数人具有连接权的 Git 服务的全部 — 只要在服务器上加入可以用 SSH 登录的帐号,然后把裸仓库放在大家都有读写权限的地方。一切都准备停当,无需更多。

下面的几节中,你会了解如何扩展到更复杂的设定。这些内容包含如何避免为每一个用户建立一个账户,给仓库添加公共读取权限,架设网页界面,使用 Gitosis 工具等等。然而,只是和几个人在一个不公开的项目上合作的话,仅仅是一个 SSH 服务器和裸仓库就足够了,记住这点就可以了。

小型安装

如果设备较少或者你只想在小型开发团队里尝试 Git ,那么一切都很简单。架设 Git 服务最复杂的地方在于账户管理。如果需要仓库对特定的用户可读,而给另一部分用户读写权限,那么访问和许可的安排就比较困难。

SSH 连接

如果已经有了一个所有开发成员都可以用 SSH 访问的服务器,架设第一个服务器将变得异常简单,几乎什么都不用做(正如上节中介绍的那样)。如果需要对仓库进行更复杂的访问控制,只要使用服务器操作系统的本地文件访问许可机制就行了。

如果需要团队里的每个人都对仓库有写权限,又不能给每个人在服务器上建立账户,那么提供 SSH 连接就是唯一的选择了。我们假设用来共享仓库的服务器已经安装了 SSH 服务,而且你通过它访问服务器。

有好几个办法可以让团队的每个人都有访问权。第一个办法是给每个人建立一个账户,直截了当但略过繁琐。反复运行 adduser 并给所有人设定临时密码可不是好玩的。

第二个办法是在主机上建立一个 git 账户,让每个需要写权限的人发送一个 SSH 公钥,然后将其加入 git 账户的 ~/.ssh/authorized_keys 文件。这样一来,所有人都将通过 git 账户访问主机。这丝毫不会影响提交的数据 — 访问主机用的身份不会影响提交对象的提交者信息。

另一个办法是让 SSH 服务器通过某个 LDAP 服务,或者其他已经设定好的集中授权机制,来进行授权。只要每个人都能获得主机的 shell 访问权,任何可用的 SSH 授权机制都能达到相同效果。

Git仓库迁移保留提交记录

一、建立新仓库

1、从原地址克隆一份裸版本库

git clone –bare https://wangjiayi@dev365.keytop.cn/bitbucket/scm/sup/kos_datasync.git

2、创建新项目

mkdir /scm2/data/git/projects/kos/datasync

git init –bare datasync

3、进入裸版本库,以镜像的方式将代码推送到服务器上

cd kos_datasync.git

git push –mirror https://wangjiayi@dev365.keytop.cn/bitbucket/scm/kos/datasync.git

4、删除本地代码

rm -rf kos_datasync.git

5、到新的目录clone远程仓库到本地

git clone –bare https://wangjiayi@dev365.keytop.cn/bitbucket/scm/kos/datasync.git

二、切换.git/config里面的remote_url

git branch -r

git remote set-url origin remote_git_address

第二种切换remote_url的方法更直接,直接更改.git/conf配置文件里的ip地址就行。

———————

本文来自 OPS运动狂-JoyWang 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/wjy1990831/article/details/80417402?utm_source=copy

git仓库迁移的两种解决方案

Git仓库迁移而不丢失log的方法

  • 要求能保留原先的commit记录,应该如何迁移呢?
  • 同时,本地已经clone了原仓库,要配置成新的仓库地址,该如何修改呢?
  • 注意:如果使用了代码审核工具Gerrit,那么在进行操作之前需要将Gerrit关掉,等成功恢复后再将Gerrit开户即可

1、使用git push –mirror

先了解一些git的基本参数介绍
git clone –bare

GIT-CLONE(1)                      Git Manual                      GIT-CLONE(1)

NAME
       git-clone - Clone a repository into a new directory

SYNOPSIS
       git clone [--template=<template_directory>]
                 [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
                 [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
                 [--depth <depth>] [--recursive] [--] <repository> [<directory>]
        --bare
            Make a bare GIT repository. That is, instead of creating <directory> and placing the administrative files
           in <directory>/.git, make the <directory> itself the $GIT_DIR. This obviously implies the -n because there
           is nowhere to check out the working tree. Also the branch heads at the remote are copied directly to
           corresponding local branch heads, without mapping them to refs/remotes/origin/. When this option is used,
           neither remote-tracking branches nor the related configuration variables are created.

git push –mirror

--mirror
           Instead of naming each ref to push, specifies that all refs under refs/ (which includes but is not limited
           to refs/heads/, refs/remotes/, and refs/tags/) be mirrored to the remote repository. Newly created local
           refs will be pushed to the remote end, locally updated refs will be force updated on the remote end, and
           deleted refs will be removed from the remote end. This is the default if the configuration option
           remote.<remote>.mirror is set.

1、建立新仓库

  • 1). 从原地址克隆一份裸版本库,比如原本托管于 GitHub,或者是本地的私有仓库
git clone --bare git://192.168.10.XX/git_repo/project_name.git
  • 2). 然后到新的 Git 服务器上创建一个新项目,比如 GitCafe,亦或是本地的私有仓库,如192.168.20.XX
su - git
cd /path/to/path/
mkdir new_project_name.git
git init --bare new_project_name.git
  • 3). 以镜像推送的方式上传代码到 GitCafe 服务器上。
    请确保已经添加了公钥到新的机器上
cd project_name.git
git push --mirror git@192.168.20.XX/path/to/path/new_project_name.git
  • 4). 删除本地代码
cd ..
rm -rf project_name.git
  • 5). 到新服务器上找到 Clone 地址,直接Clone到本地就可以了。
git clone git@192.168.20.XX/path/to/path/new_project_name.git

这种方式可以保留原版本库中的所有内容。

2、切换remote_url

先查看remote的名字

git branch -r

假设你的remote是origin,用git remote set_url 更换地址

git remote set-url origin remote_git_address

remote_git_address更换成你的新的仓库地址。

第二种切换remote_url的方法更直接,直接更改.git/conf配置文件里的ip地址就行。

Ubuntu下搭建本地Gerrit代码审核服务器

最近自己在本地搭了一遍Gerrit代码审核服务器,本地的系统是Ubuntu的。后续还会介绍怎么在Centos上搭建,这两者还是有区别的。过程中踩了很多坑,希望能给后面的人多一些参考,少走点弯路。 [TOC] 需要准备的软件 安装git 安装jdk 安装apach2 安装gerrit 配置 验证 创建第一个项目 插件扩展 需要准备的软件 git jdk apache2 gerrit 安装git apt-get install git 1 安装jdk 1.下载 http://www.oracle.com/technetwork/java/javase/downloads/index.html 我下载的是Java SE Development Kit 8u151这个包,最好不要下1.6及以下,最新的gerrit包对jdk的版本有要求,可能会出现兼容性问题。 2.安装 在/usr/ 目录下新建一个java文件夹,将下载好的jdk移动到/usr/java下。 解压:tar -zxvf jdk-**.tar.gz 解压完成后删除jdk的安装包 3.配置环境变量 (1)在终端中输入命令 sudo gedit /etc/profile 1 (2)在文件末尾加入 JAVA_HOME=/usr/java/jdk1.8.0_152 CLASSPATH=.:$JAVA_HOME/lib.tools.jar PATH=$JAVA_HOME/bin:$PATH export JAVA_HOME CLASSPATH PATH 1 2 3 4 (3)保存退出后,执行下面命令,让profile文件生效 source /etc/profile 1 (4)验证是否安装成功 java -version 1 安装apache2 apache2服务器是用来做反向代理用的。由于使用我们后面在安装Gerrit时选择的认证方式为http,需要用apache2来做一个反向代理。 1. 安装apache2 sudo apt-get install apache2 1 2.验证是否安装成功 sudo /etc/init.d/apache2 start 1 如果出现下面的提示,说明以已经安装启动成功。 [ ok ] Starting apache2 (via systemctl): apache2.service. 1 安装gerrit 如果要去gerrit官网下安装包,是需要fq的,我下载了一个最新的包放在github上,有需要的同学自行下载: https://github.com/miguoer/software/blob/master/gerrit/gerrit-2.14.6.war 1 下面开始安装。你可以创建一个单独的账户来安装gerrit,也可以直接在当前用户环境下安装。我选择的是就在当前用户环境下安装。 选择自己专门放安装软件的目录,将下载好的war包复制进去,然后执行 java -jar gerrit-2.14.6.war init -d review_site 1 review_site是安装后文件目录名。 执行完这行命令,会出现安装提示: huanglin@huanglin:~/Software$ java -jar gerrit-2.14.6.war init -d test Using secure store: com.google.gerrit.server.securestore.DefaultSecureStore [2017-12-06 16:34:16,022] [main] INFO com.google.gerrit.server.config.GerritServerConfigProvider : No /home/huanglin/Software/test/etc/gerrit.config; assuming defaults *** Gerrit Code Review 2.14.6 *** Create ‘/home/huanglin/Software/test’ [Y/n]? y *** Git Repositories *** Location of Git repositories [git]: *** SQL Database *** Database server type [h2]: *** Index *** Type [lucene/?]: *** User Authentication *** Authentication method [openid/?]: http(选择http的认证方式,避免使用默认openid,需要fq的) Get username from custom HTTP header [y/N]? SSO logout URL : Enable signed push support [y/N]? *** Review Labels *** Install Verified label [y/N]? y(这里写y,方便后续使用Jenkins做持续集成) *** Email Delivery *** SMTP server hostname [localhost]: SMTP server port [(default)]: SMTP encryption [none/?]: SMTP username : *** Container Process *** Run as [huanglin]: Java runtime [/usr/java/jdk1.8.0_152/jre]: Copy gerrit-2.14.6.war to test/bin/gerrit.war [Y/n]? Copying gerrit-2.14.6.war to test/bin/gerrit.war *** SSH Daemon *** Listen on address [*]: Listen on port [29418]: Generating SSH host key … rsa… dsa… ed25519… ecdsa 256… ecdsa 384… ecdsa 521… done *** HTTP Daemon *** Behind reverse proxy [y/N]? y Proxy uses SSL (https://) [y/N]? Subdirectory on proxy server [/]: Listen on address [*]: Listen on port [8081]: Canonical URL [http://huanglin/]: *** Cache *** *** Plugins *** ***注意这些插件最好都行选择安装,以防后续需要用到的时候没有,还要手动安装。 Installing plugins. Install plugin commit-message-length-validator version v2.14.6 [y/N]? y Installed commit-message-length-validator v2.14.6 Install plugin download-commands version v2.14.6 [y/N]? y Installed download-commands v2.14.6 Install plugin hooks version v2.14.6 [y/N]? …后面的插件也一直填 y ,按enter执行下一步 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 配置 1.修改review_site/etc/ 下的gerrit.config文件 。把canonicalWebUrl配置为本地服务器(这里就是我的电脑)IP。listenUrl为代理服务器的IP,此处端口配置为了8090。 [gerrit] basePath = git serverId = 6ae95b16-d269-480e-9319-0be348717a1b canonicalWebUrl = http://localhost/ [database] type = h2 database = /home/huanglin/Software/review_site/db/ReviewDB [index] type = LUCENE [auth] type = HTTP [receive] enableSignedPush = true [sendemail] smtpServer = localhost [container] user = huanglin javaHome = /usr/java/jdk1.8.0_152/jre [sshd] listenAddress = *:29418 [httpd] listenUrl = http://*:8090/ [cache] directory = cache 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 2.添加gerrit用户 (1)在任意目录(我的是/home/huanglin/Software/)下创建一个保存密码的文件 $ cd /home/huanglin/Software/ $ touch pwd 1 2 (2)添加用户名密码 $ htpasswd -b pwd admin ****** 1 第一个登录gerrit的用户将自动作为管理员账户。 3.配置Apache2反向代理 (1)进入apache2目录 cd /etc/apache2/ 1 (2)修改port.conf文件,添加了对8091端口的监听。这个端口是后续在浏览器中访问gerrit时输入的端口。前面的8090端口是实际访问的端口。 # If you just change the port or add more ports here, you will likely also # have to change the VirtualHost statement in # /etc/apache2/sites-enabled/000-default.conf Listen 80 Listen 8091 Listen 443 Listen 443 # vim: syntax=apache ts=4 sw=4 sts=4 sr noet 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (3)在apache2目录下新建httpd.conf文件,编辑该文件。 $ touch httpd.conf $ sudo gedit httpd.conf 1 2 然后把下面的代码添加到httpd.conf中,当用户访问localhost:8091时,会被代理到http://localhost:8090/。这个8090端口必须和gerrit.conf中配置的代理服务器的端口一致。 AuthUserFile 为上面创建的用于保存gerrit用户密码的文件。 ServerName localhost ProxyRequests Off ProxyVia Off ProxyPreserveHost On AllowEncodedSlashes On Order deny,allow Allow from all AuthType Basic AuthName “Gerrit Code Review” Require valid-user AuthBasicProvider file AuthUserFile /home/huanglin/Software/pwd ProxyPass / http://localhost:8090/ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 验证 完成以上步骤之后,基本上就大功告成了。下面我们来验证下是否成功。 1. 重启Gerrit服务和Apache2服务 $ /home/huanglin/Software/review_site/bin/gerrit.sh restart $ /etc/init.d/apache2 restart 1 2 2.打开浏览器,输入http://localhost:8091/。这时如果出现提示输入用户名密码的窗口,就说明搭建成功了。 创建第一个项目 配置gerrit 添加ssh的公钥 本地打开终端,输入命令 ssh-keygen 1 公钥默认保存的地址为home目录下的 .ssh.id_rsa 打开~./.ssh/id_rsa.pub,把里面的内容复制到gerrit/settings/SSH Public Key里面。 进入Projects,点击Create New Project,务必勾选 Create initial empty commit 否则拉代码时会有问题。 项目创建好之后,进入项目页,点击刚创建的项目,就有clone地址。通过这个地址就可以拉代码到本地了。 提交代码:当本地代码有修改后,请执行一下提交流程。 // $ git add . //本地commit $ git commit -m “commit msg” //合并服务器代码,可以选择合并的分支,这里是合并master分支 $ git rebase origin/master //push 代码到服务器 $ git push origin HEAD:refs/for/master 1 2 3 4 5 6 7 8 注意: 如果是第一次push代码,可能会报错 remote: Processing changes: refs: 1, done remote: ERROR: [4a91168] missing Change-Id in commit message footer remote: remote: Hint: To automatically insert Change-Id, install the hook: remote: gitdir=$(git rev-parse –git-dir); scp -p -P 29418 huanglin@192.168.0.221:hooks/commit-msg ${gitdir}/hooks/ remote: And then amend the commit: remote: git commit –amend remote: To ssh://huanglin@192.168.0.221:29418/testdddddd ! [remote rejected] HEAD -> refs/for/master ([4a91168] missing Change-Id in commit message footer) 1 2 3 4 5 6 7 8 9 10 这个时候只需要把报错信息中如下内容复制到终端,按回车键。再执行 git commit –amend。然后重新push就大功告成了。 gitdir=$(git rev-parse –git-dir); scp -p -P 29418 huanglin@192.168.0.221:hooks/commit-msg ${gitdir}/hooks/ 1 插件扩展 默认安装的gerrit可能功能并不是很完整,我在安装完默认所有的插件之后,发现gerrit操作页面上没有删除项目的选项,一番搜索之后原来是需要安装一个delete-project的插件。所以如果大家需要扩展什么功能,要下插件的话,这里也分享一个插件下载的网站 https://gerrit-ci.gerritforge.com/view/Plugins-stable-2.13/ 1.下载插件 2.将下载好的插件放在安装目录下的plugins文件夹下面 3.重启gerrit服务,从浏览器重新进去,点击plugins/installed,就能看到刚安装的插件了。 参考 gerrit官方文档 https://git.eclipse.org/r/Documentation/install-quick.html#_project_creation ——————— 本文来自 HuangLin_Developer 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/huanglin_developer/article/details/78732306?utm_source=copy

Linux -apache – htpasswd命令详解

创建用户:

svn@git:~/gerrit$ htpasswd -b passwords test922 123456
Adding password for user test922

删除用户:

svn@git:~/gerrit$ htpasswd -D passwords test922
Deleting password for user test922

 

 

 

 

今天先 分享apache htpasswd命令如何使用,即apache htpasswd命令用法介绍。
1、在apache安装目录bin下找到htpasswd.exe

2、在命令行方式下输入htpasswd -help命令,显示apache htpasswd命令帮助信息,注意需要在htpasswd.exe的当前目录下,即Apache\bin目录下使用htpasswd命令

apache htpasswd命令用法及选项说明

apache htpasswd help帮助命令说明

apache htpasswd命令用法

htpasswd [-cmdpsD] passwordfile username

htpasswd -b[cmdpsD] passwordfile username password

htpasswd -n[mdps] username

htpasswd -nb[mdps] username password

apache htpasswd命令选项参数说明

-c  创建一个加密文件

-n  不更新加密文件,只将apache htpasswd命令加密后的用户名密码显示在屏幕上

-m  默认apache htpassswd命令采用MD5算法对密码进行加密

-d  apache htpassswd命令采用CRYPT算法对密码进行加密

-p  apache htpassswd命令不对密码进行进行加密,即明文密码

-s  apache htpassswd命令采用SHA算法对密码进行加密

-b  在apache htpassswd命令行中一并输入用户名和密码而不是根据提示输入密码

-D  删除指定的用户

在Windows, NetWare and TPF 系统中 ‘-m’选项是默认的,在使用apache htpasswd命令时可以忽略。在其他系统中,’-p’选项可能不能工作。

apache htpasswd命令用法实例

1、如何利用htpasswd命令添加用户?

htpasswd -bc .passwd www.leapsoul.cn php

在bin目录下生成一个.passwd文件,用户名www.leapsoul.cn,密码:php,默认采用MD5加密方式

2、如何在原有密码文件中增加下一个用户?

htpasswd -b .passwd leapsoul phpdev

去掉c选项,即可在第一个用户之后添加第二个用户,依此类推

3、如何不更新密码文件,只显示加密后的用户名和密码?

htpasswd -nb leapsoul phpdev

不更新.passwd文件,只在屏幕上输出用户名和经过加密后的密码

4、如何利用htpasswd命令删除用户名和密码?

htpasswd -D .passwd leapsoul

5、如何利用htpasswd命令修改密码?

htpasswd -D .passwd leapsoul

htpasswd -b .passwd leapsoul phpdev

即先使用htpasswd删除命令删除指定用户,再利用htpasswd添加用户命令创建用户即可实现修改密码的功能。

至此,apache htpasswd命令的具体介绍和使用方法就介绍完了。

转自: http://361324767.blog.163.com/blog/static/1149025252012727109254/

———————

本文来自 ljchlx 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/ljchlx/article/details/22039965?utm_source=copy

kernel: TCP: time wait bucket table overflow的问题剖析及解决方法

随着访问量的增大,系统默认的承受能力达到上限,这个时候就会报一些异常。比如/var/log/messages中常见的“kernel: TCP: time wait bucket table overflow”这个信息,会发现每隔5s就会报出几行。此时查看连接状态如下:

[root@ZhOu ~]# netstat -an | awk '{print $6}' | sort | uniq -c | sort -rn
  16539 TIME_WAIT
   3159 ESTABLISHED
     23 LISTEN
     20 CONNECTED
      5 STREAM
      4 
      1 I-Node
      1 Foreign
      1 established)
      1 and
      1 9793
      1 938869322
      1 8751
      1 7183
      1 7182
      1 7168
      1 10007
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

可以看到TIME_WAIT的量还是很高的。下面先看一下TCP连接的过程

这里写图片描述

通过此图先说明几个概念:

TIME_WAIT的产生条件:主动关闭方在发送四次挥手的最后一个ACK会变为TIME_WAIT状态,保留次状态的时间为两个MSL(linux里一个MSL为30s,是不可配置的)

TIME_WAIT两个MSL的作用:可靠安全的关闭TCP连接。比如网络拥塞,主动方最后一个ACK被动方没收到,这时被动方会对FIN开启TCP重传,发送多个FIN包,在这时尚未关闭的TIME_WAIT就会把这些尾巴问题处理掉,不至于对新连接及其它服务产生影响。

TIME_WAIT占用的资源:少量内存(查资料大概4K)和一个fd。

TIME_WAIT关闭的危害:1、 网络情况不好时,如果主动方无TIME_WAIT等待,关闭前个连接后,主动方与被动方又建立起新的TCP连接,这时被动方重传或延时过来的FIN包过来后会直接影响新的TCP连接;

2、 同样网络情况不好并且无TIME_WAIT等待,关闭连接后无新连接,当接收到被动方重传或延迟的FIN包后,会给被动方回一个RST包,可能会影响被动方其它的服务连接。

打印“time wait bucket table overflow”信息的代码:

    void tcp_time_wait(struct sock *sk, int state, int timeo)
    {
        struct inet_timewait_sock *tw = NULL;
        const struct inet_connection_sock *icsk = inet_csk(sk);
        const struct tcp_sock *tp = tcp_sk(sk);
        int recycle_ok = 0;

        if (tcp_death_row.sysctl_tw_recycle && tp->rx_opt.ts_recent_stamp)
            recycle_ok = icsk->icsk_af_ops->remember_stamp(sk);

        if (tcp_death_row.tw_count < tcp_death_row.sysctl_max_tw_buckets)
            tw = inet_twsk_alloc(sk, state);

        if (tw != NULL) {
            struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw);
            const int rto = (icsk->icsk_rto << 2) - (icsk->icsk_rto >> 1);
        ....
        } else {
                /* Sorry, if we're out of memory, just CLOSE this
                 * socket up. We've got bigger problems than
                 * non-graceful socket closings.
                 */
                LIMIT_NETDEBUG(KERN_INFO "TCP: time wait bucket table overflow\n");
        } 
  • 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

此段代码处于tcp套接字关闭流程,此时本机主动关闭tcp套接字,套接字状态=变为time wait,等待对端进行关闭套接字。
从代码可以看出,有两种情况可能导致这样的打印:
1、当tcp_death_row.tw_count < tcp_death_row.sysctl_max_tw_buckets时,即当当前处于time wait状态的socket数量超过sysctl_max_tw_buckets(即net.ipv4.tcp_max_tw_buckets)时
2、当inet_twsk_alloc(sk, state)返回为NULL时,出现这样的打印,再看看inet_twsk_alloc(sk, state)的代码:


    struct inet_timewait_sock *inet_twsk_alloc(const struct sock *sk, const int state)
    {
        struct inet_timewait_sock *tw =
            kmem_cache_alloc(sk->sk_prot_creator->twsk_prot->twsk_slab,
                     GFP_ATOMIC);
    ...
    return tw;
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

由此可以看出,当kmem_cache_alloc(从slab中分配内存)失败时,会返回NULL,最终导致这样的打印,这种情况可能的原因为内存不足。
综上,打印出现有两种可能原因:
1) 处于time wait状态的tcp套接字,超过net.ipv4.tcp_max_tw_buckets限制
2) 申请内存失败,可能原因为内存不足

该打印不影响系统的正常功能,当出现这种情况时,出现异常的套接字打印完成后会立即释放,不再等对端确认关闭。但有隐患,如果表示处于time wait的socket太多,则可能导致无法创建新的连接。这种可能是业务代码在socket关闭的处理上有些问题,可能存在大量半关闭的socket。另外,也可能表示内存不足,需要关注。

解决方法

既然知道了TIME_WAIT的用意了,尽量按照TCP的协议规定来调整,对于tw的reuse、recycle其实是违反TCP协议规定的,服务器资源允许、负载不大的条件下,尽量不要打开,当出现TCP: time wait bucket table overflow,需要调整/etc/sysctl.conf中下面参数:

net.ipv4.tcp_max_tw_buckets = 50000 
调大timewait 的数量

net.ipv4.tcp_fin_timeout = 10  
如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2 状态的时间。对端可以出错并永远不关闭连接,甚至意外当机。缺省值是60 秒。2.2 内核的通常值是180 秒,3你可以按这个设置,但要记住的是,即使你的机器是一个轻载的WEB 服务器,也有因为大量的死套接字而内存溢出的风险,FIN- WAIT-2 的危险性比FIN-WAIT-1 要小,因为它最多只能吃掉1.5K 内存,但是它们的生存期长些。

net.ipv4.tcp_tw_recycle= 1   
启用timewait 快速回收

net.ipv4.tcp_tw_reuse = 1    
开启重用,允许将TIME-WAIT sockets 重新用于新的TCP 连接

net.ipv4.tcp_keepalive_time = 15  
当keepalive 启用的时候,TCP 发送keepalive 消息的频度,缺省是2 小时  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

设置完毕,查看messages中的信息和连接状态,一切正常了。

 

参考文章

http://benpaozhe.blog.51cto.com/10239098/1767612

iptables:添加SNAT后就不能保留源地址了(即使设置网关为iptables机器ip)

添加SNAT后就不能保留源地址了(即使设置网关为iptables机器ip):
如:
主机1:iptables服务器配置:
[root@xjjwt ~]# ip a|grep 192
inet 192.168.1.89/24 brd 192.168.1.255 scope global eth0
[root@xjjwt ~]# iptables-save
# Generated by iptables-save v1.4.7 on Wed Sep 12 16:06:08 2018
*nat
:PREROUTING ACCEPT [1503:114205]
:POSTROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A PREROUTING -d 192.168.1.89/32 -p tcp -m tcp –dport 80 -j DNAT –to-destination 192.168.1.182:80
-A POSTROUTING -d 192.168.1.182/32 -p tcp -m tcp –dport 80 -j SNAT –to-source 192.168.1.89
-A POSTROUTING -o eth0 -j MASQUERADE
-A POSTROUTING -j MASQUERADE
COMMIT
# Completed on Wed Sep 12 16:06:08 2018
# Generated by iptables-save v1.4.7 on Wed Sep 12 16:06:08 2018
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [61:4425]
:OUTPUT ACCEPT [821:113448]
-A INPUT -m state –state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state –state NEW -m tcp –dport 22 -j ACCEPT
-A INPUT -j REJECT –reject-with icmp-host-prohibited
-A FORWARD -d 192.168.0.0/24 -j ACCEPT
-A FORWARD -d 192.168.1.182/32 -p tcp -m tcp –dport 80 -j ACCEPT
COMMIT
# Completed on Wed Sep 12 16:06:08 2018
[root@xjjwt ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0
0.0.0.0 192.168.1.1 0.0.0.0 UG 0 0 0 eth0
[root@xjjwt ~]#

抓包:


主机二:
[root@nginx ~]# ip a|grep 192
inet 192.168.1.182/24 brd 192.168.1.255 scope global eth0
[root@nginx ~]#
[root@nginx ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0
0.0.0.0 192.168.1.89 0.0.0.0 UG 0 0 0 eth0
[root@nginx ~]#

抓包:

From 192.168.1.89 icmp_seq=1 Destination Host Prohibited

ping 1.1.1.1
From 1.1.1.1 icmp_seq=1 Destination Host Prohibited

出现这个问题原因是因为服务器上iptables配置原因造成的。

干脆点的话,直接

apt remove iptablels

其他解决方法:
检查filter表中的FORWARD链

iptables -t filter --list

看看结果中是否有这一句

REJECT all -- anywhere anywhere reject-with icmp-host-prohibited
或iptables -D FORWARD -j REJECT --reject-with icmp-host-prohibited)

如果有,就删除它

iptables -L INPUT --line-numbers 

找到这一行的行号,我的主机上显示为11行

执行删除命令,删除第11行

iptables -D INPUT 11 #-D是删除参数