要搭建一个基于 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