如果我在 Linux 控制台中运行
nohup node /tmp/test-app/index.js > /tmp/test-app/nohup.out 2>&1 &
命令,它就会起作用。它在后台启动node.js服务器,我可以使用命令ps aux | grep node
来查看正在运行的进程。
但是,如果我运行与 Playbook 中的任务完全相同的命令,则 Playbook 会执行并且该任务会被标记为
changed
,但它实际上不会启动节点服务器。
我已检查
/var/log/messages
并使用 -vvv
详细记录了 Playbook 的执行情况。该命令似乎是转到 Linux 控制台,但节点服务器似乎不是从 Playbook 启动的(但如果直接在控制台上执行该命令,它就会启动。
请让我知道如何解决这个问题。
这是 Playbook 任务:
---
- name: Test app
hosts: all
gather_facts: true
vars:
tmp_dir: '/tmp/test-app'
tasks:
- name: Start the demo app in the background
shell: 'nohup node /tmp/test-app/index.js > /tmp/test-app/nohup.out 2>&1 &'
args:
chdir: '{{tmp_dir}}'
如果我在 Linux 控制台中运行
命令nohup node /tmp/test-app/index.js > /tmp/test-app/nohup.out 2>&1 &
首先 - 您的剧本目标是
all
主机。既然您说剧本执行并将任务标记为 changed
,您也有一个库存,但您没有发布它。因此,如果您的控制器不包含在该清单中(默认情况下不包含),则结果是预期的,因为 Ansible 实际上不会对其执行此任务。
我已检查 /var/log/messages 并使用 -vvv 详细记录了 Playbook 的执行情况。
无论第一点如何,都值得检查重定向 stdout 的文件,尤其是当您将 stderr 重定向到同一位置时 - 这种方法掩盖了任务的实际结果并且应该非常频繁地使用小心或根本避免。
例如,如果我们采用 NodeJS 的当前 LTS 版本 (20.12.0),即来自 https://nodejs.org/en 的示例 NodeJS 服务器代码,并使用您的命令“按原样”运行它,我们'将在
/tmp/test-app/nohup.out
中看到以下内容:
(node:7165) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/private/tmp/test-app/index.js:2
import { createServer } from 'node:http';
^^^^^^
SyntaxError: Cannot use import statement outside a module
at internalCompileFunction (node:internal/vm:128:18)
at wrapSafe (node:internal/modules/cjs/loader:1280:20)
at Module._compile (node:internal/modules/cjs/loader:1332:27)
at Module._extensions..js (node:internal/modules/cjs/loader:1427:10)
at Module.load (node:internal/modules/cjs/loader:1206:32)
at Module._load (node:internal/modules/cjs/loader:1022:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
at node:internal/main/run_main_module:28:49
Node.js v20.12.0
如果我们将脚本名称更改为
server.mjs
,服务器将毫无问题地启动:
TASK [Start the demo app in the background]
******************************************************************************************************
changed: [localhost]
TASK [Display the result]
******************************************************************************************************
ok: [localhost] =>
shell_nohup_result:
changed: true
cmd: nohup node /tmp/test-app/server.mjs > /tmp/test-app/nohup.out 2>&1 &
delta: '0:00:00.007461'
end: '2024-04-01 20:37:41.282527'
failed: false
msg: ''
rc: 0
start: '2024-04-01 20:37:41.275066'
stderr: ''
stderr_lines: []
stdout: ''
stdout_lines: []
TASK [Check the process]
******************************************************************************************************
changed: [localhost]
TASK [Display the processes]
******************************************************************************************************
ok: [localhost] =>
msg:
- alexander 7407 8.8 0.3 34836824 24976 s002 S+ 8:37PM 0:00.08 node /tmp/test-app/server.mjs
TASK [Check the logs]
******************************************************************************************************
ok: [localhost] =>
msg: Listening on 127.0.0.1:3000
...我正在做的
方法有什么问题吗?nohup
除了上面描述的之外,一旦你尝试再次运行相同的剧本,你会再次感到惊讶:
TASK [Check the logs] ******************************************************************************************************
ok: [localhost] =>
msg: |-
node:events:496
throw er; // Unhandled 'error' event
^
Error: listen EADDRINUSE: address already in use 127.0.0.1:3000
at Server.setupListenHandle [as _listen2] (node:net:1897:16)
at listenInCluster (node:net:1945:12)
at doListen (node:net:2109:7)
at process.processTicksAndRejections (node:internal/process/task_queues:83:21)
Emitted 'error' event on Server instance at:
at emitErrorNT (node:net:1924:8)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
code: 'EADDRINUSE',
errno: -48,
syscall: 'listen',
address: '127.0.0.1',
port: 3000
}
Node.js v20.12.0
显然,这不是一个幂等的结果。由于幂等性是 Ansible 旨在推广的主要内容之一,因此您可能希望以某种方式解决此问题。例如,在尝试再次启动之前停止该进程。
请提供经过测试、有效的解决方案。
这可能比人们想象的要复杂一些。您需要跟踪 PID、它正在使用的端口号、根据环境选择要使用的命令、处理其返回代码,因为它们可能会根据应用程序状态而变化,决定是否要应用滚动更新(我怀疑现在就是这样,但仍然是这样),等等。
将以下剧本视为最简单的工作示例(如果
pkill
在您的系统上可用):
---
- name: Test app
hosts: localhost
gather_facts: false
connection: local
vars:
tmp_dir: '/tmp/test-app'
tasks:
- name: Create the test directory
file:
path: '{{ tmp_dir }}'
state: '{{ item }}'
loop:
- absent
- directory
- name: Create an HTTP server using NodeJS (see https://nodejs.org/en for details)
copy:
dest: '{{ tmp_dir }}/server.mjs'
content: |
// server.mjs
import { createServer } from 'node:http';
const server = createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World!\n');
});
// starts a simple http server locally on port 3000
server.listen(3000, '127.0.0.1', () => {
console.log('Listening on 127.0.0.1:3000');
});
// run with `node server.mjs`
- name: Stop the NodeJS processes
command: 'pkill node'
register: command_pkill_result
failed_when: command_pkill_result.rc not in [0, 1]
- name: Start the demo app in the background
shell: 'nohup node /tmp/test-app/server.mjs > /tmp/test-app/nohup.out 2>&1 &'
args:
chdir: '{{ tmp_dir }}'
register: shell_nohup_result
- name: Display the result
debug:
var: shell_nohup_result
- name: Check the processes
command: 'ps aux'
register: command_ps_result
- name: Display the NodeJS processes
debug:
msg: '{{ command_ps_result.stdout_lines | map("regex_search", ".*node.*") | select }}'
- name: Check the logs
debug:
msg: '{{ lookup("file", tmp_dir + "/nohup.out") }}'
- name: Check the actual server
uri:
url: http://127.0.0.1:3000
return_content: true
register: uri_result
- name: Show what the app is trying to say
debug:
var: uri_result.content
这个答案是2014年(10年前)的。您确定这仍然是正确的方法吗?
这么说吧:
总结一下这些说法——在当前环境下没有“正确”的方法。
我们谈论的是仅出于教育目的运行一个简单应用程序的本地实例 - 一旦明显的问题得到解决,您的解决方案就可以正常工作。您可能还想研究
shell
和 command
模块之间的差异:
如果您想以这种方式构建生产级应用程序,当然您必须考虑更多细节,并且这些细节的描述将导致关于 SDLC 的课程。关于工具 - 您将选择限制为几种已经存在多年并经过数千次测试的通用工具,然后选择最适合您需求的工具。例如,您可以选择 在预构建的 NodeJS 容器中运行服务器。 Ansible 还能够控制容器,以及控制 Docker Compose、Helm、Kubernetes 和容器周围的其他工具。但这对于最初的问题来说听起来已经有点偏离主题了。