搭建gpsd时间服务器

要搭建一个基于 gpsd 的时间服务器,你可以按照以下步骤进行:

硬件准备:

GPS 接收器:选择一个兼容 gpsd 的 GPS 接收器,比如 u-blox、Garmin 或者 SiRFstar 等品牌的设备。带串口的工控机一台,装好linux系统,比如debian11

Rs232输出口的gps模块一个,ttl的可以让卖家改下,工本费另外加3-5元。【GPS 北斗 GLONASS三模模块 无人机测亩仪飞控车载导航带FLASH】复制¥QYT0yvh00H8ae¥,打开【手机阿里】或【支付宝-点首页进行搜索】查看:https://qr.1688.com/s/sQcXzcD1 CZ7583【RS232电平输出GMOUISE·G7020芯片GPS天线GPS模块GPS模组BG-728R】复制¥7dJa1FA004nag¥,打开【手机阿里】或【支付宝-点首页进行搜索】查看:https://qr.1688.com/s/kSXprWkI CZ1267 记得要发货前焊接上flash模块(记忆用)

DB9免焊接连接器9针串口【RS232电平输出GMOUISE·G7020芯片GPS天线GPS模块GPS模组BG-728R】复制¥7dJa1FA004nag¥,打开【手机阿里】或【支付宝-点首页进行搜索】查看:https://qr.1688.com/s/kSXprWkI CZ1267

Db9的gnd(地线)与模块上的Gnd(地线)并线连接usb线的黑线模块的vdd与usb线的红线5v+连接模块的pps连接db9的DCD(关键,秒脉冲信号),Txd连接db9的Rxd,Rxd连接db9的Txd

橙色:dcd 模块的pps

绿色:rcd 模块的txd

蓝色:txd 模块的rxd

棕色:gnd 模块的gnd并线usb的黑线

软件准备:

操作系统:你可以选择一个兼容 gpsd 的操作系统,比如 Linux 发行版,如Ubuntu、Debian、Raspbian 等。

检查系统内核配置:

grep '^CONFIG_PPS' /boot/config-$(uname -r)

输出包含以下内容:

CONFIG_PPS=y
CONFIG_PPS_CLIENT_LDISC=m
CONFIG_PPS_CLIENT_PARPORT=m

GPSD 软件包:安装 GPSD 软件包,它是一个用于与 GPS 接收器通信的守护进程。你可以通过系统的包管理器来安装它,比如在 Ubuntu 上可以使用以下命令:

apt-get install gpsd chrony setserial gpsd-clients pps-tools

也可以下载源代码包:http://download-mirror.savannah.gnu.org/releases/gpsd/gpsd-3.25.tar.gz

交叉编译:scons && scons check

如果openeuler找不到scons命令,则使用以下命令安装工具包:

yum -y install git gcc make scons python3 python3-devel     libtool ncurses-devel pps-tools-devel systemd-devel     readline-devel bluez-libs-devel dbus-devel

如果“scons”失败,则您的目标系统可能已迁移到 Python 3 并删除了程序“python”。 Python.org 表示,如果您安装了 Python,那么您的路径中应该有一个名为“python”的程序。 PEP 394 中对此进行了规定。并不总是遵循此规则。您可以通过将 python3 链接到 python 来解决此问题,如下所示:

ln -s /usr/bin/python3 /usr/bin/python

‘scons’ 和 ‘gpsd’ 都可以在 Python 2 或 Python 3 上正常工作。如果 python 安装正确,您安装的 python 对用户来说应该是透明的。

有时,由于 scons 数据库损坏,构建可能会以完全奇怪的方式失败。这似乎与 ^Cing 在不合时宜的时刻构建有关。如果您怀疑这一点,请参阅下面的“恢复到干净状态”,然后重试。

恢复到干净的状态
相当于“make clean”的是“scons -c”或“scons --clean”。这会将您的源代码树恢复到干净的状态,几乎就像您刚刚克隆或下载它一样。

如果已通过,并且需要udev支持:

scons udev-install

步骤:

连接 GPS 接收器:将 GPS 接收器连接到服务器的 USB 或串口端口。一旦连接好,可以使用以下命令来检查设备是否被正确识别:

ls /dev/ttyS*

启动 GPSD 守护进程:使用以下脚本(脚本放在/data/shell/gpsd_daemon.sh)启动 gpsd 守护进程,并指定你的 GPS 设备的路径(假设设备路径为 /dev/ttyS0):

#!/bin/bash
LANG=C
export LANG
while :; do
    #如果没有模块pps_ldisc,PPS 功能将无法使用
    if [[ "$(lsmod | grep -c ^pps_ldisc)" -eq "0" ]]; then
        /usr/sbin/modprobe pps_ldisc
    fi
    if [[ -n "$(pgrep chronyd)" ]] && [[ "$(ps -ef | egrep -c "[g]psd -n /dev/ttyS0 -F /var/run/gpsd.ttyS0.sock -S 2947")" -eq "0" ]]; then
        gpsd -n /dev/ttyS0 -F /var/run/gpsd.ttyS0.sock -S 2947
    fi
    #接了两个模块写下面的,一个模块不写下面的
    if [[ -n "$(pgrep chronyd)" ]] && [[ "$(ps -ef | egrep -c "[g]psd -n /dev/ttyS1 -F /var/run/gpsd.ttyS1.sock -S 2948")" -eq "0" ]]; then
        gpsd -n /dev/ttyS1 -F /var/run/gpsd.ttyS1.sock -S 2948
    fi
    sleep 5
done

supervisor的conf.d里写一个gpsd_daemon.conf:

[program:gpsd_daemon]
command=/bin/bash /data/shell/gpsd_daemon.sh

启动gpsd守护脚本

supervisorctl reload

测试 GPSD:使用 gpsmon 命令可以检查 gpsd 是否能够成功与 GPS 接收器通信。在终端中输入以下命令:

gpsmon
#看第二个模块的
gpsmon 127.0.0.1:2948

如果一切正常,你应该能够在屏幕上看到 GPS 接收器发送的信息:

配置 NTP 服务:

要将服务器转变为一个时间服务器,安装和配置chrony/etc/chrony.conf的内容:

#请注意,gpsd 需要在 chronyd 之后启动才能连接到套接字。
#/@RUNDIR@/chrony.XXX.sock chronyd 提供的可选 Unix 域套接字,配置了 SOCK 参考时钟,其中 XXX 是串行设备的名称(例如 ttyS0)。启动 gpsd 时,它将尝试 连接到此套接字。如果已连接,它将向 chronyd 发送 PPS 计时信息 以同步系统时钟。
refclock SOCK /var/run/chrony.ttyS0.sock refid GPS1 poll 2 filter 4
refclock SOCK /var/run/chrony.ttyS1.sock refid GPS2 poll 2 filter 4
server clock.fmt.he.net minpoll 4 maxpoll 7 iburst
server clock.sjc.he.net minpoll 4 maxpoll 7 iburst
server time.cloudflare.com minpoll 4 maxpoll 7 iburst
server time.google.com minpoll 4 maxpoll 7 iburst
server time.nist.gov minpoll 4 maxpoll 7 iburst
server gpstime.la-archdiocese.net minpoll 4 maxpoll 7 iburst
pool ntp.aliyun.com minpoll 4 maxpoll 7 iburst maxsources 4
pool cn.pool.ntp.org minpoll 4 maxpoll 7 iburst maxsources 4
authselectmode ignore
local stratum 1
lock_all
keyfile /etc/chrony/chrony.keys
minsources 1
driftfile /var/lib/chrony/chrony.drift
ntsdumpdir /var/lib/chrony
logchange 0.5
hwclockfile /etc/adjtime
log tracking measurements statistics
logdir /var/log/chrony
maxupdateskew 100.0
dumponexit
dumpdir /var/lib/chrony
rtcsync
makestep 0.2 -1
hwtimestamp *
ratelimit interval 3 burst 8 leak 2
bindaddress 0.0.0.0
port 1123
bindcmdaddress /var/run/chrony/chronyd.sock
cmdallow 127.0.0.1
allow 103.114.161.219
allow 127.0.0.1
allow 10.0.0.0/8
allow 172.16.0.0/12
allow 192.168.0.0/16

重新启动chrony:

systemctl restart chrony

编译chrony安装:

下载地址:https://chrony-project.org/releases/chrony-4.5.tar.gz

cd chrony-4.5
./configure --prefix=/opt/app/chrony/ --disable-ipv6 && make && make install

/opt/app/chrony/chrony.conf内容如上。

chronyd守护进程脚本:/opt/app/chrony/sbin/chronyd.sh

#!/bin/bash
while :
do
if [[ "$(pgrep -c "chronyd")" -eq "0" ]];then
        /opt/app/chrony/sbin/chronyd -u root -f /opt/app/chrony/chrony.conf
fi
sleep 3
done

supervisor的conf.d里写一个chronyd.conf:

[program:chronyd]
command=bash /opt/app/chrony/sbin/chronyd.sh
startsecs=10
autorestart=true
startretries=60

启动gpsd守护脚本

supervisorctl reload

为了减轻针对ntp服务的dos/ddos攻击的影响,前置需要使用nginx的udp代理,并限制并发数和访问来源(限制为内网、中国大陆访问)

一、安装docker和docker-compose

1.下载安装包,上传服务器

https://download.docker.com/linux/static/stable/x86_64/docker-24.0.6.tgz

2、安装

cp docker/* /usr/bin

3、注册系统服务

cat >/lib/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target firewalld.service
Wants=network-online.target
[Service]
Type=notify
ExecStart=/usr/bin/dockerd
ExecReload=/bin/kill -s HUP $MAINPID
LimitNOFILE=65535
LimitNPROC=65535
LimitCORE=65535
TimeoutStartSec=0
Delegate=yes
KillMode=process
Restart=on-failure
StartLimitBurst=3
StartLimitInterval=60s
[Install]
WantedBy=multi-user.target

4、设置deamon.json

mkdir /etc/docker
cat >/etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "5"
  },
  "registry-mirrors": [
    "https://eme145et.mirror.aliyuncs.com"
  ],
  "proxies": {
    "https-proxy": "http://relay-acting.example.com:65535",
    "no-proxy": "*.cn,127.0.0.0/8,192.168.0.0/16,172.16.0.0/12,10.0.0.0/8"
  }
}

5、启动和开机自启动

systemctl daemon-reload
systemctl enable --now docker
docker info

6、安装docker-compose

curl -SL https://github.com/docker/compose/releases/download/v2.24.7/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose

二、设计docker-compose.yml编排文件

name: ntp_server
services:
  ntp.nginx:
    container_name: ntp.nginx
    dns:
    - 114.114.114.114
    - 119.29.29.29
    - 223.5.5.5
    - 223.6.6.6
    image: nginx
    networks:
      default: null
    ports:
    - mode: ingress
      target: 123
      published: 123
      protocol: udp
    restart: always
    tty: true
    volumes:
    - type: bind
      source: /etc/localtime
      target: /etc/localtime
      bind:
        create_host_path: true
    - type: bind
      source: ./nginx/conf.d
      target: /etc/nginx/conf.d
      bind:
        create_host_path: true
    - type: bind
      source: ./nginx/nginx.conf
      target: /etc/nginx/nginx.conf
      bind:
        create_host_path: true
networks:
  default:
    name: ntp_server_default

三、设置配置文件

1.nginx.conf:

user  nginx;
worker_processes  auto;
worker_cpu_affinity auto;
error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;
timer_resolution 100ms;




events {
    worker_connections  1024;
}
include /etc/nginx/conf.d/ntp.conf;




http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    tcp_nopush     on;
    tcp_nodelay on;
    keepalive_timeout  65;
}

2、ntp.conf:

stream {
    resolver 114.114.114.114 valid=30s;
    resolver_timeout 30s;
    tcp_nodelay on;
    preread_buffer_size 16k;
    preread_timeout 30s;
    proxy_protocol_timeout 30s;
    proxy_connect_timeout 60s;
    proxy_buffer_size 16k;
    proxy_half_close on;
    proxy_protocol off;
    variables_hash_bucket_size 64;
    variables_hash_max_size 1024;
    log_format basic '$remote_addr [$time_local] '
    '$protocol $status $bytes_sent $bytes_received '
    '$session_time';
    log_format proxy '$remote_addr [$time_local] '
    '$protocol $status $bytes_sent $bytes_received '
    '$session_time "$upstream_addr" '
    '"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"';
    access_log /var/log/nginx/ntp.log proxy;
    map_hash_bucket_size 128;




    upstream abfaen {
        hash $remote_addr consistent;
        server 192.168.23.4:1123;
    }
    limit_conn_zone $binary_remote_addr zone=meompimg:10m;




    geo $ebssmnteenmahl {
        default 0;
        172.16.0.0/12 estogm;
        192.168.0.0/16 estogm;
        10.0.0.0/8 estogm;
        103.114.161.219/32 estogm;
        include /etc/nginx/conf.d/cn.conf;
    }




    map $ebssmnteenmahl $afliahgsno {
        default 127.0.0.36:31680;
        estogm abfaen;
        CN abfaen;
    }




    server {
        listen 123 udp reuseport;
        set_real_ip_from 172.16.0.0/12;
        set_real_ip_from 10.0.0.0/8;
        set_real_ip_from 192.168.0.0/16;
        limit_conn meompimg 6;
        limit_conn_log_level error;
        proxy_pass $afliahgsno;
        proxy_timeout 20s;
    }
}

3、cn.conf:

1.0.1.0/24 CN;
1.0.2.0/23 CN;
1.0.8.0/21 CN;
1.0.32.0/19 CN;
1.1.0.0/24 CN;
1.1.2.0/23 CN;
1.1.4.0/22 CN;
1.1.9.0/24 CN;
1.1.10.0/23 CN;
1.1.12.0/22 CN;
......
......

Tips:可以用以下shell脚本生成cn.conf的内容:

wget -q -O - "http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest" |awk -F'[|]' '{if($2=="CN" && $3=="ipv4"){print $4,$5}}' |while read ip cnt;do echo $cnt |awk 'BEGIN{j=0}{for(i=$1;i>1;i/=2){j++}}END{printf"%s/%s %s;\n",ip,32-j,"CN"}' ip=$ip;done >cn.conf

项目文件树结构:

.
├── docker-compose.yml
└── nginx
    ├── conf.d
    │   ├── cn.conf
    │   └── ntp.conf
    └── nginx.conf




3 directories, 4 files

启动容器:

docker-compose up -d

全容器化方案:

目录树结构

.
├── chrony.conf
├── docker-compose.yml
├── Dockerfile
├── gpsd-3.27.tar.gz
├── images
│   ├── alpine_3.22.0.img.gz
│   ├── alpine-gpsd_3.27.img.gz
│   ├── load.sh
│   └── nginx_1.27.5.img.gz
├── nginx
│   ├── conf.d
│   │   ├── cn.conf
│   │   ├── deny.conf
│   │   └── ntp.conf
│   └── nginx.conf
└── ntp-nfw.sh




4 directories, 13 files

Dockerfile的代码:

FROM alpine:latest as builder
RUN sed -i 's/dl-cdn.alpinelinux.org/mirror.nju.edu.cn/g' /etc/apk/repositories && \
    apk add --no-cache build-base scons python3 python3-dev linux-headers ncurses-dev pps-tools-dev dbus-dev
WORKDIR /app
ADD ./gpsd-3.27.tar.gz .
RUN cd gpsd-3.27 && \
    scons && \
    scons install --install-sandbox=/tmp/gpsd-install
FROM alpine:latest
RUN sed -i 's/dl-cdn.alpinelinux.org/mirror.nju.edu.cn/g' /etc/apk/repositories && \
    apk add --no-cache pps-tools ncurses dbus python3 chrony
COPY --from=builder /tmp/gpsd-install/ /
WORKDIR /etc/chrony
ADD ./chrony.conf .
EXPOSE 2947/tcp 2948/tcp 1123/udp
ENV PATH="/usr/local/bin:/usr/local/sbin:$PATH"
ENTRYPOINT ["sh", "-c"]
CMD ["gpsd -G -n /dev/ttyS0 -F /var/run/gpsd.ttyS0.sock -S 2947 && gpsd -G -n /dev/ttyS1 -F /var/run/gpsd.ttyS1.sock -S 2948 && chronyd -d -f /etc/chrony/chrony.conf"]

docker build –no-cache -t alpine-gpsd:3.27 .

docker-compose.yml的代码:

name: ntp_server
services:
  ntp.gpsd:
    cap_add:
      - SYS_RAWIO
    container_name: ntp.gpsd
    devices:
      - source: /dev/ttyS0
        target: /dev/ttyS0
        permissions: rwm
      - source: /dev/ttyS1
        target: /dev/ttyS1
        permissions: rwm
    image: alpine-gpsd:3.27
    networks:
      default: null
    privileged: true
    restart: always
    tty: true
  ntp.nginx:
    container_name: ntp.nginx
    dns:
      - 114.114.114.114
      - 119.29.29.29
      - 223.5.5.5
      - 223.6.6.6
    image: nginx
    networks:
      default: null
    ports:
      - mode: ingress
        target: 123
        published: "123"
        protocol: udp
    restart: always
    tty: true
    volumes:
      - type: bind
        source: /etc/localtime
        target: /etc/localtime
        bind:
          create_host_path: true
      - type: bind
        source: /data/gpsd/nginx/conf.d
        target: /etc/nginx/conf.d
        bind:
          create_host_path: true
      - type: bind
        source: /data/gpsd/nginx/nginx.conf
        target: /etc/nginx/nginx.conf
        bind:
          create_host_path: true
networks:
  default:
    name: ntp_server_default

nginx/conf.d/ntp.conf的代码里的upstream部分代码修改为以下:

upstream abfaen {
        hash $remote_addr consistent;
        server ntp.gpsd:1123;
    }

docker-compose up -d

ntp自动防火墙脚本的代码:

#!/bin/bash
if [[ "$(docker inspect -f {{.State.Status}} ntp.nginx 2>/dev/null)" == "running" ]]; then
    docker exec ntp.nginx grep "127.0.0.36" /var/log/nginx/ntp.log | awk '{a[$1]++}END{for(A in a){print a[A],A}}' | sort -t1 -rn | head -n 10 | awk '{printf"%s %s/32;\n","deny",$2}' >/tmp/__nginx_deny__.conf
    m1=$(md5sum /tmp/__nginx_deny__.conf 2>/dev/null | awk '{print $1}')
    m2=$(md5sum /tmp/__nginx_deny__.hist 2>/dev/null | awk '{print $1}')
    if [[ -s "/tmp/__nginx_deny__.conf" ]] && [[ $m1 != $m2 ]]; then
        /bin/cp /tmp/__nginx_deny__.conf /data/ntp/nginx/conf.d/deny.conf
        /bin/mv /tmp/__nginx_deny__.conf /tmp/__nginx_deny__.hist
        if docker exec ntp.nginx nginx -t >/dev/null 2>&1; then
            docker exec ntp.nginx nginx -s reload
        fi
    else
        echo "$(date +"%Y/%m/%d %H:%M:%S") 配置未更新!"
    fi
fi
Categories: 系统运维