menu Chancel's blog
rss_feed
Chancel's blog
有善始者实繁,能克终者盖寡。

使用Docker容器取代虚拟机作为远程开发的容器

作者:Chancel Yang, 创建:2023-10-19, 字数:3658, 已阅:250, 最后更新:2024-03-18

一直以来,我的开发环境都是使用Visual Studio Code(以下简称VSC)远程SSH进行开发

远程开发有以下几个好处:

  1. 环境统一:无论是在公司或是在外都是保持同一份代码以及同一个系统开发环境,不用记住多个开发环境的细节
  2. 强隔离:虚拟机只有开发环境,不需混装office、通讯软件和VPN等复杂软件
  3. 低要求:对客户端要求低,只要安装VSC即可

而远程服务器的系统我通常选择是虚拟机+Debian12,再通过Gost组网就可以实现在任何网络下的随时访问

但是这套方案的不足之处:

  1. 文件与网络均被隔离:由于虚拟系统与宿主系统是完全隔离的,这就导致在部分开发场景需要反复下载上传文件(如配置文件),涉及调试web时也要更改0.0.0.0才能访问
  2. 硬件资源切割:虚拟机需要规划好性能,提前划出硬盘、CPU、网络等资源作为独立的虚拟资源,尤其涉及到显卡训练等场景非常不便捷

解决以上不足之处的方案就是采用Docker来替换虚拟化,Docker的轻量级虚拟可以很好的弥补这些缺点,与宿主机实现统一网络环境与文件系统

下面是实践一个Debian12容器作为开发容器的实践

1. Debian12容器

debian12容器实现的目标如下

  1. Host网络:采用host网络,无需映射端口方便web应用调试
  2. 持久化存储:对代码目录做持久化存储,和宿主机双向绑定,方便更改文件

为了方便迁移容器,此次采用docker-compose来构建

1.1. 镜像构建

Docker设计理念与虚拟机不同,一个容器是生命进程等于一个应用的生命进程,而不是虚拟机这种完全独立的操作系统

所以要给容器一个永久运行的应用,ssh服务就合适

构建一个Dockerfile文件如下

Docker
FROM debian:12.1

ENV TZ=Asia/Shanghai

# Software install
RUN apt-get update && \
    apt-get install -y openssh-server vim wget git zsh curl unzip gcc make openssl


# Create new user 'chancel'
RUN useradd -m chancel && \
    echo 'chancel:chancel' | chpasswd

# SSH configuration
RUN mkdir -p /run/sshd && \
    ssh-keygen -A && \
    echo 'root:root' | chpasswd && \
    sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin no/' /etc/ssh/sshd_config && \
    sed -i 's/#Port 22/Port 30022/' /etc/ssh/sshd_config

CMD ["/usr/sbin/sshd", "-D"]

容器说明:

  1. 安装的基础服务如ssh、git等
  2. 创建了一个用户chancelroot
  3. 更改ssh服务的端口为30022和不允许root登录

构建镜像

Bash
sudo docker build -t debian12:latest . --no-cache

1.2. 代码目录

为了方便宿主机器与容器共享代码目录,创建一个目录debian12用于存放容器的代码文件

将上面的Dockerfile放入debian12/debian12中,代码文件夹codes也放入debian12文件夹中,再创建一个docker-compose.yaml文件

最终目录结构如下

Bash
chancel@R7-4750G ~/Docker/debian12$ tree -L 2                                                                               
.
├── codes
│   ├── golang
│   ├── nodejs
│   ├── python
│   └── web
├── debian12
│   └── Dockerfile
└── docker-compose.yaml

6 directories, 2 files

到此,初始化工作基本完成,我们创建一个docker-compose.yaml文件,内容如下

YAML
version: '3'
services:
  debian12:
    build: ./debian12
    image: debian12:latest
    container_name: debian1
    restart: always
    network_mode: host
    hostname: debian12
    volumes:
      - "./codes:/codes"
      - "/etc/localtime:/etc/localtime:ro"

启动容器

Bash
sudo docker-compose up

1.3. 连接

运行成功后,在宿主机使用ssh容器连接

Bash
ssh root@127.0.0.1 -p 30022

连接后可以根据需要自行进行系统设置了

1.4. 备份

容器比起虚拟机而言,更容易被删除,所以定时做冷备也是保持开发环境稳定重要的一环

以上面的容器debian12为例,这里在做完初步的系统设置后,创建一个新的备份镜像

Bash
sudo docker commit debian12 debian12_backup_image

如果需要迁移,或者考虑冷备的话还可以将这个新的备份镜像保存为tar文件

Bash
sudo docker save -o debian12_backup_image.tar debian12_backup_image

迁移到其他具备docker环境的服务器上,使用load就可以将这个镜像重新导入

Bash
docker load -i debian12_backup_image.tar

2. 问题

Docker的镜像debian:12.1并非完整操作系统,在日常使用会出现不少小问题,以下是一些记录与修复

2.1. 光标输入错位

这是缺失中文编码的问题,输入locale可以检查

Bash
chancel@6a57fa21dec6 locale
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC=zh_CN.UTF-8
LC_TIME=zh_CN.UTF-8
LC_COLLATE="en_US.UTF-8"
LC_MONETARY=zh_CN.UTF-8
LC_MESSAGES="en_US.UTF-8"
LC_PAPER=zh_CN.UTF-8
LC_NAME=zh_CN.UTF-8
LC_ADDRESS=zh_CN.UTF-8
LC_TELEPHONE=zh_CN.UTF-8
LC_MEASUREMENT=zh_CN.UTF-8
LC_IDENTIFICATION=zh_CN.UTF-8
LC_ALL=

修复需要安装locales,如下

Bash
sudo apt install -y locales

运行选择默认的语言编码

Bash
sudo dpkg-reconfigure locales

在弹出的所有语言编码中选择你需要的默认语言编码,我这里选择的是120. en_US.UTF-8 UTF-8


[[replyMessage== null?"发表评论":"发表评论 @ " + replyMessage.m_author]]

account_circle
email
web_asset
textsms

评论列表([[messageResponse.total]])

还没有可以显示的留言...
[[messageItem.m_author]] [[messageItem.m_author]]
[[messageItem.create_time]]
[[getEnviron(messageItem.m_environ)]]
[[subMessage.m_author]] [[subMessage.m_author]] @ [[subMessage.parent_message.m_author]] [[subMessage.parent_message.m_author]]
[[subMessage.create_time]]
[[getEnviron(messageItem.m_environ)]]