JoyLau's Blog

JoyLau 的技术学习与思考

LocalDateTime 将 long 格式的时间转化本地时间字符串

1
2
3
LocalDateTime
.ofEpochSecond(System.currentTimeMillis() / 1000, 0, ZoneOffset.ofHours(8))
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))

reduce 导致的源集合对象改变

例如下属代码导致 images 里的 DataImage 对象里的 stake 对象的数量改变

1
2
3
4
5
6
7
8
9
10
Map<String,List<HighwayStake>> roadStakeMap = images.stream()
.filter(image -> !image.getStakes().isEmpty())
.map(DataImage::getStakes())
.reduce((highwayStakes, highwayStakes2) -> {
highwayStakes2.addAll(highwayStakes);
return highwayStakes2;
})
.orElse(new ArrayList<>())
.stream()
.collect(Collectors.groupingBy(HighwayStake::getDlmc));

因为对 dataImage 的 stakes 集合进行了合并,将 map 操作改为 复制一个新的 list , 而不是操作原来的 stakes

1
2
3
4
5
6
7
8
9
10
Map<String,List<HighwayStake>> roadStakeMap = images.stream()
.filter(image -> !image.getStakes().isEmpty())
.map(dataImage -> new ArrayList<>(dataImage.getStakes()))
.reduce((highwayStakes, highwayStakes2) -> {
highwayStakes2.addAll(highwayStakes);
return highwayStakes2;
})
.orElse(new ArrayList<>())
.stream()
.collect(Collectors.groupingBy(HighwayStake::getDlmc));

List 的深度拷贝

上述的问题实际上是一个 list 的拷贝,而且是 浅度复制

new ArrayList<>(list)Collections.copy(dest,src) 都是浅度复制

下面代码是一个靠谱的 深度拷贝, 需要 T 实现序列化接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* list 深度复制
*/
public static <T> List<T> deepCopy(List<T> source) {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
List<T> dest = new ArrayList<>();
try {
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(source);

ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
dest = (List<T>) in.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return dest;
}

reduce() 使用记录

reduce 有三种方法可以使用:

  • Optional<T> reduce(BinaryOperator<T> accumulator)
  • T reduce(T identity, BinaryOperator<T> accumulator)
  • <U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner)

第一种传入二元运算表达式,第二种是带初始值的二元运算表达式,这里着重记录下第三种的使用方式

第三种第一个参数方法的返回值类型,
第二个参数是一个二元运算表达式,这个表达式的第一个参数是方法的返回值,也就是方法的第一个参数,第二个参数是 Stream 里的值
第三个参数也是一个二元运算表达式,表达式的2个参数都是方法返回值的类型,用于对返回值类型的操作

第三个参数在非并发的情况下返回任何类型(甚至是 null)都没有影响,因为在非并发情况下,第三个二元表达式根本不会执行

那么第三个二元表达式用在并发的情况下,在并发的情况下,第二个二元表达式的第一个参数始终是方法的第一个类型,第三个三元表达式用于将不同线程操作的结果汇总起来

map() 和 flatMap()

区别在于, map() 返回自定义对象, 而 flatMap() 返回 Stream 流对象

distinct() 使用记录

最近在 lamda 的 stream 进行 list 去重复的时候,发现没有生效
代码如下:

1
2
3
4
Map<String, Map<String, List<FollowAnalysisPojo>>> maps = allList
.parallelStream()
.distinct()
.collect(Collectors.groupingBy(FollowAnalysisPojo::getMainPlateNum,Collectors.groupingBy(FollowAnalysisPojo::getPlateNum)));

实体类:

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
@Data
public class FollowAnalysisPojo {
/*被跟车牌*/
private String mainPlateNum;
/*跟踪车牌*/
private String plateNum;
private String vehicleType;
private String siteName;
private String directionName;
/*车主时间*/
private String passTimeMain;
/*伴随时间*/
private String passTimeSub;
/*跟踪次数*/
private Integer trackCount;

/*该条记录被跟踪车占据的行数,用于在前端合并单元格*/
private Integer mainRowSpan = 0;

/*该条记录跟踪车占据的行数,用于在前端合并单元格*/
private Integer rowSpan;

private String key = UUID.randomUUID().toString();
}

上面的代码是想做 先对查询出来的数据进行去重复的操作,然后在按照被跟车牌和跟踪车牌进行分组操作
有点需要说明的是 parallelStream() 比我们常用的 stream() 是并行多管操作,速度上更快

然后发现的问题是并没有去重复,当时也在奇怪 distinct() 里并没有任何参数来指定如何使用规则来去重复

正解

重写List中实体类的 equals() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
public class FollowAnalysisPojo {
......
/**
* 当车主时间,伴随时间都相同时,则认为是一个对象
* @param obj 对象
* @return Boolean
*/
@Override
public boolean equals(Object obj) {
if(!(obj instanceof FollowAnalysisPojo))return false;
FollowAnalysisPojo followAnalysisPojo = (FollowAnalysisPojo)obj;
return passTimeMain.equals(followAnalysisPojo.passTimeMain) && passTimeSub.equals(followAnalysisPojo.passTimeSub);
}
}

这样我们就按照我自定义的规则进行去重复了
运行了一下,发现还是不起作用
debug了一下,发现根本没有执行重写的 equals 方法
原来还需要重写 hashCode() 方法
equals() 方法 执行前会先执行 hashCode() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
public class FollowAnalysisPojo {
......
/**
* 重新 equals 方法必须重新 hashCode方法
* @return int
*/
@Override
public int hashCode(){
int result = passTimeMain.hashCode();
result = 31 * result + passTimeMain.hashCode();
return result;
}
}

这样就可以了。

2018-9-13 更新

如果我们不重写方法,有没有办法按照List中bean的某个属性来去重复呢?答案是有的,利用的是 stream 的 reduce,用一个set 来存放 key,代码如下:

1
2
3
4
5
6
7
8
9
List<JSONObject> result = trails.stream()
.filter(distinctByKey(VehicleTrail::getPlateNbr))
.collect(Collectors.toList());


private <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(keyExtractor.apply(t));
}

2个集合的元素两两组合成一个 n * m 的集合 (笛卡尔积)

1
2
3
4
5
Integer[] xs = new Integer[]{3, 4};
Integer[] ys = new Integer[]{5, 6};

List<Image> images = Arrays.stream(xs).flatMap(x -> Arrays.stream(ys).map(y -> new Image(x,y))).collect(Collectors.toList());

集合合并

比如: List<List> list 将所有的 Demo 合并到一个集合;

  1. reduce
1
2
3
4
5
// 第一种
List<Demo> demos = list.stream().reduce(new ArrayList<>(),(demo1,demo2) -> {demo1.addAll(demo2); return demo2;});

// 第二种
List<Demo> demos = list.stream().reduce(new ArrayList<>(),(demo1,demo2) -> Stream.concat(demo1.stream(),demo2.stream()).collect(Collectors.toList()));
  1. flatMap
1
List<Demo> demos = list.stream().flatMap(Collection::stream).collect(Collectors.toList());

无法进入容器

docker exec -it name /bin/sh 失败,
查看容器 inspect 报错信息如下:

1
2
3
pc error: code = 2 desc = oci runtime error: exec failed: 
container_linux.go:247: starting container process caused "process_linux.go:110:
decoding init error from pipe caused \"read parent: connection reset by peer\""

问题分析

  1. docker 版本为: Docker version 1.13.1, build 07f3374/1.13.1
  2. centos 版本为: CentOS Linux release 7.3.1611 (Core)
  3. 错误原因: 似乎是 docker RPM 软件包的更新时引入的错误。一个临时的解决方法是将所有docker软件包降级到以前的版本(1.13.1-75似乎可以)

降级

1
yum downgrade docker docker-client docker-common

背景

我们在 docker-compose 一条命令就启动我们的多个容器时,需要考虑到容器之间的启动顺序问题…..

比如有的服务依赖数据库的启动, service 依赖 eureka 的启动完成

docker compose 里有 depends_on 配置,但是他不能等上一个容器里的服务完全启动完成,才启动下一个容器,这仅仅定义了启动的顺序, 那么这就会导致很多问题的发生

比如应用正在等待数据库就绪,而此时数据库正在初始化数据, 导致无法连接退出等等

官方的做法

地址 : https://docs.docker.com/compose/startup-order/
官方的思路是使用一个脚本,轮询给定的主机和端口,直到它接受 TCP 连接
个人感觉这种方式不是很好

还有几个开源的工具解决方法, 这些是一些小型脚本,和上面的原理类似:

  1. wait-for-it : https://github.com/vishnubob/wait-for-it
  2. dockerize : https://github.com/jwilder/dockerize
  3. wait-for : https://github.com/Eficode/wait-for

这些工具也能解决问题,但有很大的局限性: 需要重新定义 command , 在执行完自己的脚本后在执行容器里的启动脚本

如果不知道容器的启动脚本或者容器的启动脚本很长,并且带有参数,那将非常头疼

查看容器的启动脚本:

1
docker ps --no-trunc --format="table {{.ID}}\t{{.Command}}:"

或者

1
docker inspect container

health 健康检查方法

比如下面的配置

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
server:
image: 34.0.7.183:5000/joylau/traffic-service-server:1.2.0
container_name: traffic-service-server
ports:
- 9368:9368
restart: always
volumes:
- /Users/joylau/log/server:/home/liufa/app/server/logs
environment:
activeProfile: prod
hostname: traffic-service-eureka
healthcheck:
test: "/bin/netstat -anp | grep 9367"
interval: 10s
timeout: 3s
retries: 1
admin:
image: 34.0.7.183:5000/joylau/traffic-service-admin:1.2.0
container_name: traffic-service-admin
ports:
- 9335:9335
restart: always
volumes:
- /Users/joylau/log/admin:/home/liufa/app/admin/logs
environment:
activeProfile: prod
depends_on:
server:
condition: service_healthy
hostname: traffic-service-admin
links:
- server:traffic-service-eureka

server 使用了健康检查 healthcheck

  • test : 命令,必须是字符串或列表,如果它是一个列表,第一项必须是 NONE,CMD 或 CMD-SHELL ;如果它是一个字符串,则相当于指定CMD-SHELL 后跟该字符串, 例如: test: ["CMD", "curl", "-f", "http://localhost"] 或者 test: ["CMD-SHELL", "curl -f http://localhost || exit 1"] 或者 test: curl -f https://localhost || exit 1
  • interval: 每次执行的时间间隔
  • timeout: 每次执行时的超时时间,超过这个时间,则认为不健康
  • retries: 重试次数,如果 retries 次后都是失败,则认为容器不健康
  • start_period: 启动后等待多次时间再做检查, version 2.3 版本才有

interval, timeout, start_period 格式如下:

1
2
3
4
5
2.5s
10s
1m30s
2h32m
5h34m56s

健康状态返回 0 (health) 1 (unhealth) 2(reserved)

test 命令的通用是 'xxxx && exit 0 || exit 1' , 2 一般不使用

admin depends_on server ,且条件是 service_healthy ,即容器为健康状态,即 9368 端口开启

最后

  1. depends_on 在 2.0 版本就有, healthcheck 在 2.1 版本上才添加,因此上述的写法至少在 docker-compose version: ‘2.1’ 版本中才生效
  2. docker-compose version 3 将不再支持 depends_on 中的 condition 条件
  3. depends_on 在 version 3 中以 docker swarm 模式部署时,将被忽略

Docker 容器中 IP 的配置

将 spring cloud 项目部署到 docker 容器中后,虽然可以配置容器的端口映射到宿主机的端口
但是在 eureka 界面显示的instance id 是一串随机的字符串,类似于 d97d725bf6ae 这样的
但是,事实上,我们想让他显示出 IP ,这样我们可以直接点击而打开 info 端点信息

修改 3 处配置项:

1
2
3
4
5
6
7
8
eureka:
client:
service-url:
defaultZone: http://34.0.7.183:9368/eureka/
instance:
prefer-ip-address: true
instance-id: ${eureka.instance.ip-address}:${server.port}
ip-address: 34.0.7.183
  1. eureka.instance.prefer-ip-address 配置为 true , 表示 instance 使用 ip 配置
  2. eureka.instance.prefer-ip-address 配置当前 instance 的物理 IP
  3. eureka.instance.prefer-instance-id 界面上的 instance-id 显示为 ip + 端口

docker-compose 的解决方法

通常情况下,我们使用 springcloud 都会有很多的服务需要部署,就会产生很多的容器,这么多的容器再使用 docker 一个个操作就显得很复杂
这时候需要一个编排工具,于是我们就使用 docker-compose 来部署 springcloud 服务

  1. 修改 eureka 的配置
1
2
3
4
5
6
spring:
application:
name: traffic-service-eureka
eureka:
instance:
hostname: ${spring.application.name}

使用 docker-compose 我们放弃使用 ip 来进行容器间的相互通信,继而使用 hostname,这就相当于在 /etc/hosts 添加了一条记录

  1. 接下来所有的 eureka 的 client 都使用 traffic-service-eureka 这个 hostname 来连接
1
2
3
4
eureka:
client:
service-url:
defaultZone: http://traffic-service-eureka:9368/eureka/
  1. 如果说想在 eureka 的界面上能够直接显示宿主机的 IP 和 连接地址的话,还需要设置
1
2
3
4
5
eureka:
instance:
prefer-ip-address: true
instance-id: ${eureka.instance.ip-address}:${server.port}
ip-address: 34.0.7.183
  1. docker-compose 的配置:
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
server:
image: 34.0.7.183:5000/joylau/traffic-service-server:1.2.0
container_name: traffic-service-server
ports:
- 9368:9368
restart: always
volumes:
- /Users/joylau/log/server:/home/liufa/app/server/logs
environment:
activeProfile: prod
hostname: traffic-service-eureka
healthcheck:
test: "/bin/netstat -anp | grep 9368"
interval: 10s
timeout: 3s
retries: 1
admin:
image: 34.0.7.183:5000/joylau/traffic-service-admin:1.2.0
container_name: traffic-service-admin
ports:
- 9335:9335
restart: always
volumes:
- /Users/joylau/log/admin:/home/liufa/app/admin/logs
environment:
activeProfile: prod
depends_on:
server:
condition: service_healthy
hostname: traffic-service-admin
links:
- server:traffic-service-eureka

service 模块 links server 模块,再起个别名 traffic-service-eureka ,因为我配置文件里配置的是 traffic-service-eureka,
这样 service 模块就可以通过 server 或者 traffic-service-eureka 来访问 server 了

另外,配置的 hostname,可以进入 容器中查看 /etc/hosts 该配置会在 文件中生成一个容器的 ip 和 hostname 的记录

多个服务加载顺序问题

详见 : http://blog.joylau.cn/2018/12/19/Docker-Compose-StartOrder/

  1. 查看路由表: netstat -nr

  2. 添加路由: sudo route add 34.0.7.0 34.0.7.1

  3. 删除路由: sudo route delete 0.0.0.0

  4. 清空路由表: networksetup -setadditionalroutes "Ethernet", “Ethernet” 指定路由走哪个设备(查看当前的设备可以使用这个命令 networksetup -listallnetworkservices

  5. 清空路由表: sudo route flush , 是否有效没测试过,通过 man route 看到的,等哪天试过了,再来更新这个内容是否有效

无线网卡和 USB 有线网卡同时使用

我这里的使用场景是无线接外网, USB 网卡接内网,无线路由的网关是 192.168.0.1, USB 网卡的网关是 34.0.7.1

  1. 删除默认路由: sudo route delete 0.0.0.0

  2. 添加默认路由走无线网卡: sudo route add 0.0.0.0 192.168.0.1

  3. 内网走 USB 网卡: sudo route add 34.0.7.0 34.0.7.1

  4. 调整网络顺序,网络属性里面的多个网卡的优先级顺序问题。基本原则是哪个网卡访问互联网,他的优先级就在上面就可以了

有个问题没搞明白, 按逻辑说这样添加的静态路由是临时的,在重启后会消失失效,可实际上我重启了之后并没有失效

配置永久静态路由

  1. networksetup mac 自带的工具,升级到最新的Sierra后拥有,是个“系统偏好设置”中网络设置工具的终端版

  2. networksetup –help 可以查看具体的帮助

  3. 添加静态永久路由: networksetup -setadditionalroutes "USB 10/100/1000 LAN" 10.188.12.0 255.255.255.0 192.168.8.254
    “USB 10/100/1000 LAN” 指定路由走哪个设备(查看当前的设备可以使用这个命令 networksetup -listallnetworkservices

  4. netstat -nr 查看路由表

背景

我们的程序在 Linux 上运行会产生大量日志文件,这些日志文件如果不定时清理的话会很快将磁盘占满

说明

1
2
3
4
5
6
7
8
9
10
# For details see man 4 crontabs

# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed

配置

配置一个定时清理的任务

  1. crontab -e , 添加一个定时任务, 或者 vim /etc/crontab 添加一条记录
1
10 0 * * * /home/liufa/app/cron/del_log.sh > /dev/null 2>&1 &
1
10 0 * * * root sh /home/liufa/app/cron/del_log.sh > /dev/null 2>&1 &

每天 0 点 10 分运行上述命令文件

  1. 创建文件: del_log.sh

  2. 授权 chmod +x ./del_log.sh

  3. 删除 10 天的日志文件

1
2
#!/usr/bin/env bash
find /home/liufa/app/node/logs -mtime +10 -name "*.log" -exec rm -rf {} \;
  1. 重启定时任务, systemctl restart crond , 在 Ubuntu 上叫 cron systemctl restart cron

关于定时任务的配置目录

  1. /etc/crontab 文件, 系统级别的定时任务,需要加入用户名
  2. /var/spool/cron 目录, 以用户作为区分,一般会有一个和用户名相同的文件,里面记录了定时任务, 一般使用 crontab -e 创建, 语法中不需要指定用户名
  3. /etc/cron.d/ 和 crontab 文件类似,需要指定用户名

cron执行时,也就是要读取三个地方的配置文件

注意

  1. 执行脚本使用/bin/sh(防止脚本无执行权限),要执行的文件路径是从根开始的绝对路径(防止找不到文件)
  2. 尽量把要执行的命令放在脚本里,然后把脚本放在定时任务里。对于调用脚本的定时任务,可以把标准输出错误输出重定向到空。
  3. 定时任务中带%无法执行,需要加\转义
  4. 如果时上有值,分钟上必须有值
  5. 日和周不要同时使用,会冲突
  6. >>>/dev/null 2>&1 不要同时存在

日志位置

日志位置位于 /var/log/cron.log,如果没有看到日志,可能由于没有开启 cron 日志记录,开启方法:

vim /etc/rsyslog.d/50-default.conf

/var/log/cron.log相关行,将前面注释符#去掉

重启 rsyslog

service rsyslog restart

或者查看系统日志, 使用命令:

grep cron /var/log/syslog

能看到和 cron 相关的日志信息

任务脚本中变量不生效

在脚本里除了一些自动设置的全局变量,可能有些变量没有生效, 当手动执行脚本OK,但是crontab死活不执行时,在脚本里使用下面的方式

1)脚本中涉及文件路径时写全局路径;
2)脚本执行要用到java或其他环境变量时,通过source命令引入环境变量

1
2
3
#!/bin/sh
source /etc/profile

  1. */1 * * * * . /etc/profile;/bin/sh /path/run.sh

时区问题

构建镜像

时区的配置在 /etc/localtime

localtime 文件会指向 /usr/share/zoneinfo/Asia/ 目录下的某个文件

我们只需要将其指向 ShangHai 即可

Dockerfile 可以这样配置

1
2
RUN rm -rf /etc/localtime && \
ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

先删除,在创建一个软连接即可

如果是在容器内直接操作的话:

  1. apt-get install tzdata
  2. 然后依次选择 6 , 70 即可
  3. 使用 dpkg-reconfigure tzdata 来重写选择

已构建好的镜像

启动一个容器,加上如下参数,即可使用宿主机时间

1
-v /etc/localtime:/etc/localtime:ro

已经在运行的容器

1
docker cp /etc/localtime [container]:/etc/localtime

在检查下是否修改成功:

1
docker exec [container] date

springboot 应用的时区

如果构建的 springboot 项目的镜像,基于 jib 插件构建的话,并且基础镜像选择的是 openjdk:8
那么这时 jvmFlags 参数加上一个 -Duser.timezone=GMT+08 boot 服务启动时会使用东八区的时间
而且这不会改变容器的时区,如果你进入容器,执行 date 打印的话,还是会发现时间少 8 个小时
但是对于应用来说已经没有问题了

mariadb 容器的时区

默认官方的 mariadb 的镜像时区是 0 时区的,想要改变的话,添加执行参数 --default-time-zone='+8:00'
完整 docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
db:
image: mariadb
container_name: mariadb
restart: always
networks:
- job-net
ports:
- 3306:3306
volumes:
- /home/liufa/joy-job/db-data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=123456
- MYSQL_ROOT_HOST=%
command: --default-time-zone='+8:00'

pm2-web 命令错误问题

通常我们都是将 node_modules 文件夹直接复制到镜像中

有时候会出现问题,就比如 pm2-web ,构建成镜像后,命令无法使用

原因在于开发的机器的操作系统和镜像的操作系统不一致,会导致一些包出问题

解决的方式就是重新 nmp install

Dockerfile 如下:

1
2
3
RUN rm -rf ./node_modules && \
rm -rf ./package-lock.json && \
npm install

更改 docker数据目录

默认安装的 docker 数据目录在 /var/lib/docker
想要更改的话
在 docker 的 service 文件添加参数 --graph=/home/lifa
之后重启 docker 服务

查看 docker 使用的空间

有时我们下载镜像和运行的容器多了,会占用很多磁盘,该怎么查看呢?
du sh *
或者
du -m --max-depth=1

基于已有 docker 镜像制作自己的镜像

编写 Dockerfile 文件

这里就不细说了,了解 Dockerfile 文件里的命令即可

使用 docker commit提交容器

比如说 openjdk:8 ,我想让他支持 node , php , python 等该怎么办?
有个简单方法
先 pull 下来

docker run -it openjdk:8 /bin/bash
这样就进入到该镜像了,我们可以安装自己需要的东西了
我自己遇到了棘手的问题
就是 openjdk:8 是 debian 系统的,使用的是 apt-get 包管理器
要安装其他东西就要先更新源
可以使用 apt update 更新
但是我遇到的问题是更新了也无法安装….
于是我就想着切换到阿里或者科大的源
但是发现这个简单的镜像连 vi 和 gedit 等编辑器都没有,又没法安装他们
无法编辑 /etc/apt/source.list 源文件
后来我是 cp 先备份,在 echo "xxxx" >> /ect/apt/source.list 更新的
之后再 update 就可以安装了
安装好之后退出容器,之后
docker commit containID xxx/xxxx:latest
提交容器的改变,之后就看到一个新的镜像了

这里有个问题,就是我想定义自己的启动脚本,貌似无法做到,后来看了官方文档
docker commit 有个 -c 参数,解释是这样的:

commit Apply Dockerfile instruction to the created image

以为使用 Dockerfile 的语法来创建镜像, 还有 3 个参数

-a, –author string 制定个作者
-m, –message string 本次提交信息
-p, –pause 提交镜像时暂停容器,默认是 true

假如我有个 blog 的容器, 示例:

docker commit -c ‘CMD [“sh”, “/my-blog/bash/init.sh”]’ -c “EXPOSE 80” -c “EXPOSE 8080” -a “JoyLau” -m “JoyLau’s Blog Docker Image” blog nas.joylau.cn:5007/joy/blog.joylau.cn:2.1

docker 安装在内网服务器, 如何 pull 镜像

在命令行使用 export HTTP_PROXY=xxxx:xx , 命令行里绝大部分命令都可以使用此代理联网,但是安装的 docker 不行,无法 pull 下来镜像文件,想要 pull 使用代理的话,需要添加代理的变量
vim /usr/lib/systemd/system/docker.service
添加
Environment=HTTP_PROXY=http://xxxx:xxx
Environment=HTTPS_PROXY=http://xxxx:xxx
保存
systemctl deamon-reload
systemctl restart docker

背景

有时我们的服务器网络并不允许连接互联网,这时候 yum 安装软件就有很多麻烦事情了, 我们也许会通过 yumdownloader 来从可以连接互联网的机器上下载好 rpm 安装包,
然后再拷贝到 服务器上.
命令 : yumdownloader --resolve mariadb-server , 所有依赖下载到当前文件夹下

这样做会存在很多问题:

  1. 虽然上述命令已经加上了 --resolve 来解决依赖,但是一些基础的依赖包仍然没有下载到,这时安装就有问题了
  2. 下载的很多依赖包都有安装的先后顺序,包太多的话,根本无法搞清楚顺序

还可以使用 yum install --downloadonly --downloaddir=/tmp/vsftps/ vsftpd 来下载依赖和指定下载的位置
但是如果有一些基础依赖包已经安装过了,则不会下载, 这时可以使用 reinstall 来重新下载

yum reinstall --downloadonly --downloaddir=/tmp/vsftps/ vsftpd

rsync 同步科大的源

  1. yum install rsync
  2. df -h 查看磁盘上目录的存储的空间情况
  3. 找到最大的磁盘的空间目录,最好准备好 50 GB 以上的空间
  4. 新建目录如下:
1
2
3
4
mkdir -p ./yum_data/centos/7/os/x86_64
mkdir -p ./yum_data/centos/7/extras/x86_64
mkdir -p ./yum_data/centos/7/updates/x86_64
mkdir -p ./yum_data/centos/7/epel/x86_64
  1. 开始同步 base extras updates epel 源
1
2
3
4
5
cd yum_data
rsync -avh --progress rsync://rsync.mirrors.ustc.edu.cn/repo/centos/7/os/x86_64/ ./centos/7/os/x86_64/
rsync -avh --progress rsync://rsync.mirrors.ustc.edu.cn/repo/centos/7/extras/x86_64/ ./centos/7/extras/x86_64/
rsync -avh --progress rsync://rsync.mirrors.ustc.edu.cn/repo/centos/7/updates/x86_64/ ./centos/7/updates/x86_64/
rsync -avh --progress rsync://rsync.mirrors.ustc.edu.cn/repo/epel/7/x86_64/ ./epel/7/x86_64/
  1. 开始漫长的等待……
  2. 等待全部同步完毕, tar -czf yum_data.tar.gz ./yum_data ,压缩目录
  3. 压缩包拷贝到服务器上

rsync 增量同步

使用参数 -u, 即

1
rsync -avuh --progress rsync://rsync.mirrors.ustc.edu.cn/repo/centos/7/extras/x86_64/ ./centos/7/extras/x86_64/

rsync 使用及配置解释

6 种用法

  • rsync [OPTION]… SRC DEST
  • rsync[OPTION]… SRC [USER@]HOST:DEST
  • rsync [OPTION]… [USER@]HOST:SRC DEST
  • rsync [OPTION]… [USER@]HOST::SRC DEST
  • rsync [OPTION]… SRC [USER@]HOST::DEST
  • rsync [OPTION]… rsync://[USER@]HOST[:PORT]/SRC [DEST]

1)拷贝本地文件。当SRC和DES路径信息都不包含有单个冒号”:”分隔符时就启动这种工作模式。如:rsync -a /data /backup
2)使用一个远程shell程序(如rsh、ssh)来实现将本地机器的内容拷贝到远程机器。当DST路径地址包含单个冒号”:”分隔符时启动该模式。如:rsync -avz *.c foo:src
3)使用一个远程shell程序(如rsh、ssh)来实现将远程机器的内容拷贝到本地机器。当SRC地址路径包含单个冒号”:”分隔符时启动该模式。如:rsync -avz foo:src/bar /data
4)从远程rsync服务器中拷贝文件到本地机。当SRC路径信息包含”::”分隔符时启动该模式。如:rsync -av root@172.16.78.192::www /databack
5)从本地机器拷贝文件到远程rsync服务器中。当DST路径信息包含”::”分隔符时启动该模式。如:rsync -av /databack root@172.16.78.192::www
6)列远程机的文件列表。这类似于rsync传输,不过只要在命令中省略掉本地机信息即可。如:rsync -v rsync://172.16.78.192/www

参数解释

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
-v, --verbose 详细模式输出
-q, --quiet 精简输出模式
-c, --checksum 打开校验开关,强制对文件传输进行校验
-a, --archive 归档模式,表示以递归方式传输文件,并保持所有文件属性,等于-rlptgoD
-r, --recursive 对子目录以递归模式处理
-R, --relative 使用相对路径信息
-b, --backup 创建备份,也就是对于目的已经存在有同样的文件名时,将老的文件重新命名为~filename。可以使用--suffix选项来指定不同的备份文件前缀。
--backup-dir 将备份文件(如~filename)存放在在目录下。
-suffix=SUFFIX 定义备份文件前缀
-u, --update 仅仅进行更新,也就是跳过所有已经存在于DST,并且文件时间晚于要备份的文件。(不覆盖更新的文件)
-l, --links 保留软链结
-L, --copy-links 想对待常规文件一样处理软链结
--copy-unsafe-links 仅仅拷贝指向SRC路径目录树以外的链结
--safe-links 忽略指向SRC路径目录树以外的链结
-H, --hard-links 保留硬链结
-p, --perms 保持文件权限
-o, --owner 保持文件属主信息
-g, --group 保持文件属组信息
-D, --devices 保持设备文件信息
-t, --times 保持文件时间信息
-S, --sparse 对稀疏文件进行特殊处理以节省DST的空间
-n, --dry-run现实哪些文件将被传输
-W, --whole-file 拷贝文件,不进行增量检测
-x, --one-file-system 不要跨越文件系统边界
-B, --block-size=SIZE 检验算法使用的块尺寸,默认是700字节
-e, --rsh=COMMAND 指定使用rsh、ssh方式进行数据同步
--rsync-path=PATH 指定远程服务器上的rsync命令所在路径信息
-C, --cvs-exclude 使用和CVS一样的方法自动忽略文件,用来排除那些不希望传输的文件
--existing 仅仅更新那些已经存在于DST的文件,而不备份那些新创建的文件
--delete 删除那些DST中SRC没有的文件
--delete-excluded 同样删除接收端那些被该选项指定排除的文件
--delete-after 传输结束以后再删除
--ignore-errors 及时出现IO错误也进行删除
--max-delete=NUM 最多删除NUM个文件
--partial 保留那些因故没有完全传输的文件,以是加快随后的再次传输
--force 强制删除目录,即使不为空
--numeric-ids 不将数字的用户和组ID匹配为用户名和组名
--timeout=TIME IP超时时间,单位为秒
-I, --ignore-times 不跳过那些有同样的时间和长度的文件
--size-only 当决定是否要备份文件时,仅仅察看文件大小而不考虑文件时间
--modify-window=NUM 决定文件是否时间相同时使用的时间戳窗口,默认为0
-T --temp-dir=DIR 在DIR中创建临时文件
--compare-dest=DIR 同样比较DIR中的文件来决定是否需要备份
-P 等同于 --partial
--progress 显示备份过程
-z, --compress 对备份的文件在传输时进行压缩处理
--exclude=PATTERN 指定排除不需要传输的文件模式
--include=PATTERN 指定不排除而需要传输的文件模式
--exclude-from=FILE 排除FILE中指定模式的文件
--include-from=FILE 不排除FILE指定模式匹配的文件
--version 打印版本信息
--address 绑定到特定的地址
--config=FILE 指定其他的配置文件,不使用默认的rsyncd.conf文件
--port=PORT 指定其他的rsync服务端口
--blocking-io 对远程shell使用阻塞IO
-stats 给出某些文件的传输状态
--progress 在传输时现实传输过程
--log-format=formAT 指定日志文件格式
--password-file=FILE 从FILE中得到密码
--bwlimit=KBPS 限制I/O带宽,KBytes per second
-h, --help 显示帮助信息

配置本地 yum 源

  1. 找到一个空间大的目录下,解压包: tar -xvf yum_data.tar.gz
  2. 创建一个新的源配置: touch /etc/yum.repos.d/private.repo
  3. 插入一下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[local-base]
name=Base Server Repository
baseurl=file:///home/liufa/yum_data/centos/7/os/x86_64
enabled=1
gpgcheck=0
priority=1
[local-extras]
name=Extras Repository
baseurl=file:///home/liufa/yum_data/centos/7/extras/x86_64
enabled=1
gpgcheck=0
priority=2
[local-updates]
name=Updates Server Repository
baseurl=file:///home/liufa/yum_data/centos/7/updates/x86_64
enabled=1
gpgcheck=0
priority=3
[local-epel]
name=Epel Server Repository
baseurl=file:///home/liufa/yum_data/centos/7/epel/x86_64
enabled=1
gpgcheck=0
priority=4
  1. 禁用原来的 Base Extras Updates 源: yum-config-manager --disable Base,Extras,Updates
  2. yum clean all
  3. yum makecache
  4. yum repolist 查看源信息

配置网络 yum 源

有时候我们搭建的私有 yum 还需要提供给其他的机器使用,这时候再做一个网络的 yum 即可,用 Apache 或者 Nginx 搭建个服务即可

  1. yum install nginx
  2. vim /etc/nginx/nginx.conf 修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /home/liufa/yum_data;

# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;

location / {
}

error_page 404 /404.html;
location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
  1. 这时 private.repo 里的 baseurl 全改为网络地址即可

403 权限问题

修改 nginx.conf 配置文件的 user 为 root

0%