一招解决开发环境问题 —— 远程容器开发指南

前言

使用C++作为主要开发语言的程序猿们应该会认同搭建开发环境是一件烦人的事情。为了编译一个程序不仅需要下载各种依赖包,还可能面临本地系统不兼容、编译器版本不一致、包版本冲突等各种问题。笔者在运营iLogtail开源社区的过程中发现开发和调试环境问题也是成员问的最多的问题之一,那么有没有一种方法可以彻底解决这一问题呢?

有。容器技术使应用在各种环境可以一键部署,一致执行,同样的原理也适用于开发环境部署。利用 VSCode 的 Remote-Development 插件就可以使整个开发环境运行在远程容器中。使用这种方式不但可以直接使用一致的环境开发编译,而且还自然实现了多个开发环境的隔离。下面让就我们由浅到深搭建这样的远端容器开发环境。

原理简介

为什么要远程+容器?远程解决的是开发机资源问题和代码安全问题,本地电脑的CPU和内存比较有限,为了提高编译、测试效率一般都会准备一台专门用于开发测试的机器,而部分公司为了防止代码外泄,只允许内部开发机访问代码库。容器解决的是开发环境一致性问题。两者结合起来便能构建最理想的开发环境。

在使用 Remote-Development 插件时,插件会ssh连接到远程开发机,然后根据配置直接启动或是铸造开发环境镜像后启动开发环境容器。启动时将开发机的Workspace目录作为源挂载到容器中。开发环境容器启动后,插件会自动安装VS Code Server,并安装配置指定的VS Code插件。一旦容器内的VS Code Server启动后,本地的VS Code就会直接与容器内的VS Code Server建立通信。容器内可以访问Workspace所有文件,并且修改不会因容器退出而丢失。容器开发环境可以使用的VS Code插件,在Workspace的devcontainer.json配置中指定,下文会有详细描述。

为了提高启动速度并保留容器内插件的配置,开发容器内的/vscode目录其实挂载了一个docker volume,不会自动随docker退出而回收,因此从第二次连接容器开发环境开始,无需重新安装VS Code Server、插件等,启动速度大大提高。

8f6da8352a104086dca58a705ba4a090.png

环境准备

要使VS Code可以远程连接开发机,最好使用ssh密钥建立本地电脑和开发机的信任关系。要使用容器进行开发,开发机上必须安装Docker。这两步相关教程网上较多,在这里就不再赘述。

需要注意的是,要使VS Code通过ssh连上开发机并通过docker启动开发环境容器,建立信任关系的账户必须具备docker使用权限。如果使用root账户,那么自然具备。如果非root则可以使用任意一种方式使账户获得docker使用权限:

  1. 将用户加入docker组。参考Post-installation steps for Linux(https://docs.docker.com/engine/install/linux-postinstall)。
    sudo usermod -aG docker $USER

  2. 将docker.sock权限修改为777(不太推荐,除非上述方法无法奏效)。
    sudo chmod 777 /var/run/docker.sock

下面假设开发机使用的是Linux系统,并且与本地电脑已经建立好信任关系,而且安装并具备Docker访问权限。

安装插件

在VS Code的Marketplace中搜索“Remote Development”安装插件。

a334e6f2262184061a2a88f8c08a4365.png

安装完成后,会发现多出了3个子插件。

  • Remote - Containers:连接容器开发

  • Remote - SSH:连接ssh远程开发

  • Remote - WSL:连接WSL(Windows Linux子系统)开发

使用镜像开发

使用 Remote Development 插件最直接的方式就是利用现成的编译镜像启动开发容器。这里以使用C++和Go语言编写、依赖环境相对复杂的开源项目iLogtail数据采集器项目为例,说明如何利用 Remote Development 插件进行远程容器开发。

1. 创建Remote Development配置

在iLogtail Workspace的顶层目录创建.devcontainer目录,并在里面创建devcontainer.json文件。

9ac088edf37ac05006d0ba8fb20dd69c.png

配置文件的内容如下:

{"image": "sls-opensource-registry.cn-shanghai.cr.aliyuncs.com/ilogtail-community-edition/ilogtail-build-linux:latest","customizations": {"vscode": {"extensions": ["golang.Go","ms-vscode.cpptools-extension-pack"]}}
}

其中,image字段是Remote Development插件启动开发环境的镜像地址,customizations.vscode.extensions指定了开发环境的插件。部分插件介绍如下,开发者也可以按照自己的习惯进行修改。

插件名

用途

golang.Go

Go开发必备插件

ms-vscode.cpptools-extension-pack

C++开发必备插件

2. 在容器中打开代码库

使用Shift + Command + P(Mac)或Ctrl + Shift + P(Win)打开命令面板,输入reopen,选择Remote-Containers: Reopen in Container

7053fd2387c7199cf720197406d6e0d8.png

或者若出现如下图提示,则可以直接点击在容器中重新打开。

0a79ad5a8345cb88a5b92bcc718d518f.png

首次打开时会比较慢,因为要下载镜像并安装插件,后面再次打开时速度会很快。按照提示进行镜像Build。

完成上述步骤后,我们已经可以使用VS Code进行代码编辑,并在其中进行代码编译。

注:如果以前拉取过编译镜像,可能需要触发Remote-Containers: Rebuild Container重新构建。

3. 在容器中进行开发

开发容器启动后,我们已经可以在VS Code中浏览Workspace代码了。但是随便打开一个文件,满眼都是错误提示,代码的跳转功能也不能正常工作。这是因为C++开发环境的includePath没有被正确配置。

37c2d0a2cfa2b8c6945c4d8745d91735.png

打开命令面板,输入C++ config,选择C/C++: Edit Configurations(UI)

576d677ba4544769b669347a2d54d482.png

找到Include path,输入镜像内依赖库的路径。

d2f70fd5b07917f0f3f6091c02e68e5f.png

再回来看代码时,错误提示都消失了,并且函数定义跳转正常。

4. 在容器中进行编译

打开新Terminal(找不到的可以在命令面板中输入Terminal,选择新开一个)

bcf39605773b7119ec59193530aaf926.png

  • 编译iLogtail Go插件

make vendor       # 若需要更新插件依赖库
make plugin_local # 每次更新插件代码后从这里开始

8bb6ae6b3be5a67328acd67913979181.png

  • 编译iLogtail C++代码

mkdir -p core/build # 若之前没有建过
cd core/build
cmake .. # 若增删文件,修改CMakeLists.txt后需要重新执行
make -sj$(nproc)    # 每次更新core代码后从这里开始

0d6667bb9c617c7c393c820e50b8f29d.png41f136609f8fc47410e871b128e0fb42.png

5. 获取编译产出

由于VS Code是直接将代码库目录挂载到镜像内的,因此主机上可以直接访问镜像内的编译产出。

c2e3fb66d863951d298148486e901943.png

到这里,如果要求不高的话就可以结束了,但细心的读者一定发现了一个问题,容器内生成的文件在主机上都是root权限,必须执行sudo chown -R $USER .进行修复。如果社区的成员开发机没有sudo权限怎么办?作为iLogtail社区核心贡献者,当然不能把这样的坑留给队友了。

使用Dockerfile开发

那有没有办法做到容器内权限自动适配主机呢?这样的问题当然不会难倒VS Code了。Remote Development 插件支持使用 Dockerfile 在容器中进行开发,即在启动开发容器前先使用docker build一个开发镜像,这给了修正容器内账户权限的机会。

修正的原理如下:

  1. 在Remote Development 插件 docker build 前将开发机的账户名、账户ID、组名和组ID暴露给 docker。

  2. docker build时利用这些账户信息修正容器执行账户和容器内文件权限。

接下来我们进行实际操作。

1. 修改.devcontainer.json配置文件

在配置文件中,将image部分修改为build部分,使用Dockerfile启动开发容器。同时,加入initializeCommand,在build镜像前,将账户信息暴露给docker。

{"build": {"dockerfile": "Dockerfile","args": {"USERNAME": "${localEnv:USER}"}},"initializeCommand": ".devcontainer/gen_env.sh","customizations": {"vscode": {"extensions": ["golang.Go","ms-vscode.cpptools-extension-pack"]}}
}

2. 创建Dockerfile

以编译镜像作为基础镜像,编写Dockerfile对镜像内账户和文件权限进行修正。

FROM sls-opensource-registry.cn-shanghai.cr.aliyuncs.com/ilogtail-community-edition/ilogtail-build-linux:latestARG USERNAME=admin
USER root# Create the user
COPY .env /tmp/.env
RUN source /tmp/.env && rm /tmp/.env; \if getent passwd $USERNAME; then userdel -f $USERNAME; fi; \if [ $HOST_OS = "Linux" ]; then \if getent group $GROUPNAME; then groupdel $GROUPNAME; fi; \groupadd --gid $GROUP_GID $GROUPNAME; \fi; \useradd --uid $USER_UID --gid $GROUP_GID -m $USERNAME; \echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME; \chmod 0440 /etc/sudoers.d/$USERNAME; \chown -R $USERNAME:$GROUPNAME /root/go /opt/logtail $(eval echo ~$USERNAME); \chmod 755 $(eval echo ~$USERNAME);USER $USERNAME

COPY .env /tmp/.env将主机的账户信息通过文件形式复制到容器中。

接下来的几行根据这些信息在容器内创建对应的账户。

echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME;授予该用户sudo权限。

chmodchown的几行对文件权限进行修正,使新建的用户有权限读写对应目录。其中对HOME(~$USERNAME)目录的修正必须在这里进行,否则会导致VS Code Sever没有权限安装,导致插件启动失败。

3. 创建脚本暴露主机账户信息

gen_env.sh脚本内容如下。该脚本对开发机为Mac系统也做了兼容。

set -ue
set -o pipefailif uname -s | grep Linux; thenecho -e "HOST_OS=Linux\nUSERNAME=$USER\nUSER_UID=$(id -u $USER)\nGROUPNAME=$(id -gn $USER)\nGROUP_GID=$(id -g $USER)" > .devcontainer/.env;
elseecho "HOST_OS=Darwin\nUSERNAME=$USER\nUSER_UID=$(id -u $USER)\nGROUPNAME=root\nGROUP_GID=0" > .devcontainer/.env;  
fi

前3步完成后,Workspace中的配置目录应该有这样3个文件:

16269f8b317d23ee9f54a283c8a49050.png

4. 运行观察效果

使用Shift + Command + P(Mac)或Ctrl + Shift + P(Win)打开命令面板,输入reopen,选择Remote-Containers: Rebuild Container

442858ddea28e7041eab780653c89d3f.png

在容器内重新执行id命令查看账户信息,可以看到与开发机一致。

cfc4d3cb72c006af2891fb8a23bcf177.png

在容器内重新执行之前的编译命令。然后会到开发机上查看生成的文件权限,可以看到容器内生成的文件,在开发机上都已经变成正确的权限了。

42e36e9de52726e55bd01520de5c1081.png

在容器内调试

除了编译代码,开发环境另一个重要功能是进行本地调试。打开一个iLogtail插件的单元测试文件,设置断电然后点击“debug test”。

176362584ca7f420745b8d84aeb9a974.png

什么?Failed to launch: could not launch process: fork/fork/exec ...: operation not permitted,出错了!

354c4e52a4297d3ce7a2b598c87617e7.png

查阅资料,原来是docker默认的安全策略使用Secure computing mode (seccomp)仅允许白名单系统调用,debug所需的系统调用被拒绝了。我们尝试在配置文件中添加一行"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],禁用该功能。

{"build": {"dockerfile": "Dockerfile","args": {"USERNAME": "${localEnv:USER}"}},"initializeCommand": ".devcontainer/gen_env.sh","runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],"customizations": {"vscode": {"extensions": ["golang.Go","ms-vscode.cpptools-extension-pack","DavidAnson.vscode-markdownlint"]}}
}

Rebuild Container后,再次尝试调试功能。It works!

84f9379bbc12911e7fd0efe298f931ff.png

总结

至此,我们已经可以happy地通过VS Code的Remote Development插件在远程容器内开发了。并且使用的编译镜像和插件配置文件都是可移植,可重复的,CI到代码库后可以供任何开发者使用。文章中提到的代码都可以到iLogtaill的GitHub仓库(https://github.com/alibaba/ilogtail)获取。

Remote Development插件还有很多的功能没有在本篇文章中使用到,感兴趣的读者可以根据文末参考资料进一步研究探索。

参考资料

  1. Developing inside a Container using Visual Studio Code Remote Development:

    https://code.visualstudio.com/docs/remote/containers

  2. Create a development container using Visual Studio Code Remote Development:

    https://code.visualstudio.com/docs/remote/create-dev-container

  3. Running Docker Containers as Current Host User - Juan Treminio - Senior Web Developer Blog:

    https://jtreminio.com/blog/running-docker-containers-as-current-host-user/

  4. Add non-root user to a container:

    https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user

  5. devcontainer.json reference:

    https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user

  6. Seccomp security profiles for Docker:

    https://docs.docker.com/engine/security/seccomp/

  7. alibaba/ilogtail: Fast and Lightweight Observability Data Collector:

    https://github.com/alibaba/ilogtail

推荐阅读:

  • 全宇宙首本 VS Code 中文书,来了!

  • Code Runner for VS Code,下载量突破 4000 万!支持超过50种语言

  • 微软也爱 Python!VS Code Python 全新发布!Jupyter Notebook 原生支持终于来了!

  • 微软也爱 Java!微软在 SpringOne 大会上宣布 Azure Spring Cloud 云服务!

  • 在微软(Microsoft)工作是怎样一番体验?

  • 微软内推,长期有效

  • 代码编辑器横评:为什么 VS Code 能拔得头筹

  • 知否知否,VS Code 不止开源

  • 那些年,我们一起追的 VS Code

玩转VS Code

VS Code · 编程开发 · 业界资讯

beae3e7c7be95a32709556a64744ac3c.jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/282249.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php中常用的加密方式

一、md5 要说php中的最常用的加密方式,md5可以说是当仁不让。 使用起来也很简单便捷。 注:使用方式请看 六、md5加密方式的漏洞及解决方案 二、password_hash 作为php5.5以上版本专门用于加密的方式,自然有其独到之处。 使用方式链接&a…

解决问题的策略-分而治之

一个宏伟的目标看上去遥不可及,这怎么可能做成呢?但是你把这些目标分解成一个一个的小目标,小目标再往下分解,分解到最后,分解成细枝末节时你会发现,这事其实是可以做的。这个做成了再往下走一步&#xff0…

UITabBarController的基本原理及使用(一)

前言 UITabBarController在iOS开发中是一个高频使用的控制器,典型的案例如QQ、微信均使用UITabBarController布局。本文将从一个新建工程,和大家一起了解UITabBarController的基本原理和使用方法。 基本概念 UITabBarController能够方便地管理多个控制器…

word-vba-microsoft(中英文)

中文 https://msdn.microsoft.com/zh-cn/vba/word-vba/articles/view-displaypageboundaries-property-word 英文 https://msdn.microsoft.com/en-us/vba/word-vba/articles/view-displaypageboundaries-property-word转载于:https://www.cnblogs.com/itzxy/p/7625915.html

C# 多线程ThreadPool用法举例

概述ThreadPool是.Net Framework 2.0版本中出现的。自从Task出来以后,ThreadPool已经很少用了,但是一些老的代码或者一些古老的程序猿还是会用到他,所以我们可以不用它,但是还是有必须学习和了解他.ThreadPool用法举例static void…

Mysql实现主从复制(一主双从)

一、环境介绍 LNMP(centos7,mysql5.6) vmware workstation pro配置了3个虚拟机,均安装了LNMP环境: master: 192.168.0.105 slave: 192.168.0.106 、192.168.0.107 二、原理 &a…

Elasticsearch学习笔记-04.3批处理

除了创建、更新和删除个别文档,Elasticsearch还提供了使用_bulk API的上述操作的批量操作方法。这个功能很重要,因为他提供了一种有效的机制来在尽可能少的网络传输过程中执行多次操作。 作为一个快速示例,下面的命令在一次批量操作中索引了两…

接口文档神器Swagger(下篇)

本文来自网易云社区作者:李哲二、Swagger-springmvc原理解析上面介绍了如何将springmvc和springboot与swagger结合,通过简单配置生成接口文档,以及介绍了swagger提供的一些注解。下面将介绍swagger是如何做到与springmvc结合,自动…

php实现mysql分表

一、场景说明 1、为什么要进行分表 随着数据量的不断增大,一张表中的数据肯定也会越来越多,甚至达到百万甚至千万级。我们通常会通过搭建mysql集群(主从同步),读写分离来实现优化数据库查询执行效率。 但是由于数据…

利用python进行数据分析D1——ch02引言

基础的课程还没学完,就来这本了,因为我平时的研究还是以数据的处理为主。把自己的事做好做细致读了一下介绍部分,下载书里用到的数据,下载地址:https://github.com/wesm/pydata-book 如果你需要完成以下几大类任务&…

记一次Memory Leak分析

起因:最近公司的一个web产品遇到了内存溢出,于是开始着手调查。调查:首先当务之急是找到那个或那些API导致Memory Leak,这个应该不难,根据监控分析,在内存上升时间段内有哪些API被访问,再就是根…

【t057】任务分配

Time Limit: 1 second Memory Limit: 128 MB 【问题描述】 现有n个任务,要交给A和B完成。每个任务给A或给B完成,所需的时间分别为ai和bi。问他们完成所有的任务至少要多少时间。 【输入格式】 第一行一个正整数n,表示有n个任务。 接下来有n行&#xf…

LeetCode 366. Find Leaves of Binary Tree

实质就是求每个节点的最大深度。用一个hash表记录&#xff0c;最后输出。 class Solution { public:unordered_map<TreeNode *,int> hash; // record the level from bottomvector<vector<int>> findLeaves(TreeNode* root) {vector<vector<int>>…

C#比較对象的相等性

对于相等的机制全部不同&#xff0c;这取决于比較的是引用类型还是值类型。以下分别介绍引用类型和值类型的相等性。1.比較引用类型的相等性 System.Object定义了三种不同的方法&#xff0c;来比較对象的相等性&#xff1a;ReferenceEquals()和两个版本号的Equals()。再加上比較…

WebSocket教程

一、为什么需要 WebSocket&#xff1f; 初次接触 WebSocket 的人&#xff0c;都会问同样的问题&#xff1a;我们已经有了 HTTP 协议&#xff0c;为什么还需要另一个协议&#xff1f;它能带来什么好处&#xff1f; 答案很简单&#xff0c;因为 HTTP 协议有一个缺陷&#xff1a…

C# WPF十个美观的界面设计展示

概述很多时候&#xff0c;我们设计的界面总是感觉缺乏美感&#xff0c;不是我们不会开发好看的界面&#xff0c;而是不知道怎么才算美观&#xff0c;这时候我们不妨看看别人好的页面是怎么做的.下面展示一些我觉得做的比较好的cs界面&#xff0c;希望能给大家在平时做界面设计时…

BZOJ3172: [Tjoi2013]单词

【传送门&#xff1a;BZOJ3172】 简要题意&#xff1a; 给出n个单词&#xff0c;你可以理解为将这些单词变成一个个段落&#xff0c;然后求出每个单词在所有段落中出现的次数 题解&#xff08;一&#xff09;&#xff1a; 刚开始不是很懂题目&#xff0c;结果发现将所有单词看成…

MySQL5.6二进制软件包编译安装详解(三)

一、软件环境 [rootlocalhost ~]# uname -r 3.10.0-862.el7.x86_64 [rootlocalhost ~]# cat /etc/redhat-release CentOS Linux release 7.5.1804 (Core) 二、安装部署过程详解 MySQL安装3种方式&#xff1a;1>rpm包安装应用文件默认安装在/usr/local 目录下2>源码编译需…

Java反射学习总结五(Annotation(注解)-基础篇)

Annotation(注解)简单介绍&#xff1a; 注解大家印象最深刻的可能就是JUnit做单元測试,和各种框架里的使用了。本文主要简介一下注解的用法&#xff0c;下篇文章再深入的研究。 annotation并不直接影响代码语义。可是它可以被看作类似程序的工具或者类库。它会反过来对正在执行…

使用autok3s 安装k3s 集群 和 kuboard 管理集群

一、k3s介绍1.1 什么是k3s?k3s 是经过 CNCF 认证的由 Rancher 公司开发维护的一个轻量级的 Kubernetes 发行版&#xff0c;内核机制还是和 k8s 一样&#xff0c;但是剔除了很多外部依赖以及 K8s 的 alpha、beta 特性&#xff0c;同时改变了部署方式和运行方式&#xff0c;目的…