{"id":445521,"date":"2025-01-20T09:01:05","date_gmt":"2025-01-20T09:01:05","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=445521"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2025-01-20T09:01:05","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=445521","title":{"rendered":"<span>Going NATS<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435 \u0438\u043d\u043d\u043e\u0432\u0430\u0446\u0438\u0438 \u0442\u0430\u043a \u0438\u043b\u0438 \u0438\u043d\u0430\u0447\u0435 \u0431\u0443\u0434\u0443\u0442 \u0441\u0432\u044f\u0437\u0430\u043d\u044b \u0441 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u043c\u0438, \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u0430\u043c\u0438 \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043a\u043b\u044e\u0447\u0435\u0432\u0443\u044e \u0440\u043e\u043b\u044c \u0431\u0443\u0434\u0443\u0442 \u0438\u0433\u0440\u0430\u0442\u044c \u043f\u0435\u0440\u0438\u0444\u0435\u0440\u0438\u0439\u043d\u044b\u0435 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f.<\/p>\n<p>\u041c\u043d\u043e\u0433\u0438\u0435 \u0438\u0437 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0443\u0449\u0438\u0445 \u0437\u0430\u0434\u0430\u0447, \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043d\u0435\u0430\u0434\u0435\u043a\u0432\u0430\u0442\u043d\u044b\u043c\u0438 \u0434\u043b\u044f \u043d\u043e\u0432\u044b\u0445 \u0432\u044b\u0437\u043e\u0432\u043e\u0432.<\/p>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0442\u0430\u043a\u0438\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432, \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 Ansible \u0438 SSH. Ansible, \u043a\u0430\u043a \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 SSH \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0430 \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u043c\u0430\u043d\u0434 \u043d\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0445 \u043c\u0430\u0448\u0438\u043d\u0430\u0445. \u042d\u0442\u043e \u0441\u0432\u043e\u0435\u0433\u043e \u0440\u043e\u0434\u0430 RPC \u0434\u043b\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0445 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u043c\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 YAML.<\/p>\n<p>Ansible \u043e\u0442\u043b\u0438\u0447\u043d\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u043a\u043e\u043c \u0438\u0437 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0441\u043e\u0442\u0435\u043d \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432 \u0432 \u0434\u0430\u0442\u0430-\u0446\u0435\u043d\u0442\u0440\u0435, \u043d\u043e \u043b\u044e\u0431\u043e\u0439, \u043a\u0442\u043e \u043f\u044b\u0442\u0430\u043b\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u044b\u0441\u044f\u0447\u0430\u043c\u0438 \u043c\u0430\u0448\u0438\u043d, \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0441\u0442\u0430\u043b\u043a\u0438\u0432\u0430\u043b\u0441\u044f \u0441 \u0442\u0440\u0443\u0434\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<p>\u0412 \u043c\u0438\u0440\u0435 \u0433\u0434\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u0442\u044b\u0441\u044f\u0447 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432, \u043f\u0435\u0440\u0438\u0444\u0435\u0440\u0438\u0439\u043d\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 Ansible \u043d\u0435\u0430\u0434\u0435\u043a\u0432\u0430\u0442\u043d\u044b \u0438 \u044d\u0442\u043e \u0441\u0442\u0430\u043b\u043e \u043e\u0434\u043d\u043e\u0439 \u0438\u0437 \u043f\u0440\u0438\u0447\u0438\u043d \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0430\u043a\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u043a\u0430\u043a Kubernetes.<\/p>\n<p>\u041d\u043e \u0434\u043b\u044f Edge AI \u0438 Agentic AI \u043d\u0443\u0436\u043d\u044b \u043d\u043e\u0432\u044b\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u044b.<\/p>\n<p><a href=\"https:\/\/nats.io\" rel=\"noopener noreferrer nofollow\">NATS<\/a> \u044d\u0442\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0434\u043b\u044f \u043e\u0431\u043c\u0435\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u0430\u044f, \u043d\u0430 \u043c\u043e\u0439 \u0432\u0437\u0433\u043b\u044f\u0434 \u043e\u0442\u043b\u0438\u0447\u043d\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u0440\u043e\u0434\u0430 \u0437\u0430\u0434\u0430\u0447.<\/p>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 NATS \u043c\u043e\u0436\u0435\u0442 \u0441\u0442\u0430\u0442\u044c \u043e\u0441\u043d\u043e\u0432\u043e\u0439 \u0434\u043b\u044f \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u0430\u043c\u0438 \u0438 \u043f\u0435\u0440\u0438\u0444\u0435\u0440\u0438\u0439\u043d\u044b\u043c\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438.<\/p>\n<p>\u0414\u043b\u044f \u0442\u0435\u0445 \u043a\u0442\u043e \u0437\u043d\u0430\u043a\u043e\u043c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u0442\u044c \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u044b \u0440\u0430\u0431\u043e\u0442\u044b pub\/sub. \u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e NATS \u043f\u043e\u0445\u043e\u0436 \u043d\u0430 Kafka \u0438 RabbitMQ. \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0435 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438 \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u0430 \u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u0435\u043b\u0430\u0435\u0442 NATS \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u044b\u043c \u0432\u044b\u0431\u043e\u0440\u043e\u043c \u0438\u043c\u0435\u043d\u043d\u043e \u0434\u043b\u044f \u043f\u0435\u0440\u0438\u0444\u0435\u0440\u0438\u0439\u043d\u044b\u0445 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0439.<\/p>\n<p>\u041d\u0438\u0436\u0435 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 RPC \u0434\u043b\u044f LLM \u043d\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0439 \u043c\u0430\u0448\u0438\u043d\u0435.<\/p>\n<p>\u0414\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 NATS:<\/p>\n<pre><code>curl -sf https:\/\/binaries.nats.dev\/nats-io\/nats-server\/v2@v2.10.20 | sh <\/code><\/pre>\n<p>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440 NATS:<\/p>\n<pre><code>.\/nats-server -m 8222 &amp; <\/code><\/pre>\n<p>\u041a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u043f\u0440\u043e\u0441\u0442\u043e\u0439 UI \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043d\u0430 <a href=\"http:\/\/localhost:8080\" rel=\"noopener noreferrer nofollow\">http:\/\/localhost:8080<\/a>: <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/\/post_images\/c51\/7b6\/9fa\/c517b69fa836b076694adaf6a8365787.gif\" alt=\"Command Publisher\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/\/post_images\/c51\/7b6\/9fa\/c517b69fa836b076694adaf6a8365787.gif\"\/><\/p>\n<div><figcaption>Command Publisher<\/figcaption><\/div>\n<\/figure>\n<p>Subscriber:<\/p>\n<pre><code class=\"go\">package main  import ( \"fmt\" \"os\/exec\" \"runtime\"  \"github.com\/nats-io\/nats.go\" )  func executeCommand(command string) (string, error) { var cmd *exec.Cmd if runtime.GOOS == \"windows\" { cmd = exec.Command(\"cmd\", \"\/C\", command) } else { cmd = exec.Command(\"sh\", \"-c\", command) }  output, err := cmd.CombinedOutput() if err != nil { return \"\", fmt.Errorf(\"error executing command: %v, output: %s\", err, string(output)) }  return string(output), nil }  func main() { nc, err := nats.Connect(nats.DefaultURL) if err != nil { fmt.Println(\"Error connecting to NATS:\", err) return } defer nc.Close()  _, err = nc.Subscribe(\"commands\", func(msg *nats.Msg) { command := string(msg.Data) fmt.Printf(\"Received command: %s\\n\", command)  output, err := executeCommand(command) if err != nil { fmt.Println(\"Error:\", err) nc.Publish(msg.Reply, []byte(fmt.Sprintf(\"Error: %v\", err))) return }  nc.Publish(msg.Reply, []byte(output)) fmt.Printf(\"Command executed successfully: %s\\n\", output) })  if err != nil { fmt.Println(\"Error subscribing to subject:\", err) return }  fmt.Println(\"Remote host is listening for commands...\") select {} } <\/code><\/pre>\n<p>\u0438<\/p>\n<p>Publisher:<\/p>\n<pre><code class=\"go\">package main  import ( \"fmt\" \"html\/template\" \"log\" \"net\/http\" \"time\"  \"github.com\/nats-io\/nats.go\" )  const natsServer = \"nats:\/\/localhost:4222\"  const htmlTemplate = ` &lt;!DOCTYPE html&gt; &lt;html lang=\"en\"&gt; &lt;head&gt;     &lt;meta charset=\"UTF-8\"&gt;     &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"&gt;     &lt;title&gt;Command Publisher&lt;\/title&gt;     &lt;link href=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@5.3.0-alpha1\/dist\/css\/bootstrap.min.css\" rel=\"stylesheet\"&gt;     &lt;style&gt;         body {             background-color: #f8f9fa;             padding: 20px;         }         .container {             max-width: 800px;             margin: 0 auto;         }         .response-box {             background-color: #ffffff;             border: 1px solid #dee2e6;             border-radius: 5px;             padding: 15px;             margin-top: 20px;         }     &lt;\/style&gt; &lt;\/head&gt; &lt;body&gt;     &lt;div class=\"container\"&gt;         &lt;h1 class=\"text-center my-4\"&gt;Command Publisher&lt;\/h1&gt;         &lt;form method=\"POST\" action=\"\/publish\" class=\"mb-4\"&gt;             &lt;div class=\"mb-3\"&gt;                 &lt;label for=\"command\" class=\"form-label\"&gt;Enter Command:&lt;\/label&gt;                 &lt;input type=\"text\" id=\"command\" name=\"command\" class=\"form-control\" placeholder=\"e.g., ls -l\" required&gt;             &lt;\/div&gt;             &lt;button type=\"submit\" class=\"btn btn-primary\"&gt;Execute&lt;\/button&gt;         &lt;\/form&gt;          {{if .Response}}         &lt;div class=\"response-box\"&gt;             &lt;h2&gt;Response:&lt;\/h2&gt;             &lt;pre class=\"bg-light p-3 border rounded\"&gt;{{.Response}}&lt;\/pre&gt;         &lt;\/div&gt;         {{end}}     &lt;\/div&gt;      &lt;script src=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@5.3.0-alpha1\/dist\/js\/bootstrap.bundle.min.js\"&gt;&lt;\/script&gt; &lt;\/body&gt; &lt;\/html&gt; `  type PageData struct { Response string }  func main() { nc, err := nats.Connect(natsServer) if err != nil { log.Fatalf(\"Error connecting to NATS: %v\", err) } defer nc.Close()  http.HandleFunc(\"\/\", func(w http.ResponseWriter, r *http.Request) { tmpl := template.Must(template.New(\"index\").Parse(htmlTemplate)) data := PageData{Response: \"\"} tmpl.Execute(w, data) })  http.HandleFunc(\"\/publish\", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { http.Error(w, \"Error parsing form\", http.StatusBadRequest) return }  command := r.FormValue(\"command\") if command == \"\" { http.Error(w, \"Command cannot be empty\", http.StatusBadRequest) return }  msg, err := nc.Request(\"commands\", []byte(command), 5*time.Second) if err != nil { http.Error(w, fmt.Sprintf(\"Error sending command: %v\", err), http.StatusInternalServerError) return }  tmpl := template.Must(template.New(\"index\").Parse(htmlTemplate)) data := PageData{Response: string(msg.Data)} tmpl.Execute(w, data) })  fmt.Println(\"Web UI is running on http:\/\/localhost:8080\") log.Fatal(http.ListenAndServe(\":8080\", nil)) } <\/code><\/pre>\n<p>\u0410\u0433\u0435\u043d\u0442 AI \u2013 \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u0441\u044f \u043d\u0430 \u043a\u0430\u043a\u043e\u043c-\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435. \u041d\u0435\u0441\u043b\u043e\u0436\u043d\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c, \u0447\u0442\u043e \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0435\u0444\u0435\u0440\u0438\u0439\u043d\u044b\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c: \u043a\u0430\u043c\u0435\u0440\u0430, \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u044f\u0442\u043e\u0440, \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u0440\u0438\u043a\u0438. \u041d\u043e \u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e LLM \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u0442\u0430\u043a \u0436\u0435 \u043a\u0430\u043a \u0438 \u0432 \u043e\u0431\u044b\u0447\u043d\u043e\u043c Ansible \u043f\u043b\u0435\u0439\u0431\u0443\u043a\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0432 YAML \u0444\u0430\u0439\u043b\u0435.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/\/post_images\/311\/62f\/7c4\/31162f7c40b5f8e3c669eccf05e7e8e6.png\" alt=\"Manifest\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/\/post_images\/311\/62f\/7c4\/31162f7c40b5f8e3c669eccf05e7e8e6.png\"\/><\/p>\n<div><figcaption>Manifest<\/figcaption><\/div>\n<\/figure>\n<p> \u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c YAML \u043f\u0430\u0440\u0441\u0435\u0440 \u0432 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0443\u044e \u0447\u0430\u0441\u0442\u044c. <\/p>\n<pre><code class=\"go\">package main  import ( \"fmt\" \"log\" \"os\" \"time\"  \"github.com\/nats-io\/nats.go\" \"gopkg.in\/yaml.v3\" )  const natsServer = \"nats:\/\/localhost:4222\"  type Task struct { Name    string `yaml:\"name\"` Command string `yaml:\"command\"` }  type Manifest struct { Tasks []Task `yaml:\"tasks\"` }  func main() { nc, err := nats.Connect(natsServer) if err != nil { log.Fatalf(\"Error connecting to NATS: %v\", err) } defer nc.Close()  yamlFile, err := os.ReadFile(\"manifest.yaml\") if err != nil { log.Fatalf(\"Error reading YAML file: %v\", err) }  var manifest Manifest err = yaml.Unmarshal(yamlFile, &amp;manifest) if err != nil { log.Fatalf(\"Error parsing YAML file: %v\", err) }  for _, task := range manifest.Tasks { fmt.Printf(\"Executing task: %s\\n\", task.Name) fmt.Printf(\"Command: %s\\n\", task.Command)  msg, err := nc.Request(\"commands\", []byte(task.Command), 5*time.Second) if err != nil { if err == nats.ErrTimeout { fmt.Println(\"Error: Request timed out. The subscriber did not respond in time.\") } else { fmt.Println(\"Error sending command:\", err) } continue }  fmt.Printf(\"Response from remote host:\\n%s\\n\", string(msg.Data)) fmt.Println(\"----------------------------------------\") } } <\/code><\/pre>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/\/post_images\/d09\/203\/994\/d0920399467da0f879ec1b07827e8246.png\" alt=\"client.go\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/\/post_images\/d09\/203\/994\/d0920399467da0f879ec1b07827e8246.png\"\/><\/p>\n<div><figcaption>client.go<\/figcaption><\/div>\n<\/figure>\n<p>\u00ab\u0411\u0443\u0434\u0443\u0449\u0435\u0435 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u0438\u043b\u043e, \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u043d\u043e \u0435\u0449\u0435 \u043d\u0435\u0440\u0430\u0432\u043d\u043e\u043c\u0435\u0440\u043d\u043e \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043e\u00bb. \u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0443 \u0437\u0430\u0434\u0430\u0447, \u043d\u0430\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u043d\u043e\u0432\u044b\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/874828\/\"> https:\/\/habr.com\/ru\/articles\/874828\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435 \u0438\u043d\u043d\u043e\u0432\u0430\u0446\u0438\u0438 \u0442\u0430\u043a \u0438\u043b\u0438 \u0438\u043d\u0430\u0447\u0435 \u0431\u0443\u0434\u0443\u0442 \u0441\u0432\u044f\u0437\u0430\u043d\u044b \u0441 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u043c\u0438, \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u0430\u043c\u0438 \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043a\u043b\u044e\u0447\u0435\u0432\u0443\u044e \u0440\u043e\u043b\u044c \u0431\u0443\u0434\u0443\u0442 \u0438\u0433\u0440\u0430\u0442\u044c \u043f\u0435\u0440\u0438\u0444\u0435\u0440\u0438\u0439\u043d\u044b\u0435 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f.<\/p>\n<p>\u041c\u043d\u043e\u0433\u0438\u0435 \u0438\u0437 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0443\u0449\u0438\u0445 \u0437\u0430\u0434\u0430\u0447, \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043d\u0435\u0430\u0434\u0435\u043a\u0432\u0430\u0442\u043d\u044b\u043c\u0438 \u0434\u043b\u044f \u043d\u043e\u0432\u044b\u0445 \u0432\u044b\u0437\u043e\u0432\u043e\u0432.<\/p>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0442\u0430\u043a\u0438\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432, \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 Ansible \u0438 SSH. Ansible, \u043a\u0430\u043a \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 SSH \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0430 \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u043c\u0430\u043d\u0434 \u043d\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0445 \u043c\u0430\u0448\u0438\u043d\u0430\u0445. \u042d\u0442\u043e \u0441\u0432\u043e\u0435\u0433\u043e \u0440\u043e\u0434\u0430 RPC \u0434\u043b\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0445 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u043c\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 YAML.<\/p>\n<p>Ansible \u043e\u0442\u043b\u0438\u0447\u043d\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u043a\u043e\u043c \u0438\u0437 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0441\u043e\u0442\u0435\u043d \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432 \u0432 \u0434\u0430\u0442\u0430-\u0446\u0435\u043d\u0442\u0440\u0435, \u043d\u043e \u043b\u044e\u0431\u043e\u0439, \u043a\u0442\u043e \u043f\u044b\u0442\u0430\u043b\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u044b\u0441\u044f\u0447\u0430\u043c\u0438 \u043c\u0430\u0448\u0438\u043d, \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0441\u0442\u0430\u043b\u043a\u0438\u0432\u0430\u043b\u0441\u044f \u0441 \u0442\u0440\u0443\u0434\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<p>\u0412 \u043c\u0438\u0440\u0435 \u0433\u0434\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u0442\u044b\u0441\u044f\u0447 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432, \u043f\u0435\u0440\u0438\u0444\u0435\u0440\u0438\u0439\u043d\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 Ansible \u043d\u0435\u0430\u0434\u0435\u043a\u0432\u0430\u0442\u043d\u044b \u0438 \u044d\u0442\u043e \u0441\u0442\u0430\u043b\u043e \u043e\u0434\u043d\u043e\u0439 \u0438\u0437 \u043f\u0440\u0438\u0447\u0438\u043d \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0430\u043a\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u043a\u0430\u043a Kubernetes.<\/p>\n<p>\u041d\u043e \u0434\u043b\u044f Edge AI \u0438 Agentic AI \u043d\u0443\u0436\u043d\u044b \u043d\u043e\u0432\u044b\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u044b.<\/p>\n<p><a href=\"https:\/\/nats.io\" rel=\"noopener noreferrer nofollow\">NATS<\/a> \u044d\u0442\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0434\u043b\u044f \u043e\u0431\u043c\u0435\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u0430\u044f, \u043d\u0430 \u043c\u043e\u0439 \u0432\u0437\u0433\u043b\u044f\u0434 \u043e\u0442\u043b\u0438\u0447\u043d\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u0440\u043e\u0434\u0430 \u0437\u0430\u0434\u0430\u0447.<\/p>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 NATS \u043c\u043e\u0436\u0435\u0442 \u0441\u0442\u0430\u0442\u044c \u043e\u0441\u043d\u043e\u0432\u043e\u0439 \u0434\u043b\u044f \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u0430\u043c\u0438 \u0438 \u043f\u0435\u0440\u0438\u0444\u0435\u0440\u0438\u0439\u043d\u044b\u043c\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438.<\/p>\n<p>\u0414\u043b\u044f \u0442\u0435\u0445 \u043a\u0442\u043e \u0437\u043d\u0430\u043a\u043e\u043c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u0442\u044c \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u044b \u0440\u0430\u0431\u043e\u0442\u044b pub\/sub. \u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e NATS \u043f\u043e\u0445\u043e\u0436 \u043d\u0430 Kafka \u0438 RabbitMQ. \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0435 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438 \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u0430 \u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u0435\u043b\u0430\u0435\u0442 NATS \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u044b\u043c \u0432\u044b\u0431\u043e\u0440\u043e\u043c \u0438\u043c\u0435\u043d\u043d\u043e \u0434\u043b\u044f \u043f\u0435\u0440\u0438\u0444\u0435\u0440\u0438\u0439\u043d\u044b\u0445 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0439.<\/p>\n<p>\u041d\u0438\u0436\u0435 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 RPC \u0434\u043b\u044f LLM \u043d\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0439 \u043c\u0430\u0448\u0438\u043d\u0435.<\/p>\n<p>\u0414\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 NATS:<\/p>\n<pre><code>curl -sf https:\/\/binaries.nats.dev\/nats-io\/nats-server\/v2@v2.10.20 | sh <\/code><\/pre>\n<p>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440 NATS:<\/p>\n<pre><code>.\/nats-server -m 8222 &amp; <\/code><\/pre>\n<p>\u041a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u043f\u0440\u043e\u0441\u0442\u043e\u0439 UI \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043d\u0430 <a href=\"http:\/\/localhost:8080\" rel=\"noopener noreferrer nofollow\">http:\/\/localhost:8080<\/a>: <\/p>\n<figure class=\"\">\n<div><figcaption>Command Publisher<\/figcaption><\/div>\n<\/figure>\n<p>Subscriber:<\/p>\n<pre><code class=\"go\">package main  import ( \"fmt\" \"os\/exec\" \"runtime\"  \"github.com\/nats-io\/nats.go\" )  func executeCommand(command string) (string, error) { var cmd *exec.Cmd if runtime.GOOS == \"windows\" { cmd = exec.Command(\"cmd\", \"\/C\", command) } else { cmd = exec.Command(\"sh\", \"-c\", command) }  output, err := cmd.CombinedOutput() if err != nil { return \"\", fmt.Errorf(\"error executing command: %v, output: %s\", err, string(output)) }  return string(output), nil }  func main() { nc, err := nats.Connect(nats.DefaultURL) if err != nil { fmt.Println(\"Error connecting to NATS:\", err) return } defer nc.Close()  _, err = nc.Subscribe(\"commands\", func(msg *nats.Msg) { command := string(msg.Data) fmt.Printf(\"Received command: %s\\n\", command)  output, err := executeCommand(command) if err != nil { fmt.Println(\"Error:\", err) nc.Publish(msg.Reply, []byte(fmt.Sprintf(\"Error: %v\", err))) return }  nc.Publish(msg.Reply, []byte(output)) fmt.Printf(\"Command executed successfully: %s\\n\", output) })  if err != nil { fmt.Println(\"Error subscribing to subject:\", err) return }  fmt.Println(\"Remote host is listening for commands...\") select {} } <\/code><\/pre>\n<p>\u0438<\/p>\n<p>Publisher:<\/p>\n<pre><code class=\"go\">package main  import ( \"fmt\" \"html\/template\" \"log\" \"net\/http\" \"time\"  \"github.com\/nats-io\/nats.go\" )  const natsServer = \"nats:\/\/localhost:4222\"  const htmlTemplate = ` &lt;!DOCTYPE html&gt; &lt;html lang=\"en\"&gt; &lt;head&gt;     &lt;meta charset=\"UTF-8\"&gt;     &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"&gt;     &lt;title&gt;Command Publisher&lt;\/title&gt;     &lt;link href=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@5.3.0-alpha1\/dist\/css\/bootstrap.min.css\" rel=\"stylesheet\"&gt;     &lt;style&gt;         body {             background-color: #f8f9fa;             padding: 20px;         }         .container {             max-width: 800px;             margin: 0 auto;         }         .response-box {             background-color: #ffffff;             border: 1px solid #dee2e6;             border-radius: 5px;             padding: 15px;             margin-top: 20px;         }     &lt;\/style&gt; &lt;\/head&gt; &lt;body&gt;     &lt;div class=\"container\"&gt;         &lt;h1 class=\"text-center my-4\"&gt;Command Publisher&lt;\/h1&gt;         &lt;form method=\"POST\" action=\"\/publish\" class=\"mb-4\"&gt;             &lt;div class=\"mb-3\"&gt;                 &lt;label for=\"command\" class=\"form-label\"&gt;Enter Command:&lt;\/label&gt;                 &lt;input type=\"text\" id=\"command\" name=\"command\" class=\"form-control\" placeholder=\"e.g., ls -l\" required&gt;             &lt;\/div&gt;             &lt;button type=\"submit\" class=\"btn btn-primary\"&gt;Execute&lt;\/button&gt;         &lt;\/form&gt;          {{if .Response}}         &lt;div class=\"response-box\"&gt;             &lt;h2&gt;Response:&lt;\/h2&gt;             &lt;pre class=\"bg-light p-3 border rounded\"&gt;{{.Response}}&lt;\/pre&gt;         &lt;\/div&gt;         {{end}}     &lt;\/div&gt;      &lt;script src=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@5.3.0-alpha1\/dist\/js\/bootstrap.bundle.min.js\"&gt;&lt;\/script&gt; &lt;\/body&gt; &lt;\/html&gt; `  type PageData struct { Response string }  func main() { nc, err := nats.Connect(natsServer) if err != nil { log.Fatalf(\"Error connecting to NATS: %v\", err) } defer nc.Close()  http.HandleFunc(\"\/\", func(w http.ResponseWriter, r *http.Request) { tmpl := template.Must(template.New(\"index\").Parse(htmlTemplate)) data := PageData{Response: \"\"} tmpl.Execute(w, data) })  http.HandleFunc(\"\/publish\", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { http.Error(w, \"Error parsing form\", http.StatusBadRequest) return }  command := r.FormValue(\"command\") if command == \"\" { http.Error(w, \"Command cannot be empty\", http.StatusBadRequest) return }  msg, err := nc.Request(\"commands\", []byte(command), 5*time.Second) if err != nil { http.Error(w, fmt.Sprintf(\"Error sending command: %v\", err), http.StatusInternalServerError) return }  tmpl := template.Must(template.New(\"index\").Parse(htmlTemplate)) data := PageData{Response: string(msg.Data)} tmpl.Execute(w, data) })  fmt.Println(\"Web UI is running on http:\/\/localhost:8080\") log.Fatal(http.ListenAndServe(\":8080\", nil)) } <\/code><\/pre>\n<p>\u0410\u0433\u0435\u043d\u0442 AI \u2013 \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u0441\u044f \u043d\u0430 \u043a\u0430\u043a\u043e\u043c-\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435. \u041d\u0435\u0441\u043b\u043e\u0436\u043d\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c, \u0447\u0442\u043e \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0435\u0444\u0435\u0440\u0438\u0439\u043d\u044b\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c: \u043a\u0430\u043c\u0435\u0440\u0430, \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u044f\u0442\u043e\u0440, \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u0440\u0438\u043a\u0438. \u041d\u043e \u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e LLM \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u0442\u0430\u043a \u0436\u0435 \u043a\u0430\u043a \u0438 \u0432 \u043e\u0431\u044b\u0447\u043d\u043e\u043c Ansible \u043f\u043b\u0435\u0439\u0431\u0443\u043a\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0432 YAML \u0444\u0430\u0439\u043b\u0435.<\/p>\n<figure class=\"\">\n<div><figcaption>Manifest<\/figcaption><\/div>\n<\/figure>\n<p> \u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c YAML \u043f\u0430\u0440\u0441\u0435\u0440 \u0432 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0443\u044e \u0447\u0430\u0441\u0442\u044c. <\/p>\n<pre><code class=\"go\">package main  import ( \"fmt\" \"log\" \"os\" \"time\"  \"github.com\/nats-io\/nats.go\" \"gopkg.in\/yaml.v3\" )  const natsServer = \"nats:\/\/localhost:4222\"  type Task struct { Name    string `yaml:\"name\"` Command string `yaml:\"command\"` }  type Manifest struct { Tasks []Task `yaml:\"tasks\"` }  func main() { nc, err := nats.Connect(natsServer) if err != nil { log.Fatalf(\"Error connecting to NATS: %v\", err) } defer nc.Close()  yamlFile, err := os.ReadFile(\"manifest.yaml\") if err != nil { log.Fatalf(\"Error reading YAML file: %v\", err) }  var manifest Manifest err = yaml.Unmarshal(yamlFile, &amp;manifest) if err != nil { log.Fatalf(\"Error parsing YAML file: %v\", err) }  for _, task := range manifest.Tasks { fmt.Printf(\"Executing task: %s\\n\", task.Name) fmt.Printf(\"Command: %s\\n\", task.Command)  msg, err := nc.Request(\"commands\", []byte(task.Command), 5*time.Second) if err != nil { if err == nats.ErrTimeout { fmt.Println(\"Error: Request timed out. The subscriber did not respond in time.\") } else { fmt.Println(\"Error sending command:\", err) } continue }  fmt.Printf(\"Response from remote host:\\n%s\\n\", string(msg.Data)) fmt.Println(\"----------------------------------------\") } } <\/code><\/pre>\n<figure class=\"\">\n<div><figcaption>client.go<\/figcaption><\/div>\n<\/figure>\n<p>\u00ab\u0411\u0443\u0434\u0443\u0449\u0435\u0435 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u0438\u043b\u043e, \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u043d\u043e \u0435\u0449\u0435 \u043d\u0435\u0440\u0430\u0432\u043d\u043e\u043c\u0435\u0440\u043d\u043e \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043e\u00bb. \u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0443 \u0437\u0430\u0434\u0430\u0447, \u043d\u0430\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u043d\u043e\u0432\u044b\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/874828\/\"> https:\/\/habr.com\/ru\/articles\/874828\/<\/a><br \/><\/br><\/br><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-445521","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/445521","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=445521"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/445521\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=445521"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=445521"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=445521"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}