root登录linux的时候,报“Permission denied, please try again.”

用root远程登录的时候,用户名和密码都正确,但是报错:Permission denied, please try again.

先说一下,用户登录linux的几种方式:
1. 通过密码方式
2. 通过公钥方式
3. 通过键盘交互方式:登录的时候提示你输入用户名和密码那种方式

需要这么修改:

首先修改 /etc/ssh/sshd_config 中的 PermitRootLogin,这个 PermitRootLogin 有以下几个值可以选择,通过 man sshd_config 可以看到具体哪几个值可以选。

yes:这个是默认值,允许上面3种登录方式。
prohibit-password 和 without-password:这2个是一样的,禁止密码方式、禁止键盘交互方式,只允许公钥方式。
forced-commands-only:可以登录,但是登陆后不能进入交互,而是执行指定的命令后 自动退出,指定的命令在authorized_keys里面列出,例如/bin/date
no:不允许root远程登录

所以,我们设置 PermitRootLogin yes,然后 systemctl restart sshd 即可以解决上面这个问题。

docker的网络互联中的3个注意点

容器内访问外网

只要宿主机能访问外网,那么天生的容器就能自然而然的访问外网。假如你的容器访问不了外网,那么就需要知道容器能访问外网是怎么做到的,然后再去排查问题。

这是宿主机和容器之间采用了 ip_forward 技术,那么什么是 ip_forward,请看这篇文章:ip_forward与路由转发。不过这篇文章讲得比较深奥,我是没有太看明白,只是明白了这么一点浅显的道理:内网、容器、虚拟机要访问外网,那么在网关(针对内网)、宿主机(针对容器、虚拟机)上要设置 ip_forward。

内网 --> 网关 --> 外网 : 那么需要在网关上设置ip_forward
容器 --> 宿主机 --> 外网 : 那么需要在宿主机上设置ip_forward
虚拟机 --> 宿主机 --> 外网 : 那么需要在宿主机上设置ip_forward

在宿主机上查看 ip_forward 有没有打开的方法:

cat /proc/sys/net/ipv4/ip_forward
或者
sysctl net.ipv4.ip_forward

对于sysctl,可以查看这篇文章:《lsmod,modprobe和sysctl的说明》

在docker安装的时候,安装程序会将 net.ipv4.ip_forward 设置为1,同时安装程序进一步会将 net.ipv4.conf.all.forwarding 也设置为1。
我估计对于虚拟机的安装程序来说,也会有这样的操作。

容器之间的互联

这里只讲在一台机器上的容器之间通信,同时涉及以下情况:
1. 不涉及:参见我以前的博文《docker容器之间通过link方式的互联》
2. 不涉及 docker network create 创建的容器之间的网络
3. 不涉及 overlap 网络,比如 ingress 和 flannel

比如有2个容器,a容器的ip是 172.17.0.2(其中有一个nginx,对外暴露了80端口),b容器的ip是 172.17.0.3 ,那么在 b容器中:
telnet 172.17.0.2 80
OK,没有问题。

所以,在一台机器内的容器,只要2个都属于同一个 虚拟 bridge,比如默认的 docker0,那么通过IP互联没有任何问题。

容器通过宿主机的IP和端口去访问另一个容器对外暴露的端口

上面这句话很绕口,我们来举个例子:
宿主机的IP是 192.168.1.100
a容器内有一个nginx,对外暴露了80端口,比如 docker run -d -p 80:80 nginx
在b容器内进行:

ping 192.168.1.100  #OK,没有问题,说明能访问到宿主机
telnet 192.168.1.100 80 #不行了,说明能访问到宿主机,但是因为宿主机的防火墙给挡住了

那么我们修改一下宿主机的iptables,让来自 docker0 这个虚拟网卡的流量能通行:
iptables -I INPUT chainnumber -i docker0 -j ACCEPT
以后修改iptables尽量不要用 iptables -A,因为 iptables 的策略中有可能最后一条是 reject all,所以必须要插在 reject all 这一条之前。
那么上面中的 chainnumber 用 iptables -L -n –line-numbers 来查看,具体大家可以去查互联网资料。

修改后iptables后,这样就可以在b容器访问宿主机的某个容器暴露端口了。

lsmod,modprobe和sysctl的说明

lsmod

我们经常听到linux内核,非常的高大上,内核中包含很多模块组件,构成了整个完整的内核。

如何查看linux装载的内核?很简单,用 lsmod 命令,然后会显示一大堆内核中内含的模块,我们想要看某一个模块有没有,可以用如下的命令,举例:

lsmod | grep ip_tables
ip_tables              27126  2 iptable_filter,iptable_nat

出来3列,这3列的名称我们先用一个例子举出来,然后再来解释:

lsmod | egrep 'Module|ip_tables'
Module                  Size  Used by
ip_tables              27126  2 iptable_filter,iptable_nat

Module: 模块名称
Size: 模块的大小
Used by:第1个数字表示这个模块被其他的几个模块所使用(其实表示了依赖关系),后面的是依赖这个模块的其他模块

modprobe

用来加载和移除模块的命令,比如:

modprobe br_netfilter  #加载
modprobe -r br_netfilter  #移除

sysctl

上面说了加载模块,现在来了问题了,每个模块都有参数需要设定的,那么如何修改这些参数,这些参数又是存储在哪里的,这个就是 sysctl 的作用了。

sysctl 说简单点就是用来修改内核模块参数的,嗯,非常好理解。可以看这篇介绍sysctl的文章:sysctl介绍,基本上该有的都有了,下面是对这篇文章的一些小补充:

  1. 内核模块的参数存储在 /proc/sys 这个目录下面,比如我们修改:
    sysctl -w net.ipv4.ip_forward=1
    那么实际上是 net.ipv4.ip_forward 按照 . 这个句号进行切分,前面的是目录,最后一个是文件,所以这个参数的位置在
    /proc/sys/net/ipv4/ip_forward这个文件中,一个参数一个文件,每个文件就是含有这个参数的值,所以,我们可以直接用下面的命令也是和sysctl一样的作用:
    echo 1 > /proc/sys/net/ipv4/ip_forward
  2. 按照上文中说,sysctl -w net.ipv4.ip_forward=1 中的 -w 是临时的意思,其实不是,w的意思是写到 /proc/sys/net/ipv4/ip_forward 这个文件中。
  3. 当系统重启,或者服务重启后,这个文件中的配置将丢失,为了使得重启后还是生效的,上文中说写在 /etc/sysctl.conf 这个文件中,其实按照标准的做法,应该是在 /etc/sysctl.d/这个目录中增加一个文件,文件名随便,然后将 net.ipv4.ip_forward=1 写在这个文件中。这种模式就好比 nginx 的配置文件一样,也不知道是 nginx 学习 linux 的呢,还是 linux 学习 nginx。这种配置非常合理,大家不用挤在 sysctl.conf 这一个文件中来修改。

在console中使用kvm的常用管理命令

KVM安装好后,最方便的使用KVM的界面管理器,但是有时候通过远程连接登录宿主机,不方便做到用界面来管理虚拟机,这个时候,就需要用到虚拟机的命令来管理了。

参考这篇文章,比较简单,当作入门:centos7KVM虚拟化常用虚机管理命令

这里对这些命令做个简单的补充:
1. 直接在宿主机中键入 virsh,即可以进入KVM的专有console,其中通过help能看到所有的KVM命令,比上面那篇文章中还要简单。
2. virsh list,可以查看哪些虚拟机在运行,不运行的不显示;通过

virsh list --all

可以看到所有的虚拟机,其中有名称(name)这一列用在后续的很多命令中
3. 在宿主机中可以查看虚拟机的一些基本信息

virsh dominfo your_vm_name
  1. 在宿主机中可以查看虚拟机的网络信息
virsh domifaddr your_vm_name
  1. 在宿主机中启动虚拟机
virsh start your_vm_name

但是我想知道这个虚拟机的更多信息,比如网络;或者我想登录这个虚拟机进一步操作这个虚拟机,那怎么办?

virsh console your_vm_name

但是这个命令键入后会卡在那里,没有反应,原因是虚拟机内部还需要事先设置一下,这样宿主机才能登录,如此设置:

systemctl start serial-getty@ttyS0.service
systemctl enable serial-getty@ttyS0.service

设置好后,再在宿主机中键入上面的console命令,然后再回车1下,或者几下都可以,这样就能登录了。要从虚拟机中登出,ctrl + ] 就可以退出到宿主机了。

基于docker的gitlab安装、设置和内存优化

基于docker的gitlab安装,大家一定要看一下官方文档: https://docs.gitlab.com/omnibus/docker/

我的这个gitlab安装有一些约束条件:
1. 没有域名,只有IP
2. 服务器内存只有4G
3. 内部人员协同开发,无需使用电子邮件
4. 仅仅用于最基本的git协同开发,没有使用auto devops
5. 没有采用 https,只暴露80端口,其他的22端口也无需暴露
6. 没有采用 Prometheus
7. 采用 docker-compose 来安装

docker-compose.yml 配置如下:

 gitlab:
   image: 'gitlab/gitlab-ce:latest'
   restart: always
   container_name: gitlab
   environment:
     - GITLAB_OMNIBUS_CONFIG=external_url 'http://192.168.1.100'
     - TZ=Asia/Shanghai
   ports:
     - '80:80'
   volumes:
     - '/srv/gitlab/config:/etc/gitlab'
     - '/srv/gitlab/logs:/var/log/gitlab'
     - '/srv/gitlab/data:/var/opt/gitlab'

这个配置文件解释一下:
TZ=Asia/Shanghai详见这篇文章:设定docker容器时间的3种方法

通过 docker-compose up -d gitlab 起来后,别忘记了有一个安全问题,仅内部访问,限制外界访问,详见这篇文章:Docker中限制网络访问中的ext_if是什么?

gitlab起来后,登录 root 的账号,登录后,进行应用管理设置,其中关闭 auto devops 这个功能:
Admin area –> Settings –> CI/CD –> Default to Auto DevOps pipeline for all projects 前面的勾去掉。

修改 /srv/gitlab/config/gitlab.rb,将 prometheus 禁用:
prometheus_monitoring[‘enable’] = false

内存优化

上述过程做好后,过了一天,发现gitlab运行相当慢,一看服务器内存已经没有了,而且使用了大量的swap,于是上网查如何内存优化 gitlab,找到了这篇讲得很全面的文章:解决GitLab内存消耗大的问题。最终我的优化如下:

unicorn['enable'] = true
unicorn['worker_timeout'] = 60
unicorn['worker_processes'] = 2

unicorn['worker_memory_limit_min'] = "300 * 1 << 20"
unicorn['worker_memory_limit_max'] = "500 * 1 << 20"

sidekiq['concurrency'] = 16

postgresql['shared_buffers'] = "256MB"
postgresql['max_worker_processes'] = 8

重启后,观察了2天,内存始终消耗为 1.5-2 G之间。


上述的内容已经有些过时了,最新的gitlab有些不同了,这里做一些补充

  • 假如安装的时候,外网不能用80端口,那么docker-compose配置文件应该这么设定:
 gitlab:
   image: 'gitlab/gitlab-ce:latest'
   restart: always
   container_name: gitlab
   environment:
     - GITLAB_OMNIBUS_CONFIG=external_url 'http://192.168.1.100:8000'
     - TZ=Asia/Shanghai
   ports:
     - '8000:8000'
   volumes:
     - '/etc/gitlab/config:/etc/gitlab'
     - '/etc/gitlab/logs:/var/log/gitlab'
     - '/etc/gitlab/data:/var/opt/gitlab'
  • 内存优化已经改了,成了:

puma[‘enable’] = true
puma[‘worker_timeout’] = 60
puma[‘worker_processes’] = 2
puma[‘max_threads’] = 4
puma[‘per_worker_max_memory_mb’] = 1024
sidekiq[‘max_concurrency’] = 16
postgresql[‘shared_buffers’] = “256MB”
postgresql[‘max_worker_processes’] = 8

  • 安装好后,初次登录,居然不能指定root的密码了,需要参照官方文档来进行设定:https://docs.gitlab.com/ee/security/reset_user_password.html ,但是这个文档只是涉及在服务器上传统安装gitlab,那么在docker中该如何设定呢?

先进入容器内,docker exec -it gitlab /bin/bash,然后执行 gitlab-rake “gitlab:password:reset”,意外的事情发生了,居然久久没有反应,感觉像死掉一样,过了大概5分钟,才会出来一些提示,在这个5分钟内,假如你等不及敲了一些回车,对不起,这个过程还要重新再来。所以记住:这个过程会很长,不要着急,千万不要敲回车。(这一段是修改密码的,现在的gitlab有设定初始密码的地方,见下面)

  • Visit the GitLab URL, and log in with username root and the password from the following command:

docker exec -it gitlab grep ‘Password:’ /etc/gitlab/initial_root_password

Dockerfile中的COPY中的context(上下文)指得是什么?

Dockerfile中的COPY作用、格式的详细描述不是本文所要涉及的。本文只是聊一下COPY时候要注意的context。

我们先来简单看一下COPY的第一种格式,第二种格式不说了:

COPY <src>... <dest>

dest: 指得是镜像(image)中的路径
src: 指得是Dockerfile所在的context路径
那么COPY的作用就是:将构建镜像的电脑上的文件或者文件夹拷贝到镜像中去。

我们知道,Dockerfile文件往往是放在github上的某个工程里,当我们checkout工程,然后执行 docker build 命令的时候,我们的电脑和作者的电脑肯定不一样的,所以,明白了这一点,基于这样最基本的常识,我们马上就能明白什么样的src是正确的,什么样的src是错误的:

正确:
COPY ./package.json /app/
COPY package.json /usr/src/app/

错误:
COPY ../package.json /app 因为上一层目录大家都不一样的
或者 COPY /opt/xxxx /app 有可能连操作系统都不一样,哪里有什么/opt这样的目录

所以,Dockerfile中的COPY中的context(上下文)指得就是 Dockerfile 所在的工程中的目录结构,离开了这个工程的其他目录都是错误的。

顺便说一下:dest 是可以在镜像中的任意位置的,这个无所谓,不用解释大家都明白了吧。

参考资料

dockerfile文件中copy上下文目录

Docker中限制网络访问中的ext_if是什么?

我们往往通过 -p 来使得外界访问docker中的容器,比如:
docker run -p 80:80 some-app-image

但是有下面几个场景需要知道:
1,其他容器想要访问这个容器的端口,外界不允许访问,这个容易,用docker的network既可以实现。
2,为了安全问题,只允许我所知道的外界来访问这个端口,除此以外我都不允许访问。

第2个场景经常出现,比如某服务器,有外网网卡,其中某个内网应用,通过上面的 -p 开放会使得外网也能访问。对此问题,我记得最早时候的docker是没有提到这个问题的(或者在某个地方我没有注意到),为此我还专门到网上去查找资料和询问别人,别人和我说需要对iptables进行设置。

后来,我再看docker官方文档的时候,发现了Restrict connections to the Docker daemon,限制连接访问docker,文档中举了几个例子,我们来看一下这个例子:

$ iptables -I DOCKER-USER -i ext_if ! -s 192.168.1.1 -j DROP
-I DOCKER-USER: 表示插入用户自定义的DOCKER-USER的chain中
-i ext_if: 针对 ext_if 网卡
! -s 192.168.1.1 -j DROP: 除了来源是 192.168.1.1 以外的地址全部不能访问

那 ext_if 是什么网卡?难道是 docker 又弄出来的?到网上查了一下,到处没有说起这个 ext_if 是什么,直到我最终在docker的官方文档:where ext_if is the name of the interface providing external connectivity to the host.中找到这样的话才明白,原来这个 ext_if 就是docker daemon所在服务器的网卡名称,也就是说换成比如 eth0 这样的即可。

这个让我想起了一个故事:
微软的很多安装,有时候会蹦出这样的一段话:“请按任意键继续”,于是很多人就打电话去问微软的客服:“请问任意键在键盘中的哪个位置,我怎么找不到这个键啊?”,后来微软为了解决这个歧义,把这句话改成了:“请按空格键继续”。

那么docker文档中的这句话也出现了歧义:iptables -I DOCKER-USER -i ext_if ! -s 192.168.1.1 -j DROP,应该改成这样就好了,否则找了一大圈最后才发现原来出现歧义了:iptables -I DOCKER-USER -i “your_extern_interface” ! -s 192.168.1.1 -j DROP,并且用双引号括起来,这样一来每个人都能明白了。

设定docker容器时间的3种方法

大部分的容器在制作的时候,时间都是UTC时间(又是格林威治标准时间GMT),所以我们在使用的时候,容器内部时间都是和北京时间相差8小时。

如何设定docker容器时间的3种方法,我们以alpine来举例,在举例之前,需要参考这篇文章《如何对alpine设置时区为Asia/Shanghai》,将alpine的时区功能补全,否则下面的举例不会起效:

1,容器启动后,进入容器内:
docker exec -it xxxx /bin/sh
然后执行下面的语句:
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
最后看一下时间,看到已经改成正确的:
date
这种方法在容器重启后时间又会回到UTC,所以只有在紧急的情况下才能用一下,这种方法是最不靠谱的。

2,自己做一个以alpine为基础的镜像,增加时区功能,Dockfile如下:

FROM       alpine
RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghai

这种方法的缺点很明显,大部分的镜像都是UTC时间,难道需要每个都做一个自己的镜像?所以这种方法往往用在制作自己应用的镜像时候。

为什么要安装tzdata这个包,见下面的参考资料《Linux – 时区和时间调整方法》,讲得很好。

3,启动容器时,加入环境变量,比如:

docer run -e TZ=Asia/Shanghai --rm myalpine date

这3种方法中,以这种方法最好最灵活。

参考资料

Linux – 时区和时间调整方法

如何对alpine设置时区为Asia/Shanghai

alpine是个非常简单的docker镜像,沿用以前的修改容器时间的方法都不能起效,因为它去掉了很多功能,其中就去掉了timezone这个功能,默认就是UTC时间。

我们进入alpine容器内看看里面的内容:
docker exec -it –rm alpine /bin/sh
> date
显示的是UTC时间,和北京时间相差8小时
> ls /usr/share/zoneinfo/
提示目录不存在

所以,我们需要对alpine补全这个timezone的功能,如此才能起效,Dockfile的内容如下:
FROM alpine
RUN apk add –no-cache tzdata
ENV TZ=Asia/Shanghai

为什么要安装tzdata这个包,见下面的参考资料《Linux – 时区和时间调整方法》,讲得很好。

参考资料

Docker Alpine 镜像设置东八区
https://github.com/gliderlabs/docker-alpine/issues/136
Linux – 时区和时间调整方法

如何在springboot中禁用某些jar的自动配置功能

我们知道在springboot中有这么一个自动配置的功能:假如你在工程中加入某个jar,比如mysql的jar,或者mongodb的jar,或者redis的jar,那么就算不写配置文件,springbboot也会启动相关的功能来简化构建这些客户端的复杂度。

但是有那么一种场景,比如我只想在工程中使用某个jar的某个类的功能,所以我必须要引入某个jar,但是又不想让springboot自动配置,那该怎么办?

其实很简单,只需要在启动类上进行配置,如下用mongodb来举例:

@SpringBootApplication(exclude={MongoAutoConfiguration.class})
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

这个时候来问题了,我假如想不包含 redis 的自动配置,那该如何写这个 XXXConfiguration.class 呢,这个到哪里找?
在回答这个问题之前,我们先来看一个错误提示,以redis的自动配置来举例,当设置不正确的时候,springboot启动的时候会进行报错,报错的内容大致如下:

Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled

with ‘debug’ enabled,这个是什么鬼?这个debug在哪里设置啊,难道在 logback 中?不是吧。带着这个问题,我查了资料,找到这篇文章:http://www.it1352.com/908199.html,这篇文章里讲了该如何在springboot中进行设置,哦,原来就是在 application.properties 中假如 debug=true 即可。顺便吐槽一下这篇文章的网站,里面广告太多了,感觉是一个。。。

好了,我们继续上面的那个redis那个例子,我们把 debug=true 设置好,好了,真相出来了:

    RedisAutoConfiguration matched:
      - @ConditionalOnClass found required classes 'org.springframework.data.redis.connection.jedis.JedisConnection', 'org.springframework.data.redis.core.RedisOperations', 'redis.clients.jedis.Jedis'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)

   RedisAutoConfiguration.RedisConfiguration#redisTemplate matched:
      - @ConditionalOnMissingBean (names: redisTemplate; SearchStrategy: all) did not find any beans (OnBeanCondition)

   RedisAutoConfiguration.RedisConfiguration#stringRedisTemplate matched:
      - @ConditionalOnMissingBean (types: org.springframework.data.redis.core.StringRedisTemplate; SearchStrategy: all) did not find any beans (OnBeanCondition)

   RedisAutoConfiguration.RedisConnectionConfiguration matched:
      - @ConditionalOnClass found required class 'org.apache.commons.pool2.impl.GenericObjectPool'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)

   RedisAutoConfiguration.RedisConnectionConfiguration#redisConnectionFactory matched:
      - @ConditionalOnMissingBean (types: org.springframework.data.redis.connection.RedisConnectionFactory; SearchStrategy: all) did not find any beans (OnBeanCondition)

   RedisCacheConfiguration matched:
      - Cache org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration automatic cache type (CacheCondition)

   RedisRepositoriesAutoConfiguration matched:
      - @ConditionalOnClass found required classes 'redis.clients.jedis.Jedis', 'org.springframework.data.redis.repository.configuration.EnableRedisRepositories'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)
      - @ConditionalOnProperty (spring.data.redis.repositories.enabled=true) matched (OnPropertyCondition)
      - @ConditionalOnMissingBean (types: org.springframework.data.redis.repository.support.RedisRepositoryFactoryBean; SearchStrategy: all) did not find any beans (OnBeanCondition)

于是,我们就可以这么设置我们的启动类:

@SpringBootApplication(exclude={RedisRepositoriesAutoConfiguration.class,RedisAutoConfiguration.class})
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

上面的RedisCacheConfiguration是个私有类,不能用,不用也达到了效果。

全文结束。