<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>argo | 伪架构师</title>
    <link>/tags/argo/</link>
      <atom:link href="/tags/argo/index.xml" rel="self" type="application/rss+xml" />
    <description>argo</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Tue, 11 Feb 2025 21:56:17 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>argo</title>
      <link>/tags/argo/</link>
    </image>
    
    <item>
      <title>在 Argo workflow 中使用 OBS 进行制品传递</title>
      <link>/post/artifact-with-obs-in-argo/</link>
      <pubDate>Tue, 11 Feb 2025 21:56:17 +0800</pubDate>
      <guid>/post/artifact-with-obs-in-argo/</guid>
      <description>

&lt;p&gt;在所有的通用工作流中，都会有文件传递的需求，Argo workflow 中，可以通过对接外部存储来支持这一需求。下面就以华为云为例，展示一下对接对象存储的过程。&lt;/p&gt;

&lt;h2 id=&#34;obs-侧配置&#34;&gt;OBS 侧配置&lt;/h2&gt;

&lt;p&gt;首先在 OBS 服务中创建一个存储桶，并在控制台的&lt;code&gt;用户-&amp;gt;我的凭证-&amp;gt;访问密钥&lt;/code&gt;模块中，创建一个访问密钥，并下载凭据文件，凭据文件格式大致如下所示：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;User Name,Access Key Id,Secret Access Key
&amp;quot;myusername&amp;quot;,Y9C3WCABCDEFG,6bHX5eHIJKLMN
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;argo-workflow-配置&#34;&gt;Argo workflow 配置&lt;/h2&gt;

&lt;p&gt;使用文件中的 Access Key 和 Secret Access Key ，在&lt;strong&gt;Workflow 所在的 Namespace 中&lt;/strong&gt;创建 Kubernetes Secret。例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create secret generic s3-secret \
    --from-literal accessKey=Y9C3WCABCDEFG \
    --from-literal secretKey=6bHX5eHIJKLMN
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来需要修改 Argo workflow 的配置文件，加入对制品的支持内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  artifactRepository: |
    archiveLogs: true
    s3:
      endpoint: obs.[Region ID].myhuaweicloud.com
      bucket: [Bucket Name]
      region: cn-north-4
      insecure: false
      keyFormat: &amp;quot;my-artifacts\
        /{{workflow.creationTimestamp.Y}}\
        /{{workflow.creationTimestamp.m}}\
        /{{workflow.creationTimestamp.d}}\
        /{{workflow.name}}\
        /{{pod.name}}&amp;quot;

      accessKeySecret:
        name: s3-secret
        key: accessKey
      secretKeySecret:
        name: s3-secret
        key: secretKey
      useSDKCreds: false
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的配置大致解释一下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在 OBS 中存储 Pod 日志&lt;/li&gt;
&lt;li&gt;使用了华为云北京四 Region 的 OBS 端点。&lt;/li&gt;
&lt;li&gt;需要引用前面创建的存储桶名称&lt;/li&gt;
&lt;li&gt;使用加密方式进行访问&lt;/li&gt;
&lt;li&gt;制品的存储路径模板为：&lt;code&gt;my-artifacts/实例创建时间（年/月/日）/实例名称/步骤所在 Pod 名称/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Access Key 引用 Kubernetes Secret 中名为 &lt;code&gt;s3-secret&lt;/code&gt; 的 &lt;code&gt;accessKey&lt;/code&gt; 字段&lt;/li&gt;
&lt;li&gt;Secret Key 引用 Kubernetes Secret 中名为 &lt;code&gt;s3-secret&lt;/code&gt; 的 &lt;code&gt;secretKey&lt;/code&gt; 字段&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;将上述内容加入 Argo workflow 所在命名空间的 &lt;code&gt;workflow-controller-configmap&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;启动工作流&#34;&gt;启动工作流&lt;/h2&gt;

&lt;p&gt;尝试启动一个使用制品能力的工作流，清单内容来自&lt;code&gt;https://argo-workflows.readthedocs.io/en/latest/walk-through/artifacts/&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;这个流程模板中定义了两个工步：&lt;/p&gt;

&lt;h3 id=&#34;生成制品&#34;&gt;生成制品&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;...
outputs:
  artifacts:
  # generate hello-art artifact from /tmp/hello_world.txt
  # artifacts can be directories as well as files
  - name: hello-art
    path: /tmp/hello_world.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上述代码中，将 &lt;code&gt;/tmp/hello_world.txt&lt;/code&gt; 内容作为制品，并命名为 &lt;code&gt;hello-art&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&#34;读取制品&#34;&gt;读取制品&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;inputs:
  artifacts:
  # unpack the message input artifact
  # and put it at /tmp/message
  - name: message
    path: /tmp/message
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这段代码则是获取输入中名为 &lt;code&gt;message&lt;/code&gt; 的制品，并解压到 &lt;code&gt;/tmp/message&lt;/code&gt; 路径下。&lt;/p&gt;

&lt;p&gt;执行时候，用 &lt;code&gt;{{steps.generate-artifact.outputs.artifacts.hello-art}}&lt;/code&gt; 方式引用生成的制品。&lt;/p&gt;

&lt;h3 id=&#34;执行&#34;&gt;执行&lt;/h3&gt;

&lt;p&gt;使用 Argo CLI 启动流程后，会看到类似如下的输出：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;Name:                artifact-passing-mkn57
Namespace:           default
ServiceAccount:      argo-executor
Status:              Succeeded
...
STEP                       TEMPLATE                 PODNAME                                                    DURATION  MESSAGE
 ✔ artifact-passing-mkn57  artifact-example
 ├───✔ generate-artifact   hello-world-to-file      artifact-passing-mkn57-hello-world-to-file-551171166       8s
 └───✔ consume-artifact    print-message-from-file  artifact-passing-mkn57-print-message-from-file-1735545326  8s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这时如果返回 OBS 面板，会看到存储桶中，按照前面的路径规则存储了文件以及相关的日志（&lt;code&gt;*.log&lt;/code&gt;）。&lt;/p&gt;

&lt;h2 id=&#34;其他制品相关功能&#34;&gt;其他制品相关功能&lt;/h2&gt;

&lt;h3 id=&#34;覆盖仓库配置&#34;&gt;覆盖仓库配置&lt;/h3&gt;

&lt;p&gt;前面我们在 Workflow Controller 配置文件中的配置，适用于单租户场景；多租户场景下，还可以通过 &lt;code&gt;artifactRepositoryRef&lt;/code&gt; 方式，让每个流程可以使用自己的制品配置（&lt;code&gt;https://argo-workflows.readthedocs.io/en/latest/artifact-repository-ref/&lt;/code&gt;）。&lt;/p&gt;

&lt;p&gt;首先使用 Configmap 定义多个存储对接的参数，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: ConfigMap
metadata:
  # If you want to use this config map by default, name it &amp;quot;artifact-repositories&amp;quot;. Otherwise, you can provide a reference to a
  # different config map in `artifactRepositoryRef.configMap`.
  name: my-artifact-repository
  annotations:
    # v3.0 and after - if you want to use a specific key, put that key into this annotation.
    workflows.argoproj.io/default-artifact-repository: default-v1-s3-artifact-repository
data:
  default-v1-s3-artifact-repository: |
    s3:
...
  v2-s3-artifact-repository: |
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这段 YAML 中，提供了几个信息：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;如果想要默认使用这个 Configmap 定义制品仓库，可以将其名称设置为 &lt;code&gt;artifact-repositories&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果不是默认，就需要在 &lt;code&gt;artifactRepositoryRef.configMap&lt;/code&gt; 中显示定义 Configmap 名称。&lt;/li&gt;
&lt;li&gt;v3.0 以后，可以用 &lt;code&gt;workflows.argoproj.io/default-artifact-repository&lt;/code&gt; 注解定义这个 Configmap 中的默认仓库定义&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data&lt;/code&gt; 字段定义了两个制品仓库。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;然后可以在 Workflow 中引用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;spec:
  artifactRepositoryRef:
    configMap: my-artifact-repository
    key: v2-s3-artifact-repository
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;垃圾回收&#34;&gt;垃圾回收&lt;/h3&gt;

&lt;p&gt;在 Workflow 的 &lt;code&gt;spec.artifactGC&lt;/code&gt; 中，可以定义 Garbage Collection 的策略。可选策略包括 &lt;code&gt;OnWorkflowCompletion&lt;/code&gt; 和 &lt;code&gt;OnWorkflowDeletion&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&#34;存储驱动能力列表&#34;&gt;存储驱动能力列表&lt;/h3&gt;

&lt;p&gt;除了 S3 之外，目前 Argo Workflow 支持的存储驱动能力如下：&lt;/p&gt;

&lt;p&gt;(&lt;code&gt;https://argo-workflows.readthedocs.io/en/latest/configure-artifact-repository/&lt;/code&gt;)&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Inputs&lt;/th&gt;
&lt;th&gt;Outputs&lt;/th&gt;
&lt;th&gt;Garbage Collection&lt;/th&gt;
&lt;th&gt;Usage (Feb 2020)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Artifactory&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;11%&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Azure Blob&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;GCS&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Git&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;HDFS&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;3%&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;2%&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;OSS&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Raw&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;S3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;86%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</description>
    </item>
    
    <item>
      <title>在 Argo workflow 中使用插件减少并行 Pod 数量</title>
      <link>/post/reduce-pods-with-plugins-in-argo/</link>
      <pubDate>Fri, 29 Nov 2024 21:41:22 +0800</pubDate>
      <guid>/post/reduce-pods-with-plugins-in-argo/</guid>
      <description>&lt;p&gt;在之前写过的使用 Argo workflow 调用公有云客户端软件实现运维过程的文章中，可以看到，使用 Argo workflow 的容器模板，简单的将既有运维能力容器化，就能使用 Argo workflow 对这些能力进行编排了。&lt;/p&gt;

&lt;p&gt;不过近期一个测试中，遇到个小麻烦——在一个 &lt;code&gt;With&lt;/code&gt; 循环里，我输入了 500 个任务，结果是 6 节点 CCE 集群爆满，流程卡住——集群规模的事情很简单，我直接将 Argo workflow 部署到 CCE Autopilot 集群中，随着流程启动，Auto pilot 集群非常给力，不到一分钟就扩容到了上百节点。然而新的问题出现了，Argo workflow 容器模板使用的镜像托管在 &lt;code&gt;quay.io&lt;/code&gt; 上，我被限流了——无法拉取镜像，工作流自然也就无法执行了。&lt;/p&gt;

&lt;p&gt;如果说必须要限流的话，Argo workflow 提供了多种机制，在不同粒度上对工作流的并发进行控制：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在模板中，使用 &lt;code&gt;parallelism&lt;/code&gt; 参数，限制流程实例内的并发数。&lt;/li&gt;
&lt;li&gt;在 Workflow Controller 的 Configmap（&lt;code&gt;workflow-controller-configmap&lt;/code&gt;）中，使用 &lt;code&gt;parallelism&lt;/code&gt; 或者 &lt;code&gt;namespaceParallelism&lt;/code&gt;，在集群范围内，限制总体并发的流程数量。&lt;/li&gt;
&lt;li&gt;模板中使用 &lt;code&gt;synchronization&lt;/code&gt;，使用同样的共享锁的流程实例将会被有效限流。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;不难看出，在有限集群的规模下，通过对并发的控制，以及垃圾回收策略的定义，都能有效的限制集群规模——毕竟上百节点是要花不少银子的。在这种情况下，还有一条路就是，使用执行插件。例如如下工作流：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: python-example-
spec:
  entrypoint: main
  arguments:
    parameters:
      - name: value
        value: &amp;quot;1&amp;quot;  
  templates:
    - name: main
      steps:
      - - name: evaluate
          template: evaluate
          arguments:
            parameters:
              - name: value
                value: &amp;quot;{{workflow.parameters.value}}&amp;quot;
          withSequence:
            count: &amp;quot;50&amp;quot;         
    - name: evaluate
      inputs:
        parameters:
          - name: value    
      plugin:
        python:
          expression: |
            {&amp;quot;sum&amp;quot;: int(parameters[&amp;quot;value&amp;quot;]) + 1}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里使用 &lt;code&gt;plugin.python&lt;/code&gt; 的方式引用了一个插件，执行时，循环了 50 次，提交后，我们会发现，这里只执行了一个 Pod：&lt;code&gt;python-example-hlc5t-1340600742-agent&lt;/code&gt;，也就是说，这一个 Pod 承载了所有的 50 个任务。如何实现的呢？这里就要看看 Argo workflow 的插件机制了。&lt;/p&gt;

&lt;p&gt;Argo workflow &lt;strong&gt;默认是不启用插件的&lt;/strong&gt;，要启用插件，需要给控制器加入环境变量：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: workflow-controller
spec:
  template:
    spec:
      containers:
        - name: workflow-controller
          env:
            - name: ARGO_EXECUTOR_PLUGINS
              value: &amp;quot;true&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;重启后，就可以启用上面工作流引用的插件了，启用插件的方式很有意思，提交一个 Configmap 即可：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# This is an auto-generated file. DO NOT EDIT
apiVersion: v1
data:
  sidecar.container: |
    args:
....
kind: ConfigMap
metadata:
...
    workflows.argoproj.io/version: &#39;&amp;gt;= v3.3&#39;
  creationTimestamp: null
  labels:
    workflows.argoproj.io/configmap-type: ExecutorPlugin
  name: python-executor-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这方式有点奇怪，Configmap 里面包含了一堆 Python 代码。以及似乎是 Sidecar 的容器定义。应用之后，就能够运行上述工作流了。&lt;/p&gt;

&lt;p&gt;注意 Configmap 中的注释说明：这是一个自动生成的文件，哪里来的呢？&lt;/p&gt;

&lt;p&gt;实际上，Argo workflow 插件是由 &lt;code&gt;argo executor-plugin build&lt;/code&gt; 命令构建出来的，一个插件的原始文件主要包含三个部分：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;插件清单（&lt;code&gt;plugin.yaml&lt;/code&gt;）：这里实际上是对一个容器的定义，其中包含了容器镜像、资源使用等。&lt;/li&gt;
&lt;li&gt;启动文件：一个命名为 &lt;code&gt;server.*&lt;/code&gt; 的文本文件，可以是 Shell 或者 Python 脚本，他会在插件启动时被执行。&lt;/li&gt;
&lt;li&gt;插件镜像：上述文本文件可能无法描述一些业务逻辑，因此，可以将二进制文件封装到镜像里，给启动文件调用。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;例如前边用到的 Python 插件的 &lt;code&gt;plugin.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: ExecutorPlugin
apiVersion: argoproj.io/v1alpha1
metadata:
  name: python
...
    workflows.argoproj.io/version: &#39;&amp;gt;= v3.3&#39;
spec:
  sidecar:
    container:
      command:
        - python
        - -c
      image: python:alpine
      name: python-executor-plugin
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;不难看出，这个定义和上边的 Configmap 是一致的。再看看 &lt;code&gt;server.py&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;import json
from http.server import BaseHTTPRequestHandler, HTTPServer


class Plugin(BaseHTTPRequestHandler):

    def args(self):
        return json.loads(self.rfile.read(int(self.headers.get(&#39;Content-Length&#39;))))

    def reply(self, reply):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(json.dumps(reply).encode(&amp;quot;UTF-8&amp;quot;))

    def unsupported(self):
        self.send_response(404)
        self.end_headers()

    def do_POST(self):
        if self.path == &#39;/api/v1/template.execute&#39;:
            args = self.args()

            template = args[&#39;template&#39;]
            plugin = template.get(&#39;plugin&#39;, {})

            if &#39;python&#39; in plugin:
                spec = plugin[&#39;python&#39;]

                # convert parameters into easy to use dict
                # artifacts are not supported
                parameters = {}
                for parameter in template.get(&#39;inputs&#39;, {}).get(&#39;parameters&#39;, []):
                    parameters[parameter[&#39;name&#39;]] = parameter[&#39;value&#39;]

                try:
                    code = compile(spec[&#39;expression&#39;], &amp;quot;&amp;lt;string&amp;gt;&amp;quot;, &amp;quot;eval&amp;quot;)
...


if __name__ == &#39;__main__&#39;:
    httpd = HTTPServer((&#39;&#39;, 7984), Plugin)
    httpd.serve_forever()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上边的代码，不难看出，这里只是启动了一个简单的 Python HTTP Server，监听 &lt;code&gt;/api/v1/template.execute&lt;/code&gt; 的 Post 请求，并对其进行处理。&lt;/p&gt;

&lt;p&gt;上述的 YAML 和启动代码都编写完成之后，就可以使用 &lt;code&gt;argo executor-plugin build&lt;/code&gt; 命令来构建 Configmap 了。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;当然也可以使用自己定义的基础镜像。&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Argo &#43; KooCli 操作华为云资源</title>
      <link>/post/argo-and-hwc/</link>
      <pubDate>Tue, 15 Oct 2024 01:39:10 +0800</pubDate>
      <guid>/post/argo-and-hwc/</guid>
      <description>

&lt;p&gt;前面写过一篇使用 Argo Workflow 操作 AWS 资源的例子，今天要写的是类似的，在 Argo Workflow 中，使用 CLI 客户端操作华为云资源的办法。&lt;/p&gt;

&lt;p&gt;华为云提供的 &lt;a href=&#34;https://support.huaweicloud.com/function-hcli/index.html&#34; target=&#34;_blank&#34;&gt;KooCLI&lt;/a&gt; 是一个命令行工具，其中提供了很多华为云的操作能力。要在 Argo Workflow 中使用 KooCLI，首先需要构建 KooCLI 的容器镜像，Dockerfile 如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM ubuntu:24.04
RUN apt-get update -y &amp;amp;&amp;amp; apt-get install curl -y
RUN curl -sSL https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/latest/hcloud_install.sh -o ./hcloud_install.sh \
  &amp;amp;&amp;amp; bash ./hcloud_install.sh -y \
  &amp;amp;&amp;amp; yes | hcloud --help

WORKDIR hcloud
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;整个过程和官网的说明是类似的，这里我加了一行初始化操作：&lt;code&gt;yes | hcloud --help&lt;/code&gt;，这是因为启动 &lt;code&gt;hcloud&lt;/code&gt; 的时候，首先会弹出一个 License 界面，需要输入 &lt;code&gt;yes&lt;/code&gt; 才继续。所以这里使用 &lt;code&gt;yes&lt;/code&gt; 命令进行一个初始化。&lt;/p&gt;

&lt;p&gt;容器镜像构造结束之后，就可以在 Argo Workflow 中使用 KooCLI 了。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;这次测试使用的是 Argo Workflow 的 v3.5.11 版本。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;简单粗暴上代码，在 &lt;code&gt;https://gist.github.com/fleeto/7c70b58a6ee7bdb93494f94f77db7c20&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;上述代码有几个要点：&lt;/p&gt;

&lt;h2 id=&#34;入参&#34;&gt;入参&lt;/h2&gt;

&lt;p&gt;在 &lt;code&gt;spec.arguments.parameters&lt;/code&gt; 中，定义了 &lt;code&gt;ak&lt;/code&gt;、&lt;code&gt;sk&lt;/code&gt; 以及 &lt;code&gt;region&lt;/code&gt; 三个参数，用于配置华为云的 AK、SK 以及区域。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  arguments:
    parameters:
    - name: ak
      value: &amp;quot;AKAKAK&amp;quot;
    - name: sk
      value: &amp;quot;SKSKSSK&amp;quot;
    - name: region
      value: &amp;quot;cn-north-4&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;执行-koocli&#34;&gt;执行 KooCLI&lt;/h2&gt;

&lt;p&gt;在 &lt;code&gt;list-ecs&lt;/code&gt; 步骤中，使用了前面构建的 KooCLI 镜像，用无配置方式，通过 &lt;code&gt;hcloud ECS ListCloudServers&lt;/code&gt; 命令，获取到当前区域下的所有云服务器：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;- name: list-ecs
  container:
    image: dustise/koocli:v0.0.2
    command:
    - hcloud
    args:
    - ECS
    - ListCloudServers
    - --cli-region={{workflow.parameters.region}}
    - --cli-access-key={{workflow.parameters.ak}}
    - --cli-secret-key={{workflow.parameters.sk}}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这一步骤中，我没有定义输出参数，这是因为在 Argo Workflow 中，可以使用 &lt;code&gt;steps.[步骤名称].outputs.result&lt;/code&gt; 的方式，默认导出 STDOUT 内容，但是需要注意的是，这种方式最大支持 256kb 的内容。&lt;/p&gt;

&lt;p&gt;还有一种方式就是把内容输出给文本文件，然后用如下形式声明：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;outputs:
  parameters:
  - name: hello-param
    valueFrom:
      path: /tmp/hello_world.txt 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;KooCLI 输出的 JSON 中，可以使用 &lt;code&gt;--cli-query&lt;/code&gt; 开关，使用 &lt;code&gt;JMESPath&lt;/code&gt; 方式对结果进行整理，原始的输出格式大致如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;servers&amp;quot;: [
    {},]}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;要想只输出 &lt;code&gt;servers&lt;/code&gt; 数组，可以加入 &lt;code&gt;--cli-query=servers&lt;/code&gt; 开关，就能输出只包含 &lt;code&gt;servers&lt;/code&gt; 数组的内容了。&lt;/p&gt;

&lt;h2 id=&#34;引用输出结果进行循环&#34;&gt;引用输出结果进行循环&lt;/h2&gt;

&lt;p&gt;这里使用了 &lt;code&gt;withParam&lt;/code&gt; 语法，对 &lt;code&gt;list-ecs&lt;/code&gt; 步骤的输出结果进行循环，每次循环，都会把当前循环的元素赋值给 &lt;code&gt;item&lt;/code&gt; 变量，输出 &lt;code&gt;item&lt;/code&gt; 变量的 &lt;code&gt;id&lt;/code&gt; 属性。&lt;/p&gt;

&lt;p&gt;循环变量里，我们使用了一个奇怪的表达式：&lt;code&gt;&amp;quot;{{=toJSON(jsonpath(steps.list.outputs.result, &#39;$.servers&#39;))}}&amp;quot;&lt;/code&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{{=&lt;/code&gt; 代表使用表达式进行运算。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;jsonpath&lt;/code&gt; 获得数组&lt;/li&gt;
&lt;li&gt;toJSON 把对象编码为 JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;注意，不同的 Argo workflow 版本，这一点不太一样，目前看到的&lt;a href=&#34;https://github.com/argoproj/argo-workflows/discussions/8930#discussioncomment-10866254&#34; target=&#34;_blank&#34;&gt;官网讨论&lt;/a&gt;是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3.4: &lt;code&gt;{{=toJson(jsonpath(...))}}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;3.5: &lt;code&gt;{{=toJSON(jsonpath(...))}}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;3.6: &lt;code&gt;{{=jsonpath(...)}}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;运行&#34;&gt;运行&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;argo submit&lt;/code&gt; 或者 &lt;code&gt;kubectl create&lt;/code&gt; 执行之后，可以看到，KooCLI 用了一个容器进行查询，随后在循环中，每个示例都有一个对应的 Pod 执行 ECHO 任务。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>用 Kyverno 让 Argo Workflow 单步执行</title>
      <link>/post/%E7%94%A8-kyverno-%E8%AE%A9-argo-workflow-%E5%8D%95%E6%AD%A5%E6%89%A7%E8%A1%8C/</link>
      <pubDate>Thu, 18 Jul 2024 22:59:35 +0800</pubDate>
      <guid>/post/%E7%94%A8-kyverno-%E8%AE%A9-argo-workflow-%E5%8D%95%E6%AD%A5%E6%89%A7%E8%A1%8C/</guid>
      <description>

&lt;p&gt;AWS 的 SSM Automation 中，有个有趣的特性就是单步执行，在编写自动化脚本的时候，这个功能对调试非常有帮助。Argo Workflow 也有个暂停特性，官网给出的例子是这样的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: pause-after-
spec:
  entrypoint: whalesay
  templates:
    - name: whalesay
      container:
        image: argoproj/argosay:v2
        env:
          - name: ARGO_DEBUG_PAUSE_AFTER
            value: &#39;true&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;把他提交到 Argo 会看到暂停的情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ argo submit --watch debug.yml
Name:                pause-after-hpvg9                                                                                                                                          [0/1455]
Namespace:           default
ServiceAccount:      unset (will run with the default ServiceAccount)
Status:              Running
Conditions:
 PodRunning          True
Created:             Thu Jul 18 23:18:46 +0800 (18 seconds ago)
Started:             Thu Jul 18 23:18:46 +0800 (18 seconds ago)
Duration:            18 seconds
Progress:            0/1

STEP                  TEMPLATE  PODNAME            DURATION  MESSAGE
 ● pause-after-hpvg9  whalesay  pause-after-hpvg9  18s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;你会发现，这个 Workflow 会一直冻结在这个状态，&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ argo list
NAME                STATUS      AGE   DURATION   PRIORITY   MESSAGE
pause-after-hpvg9   Running     11m   11m        0
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这时候只要进入 Pod，执行一个命令，工作流就会完成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl exec -it pause-after-hpvg9 -- bash
root@pause-after-hpvg9:/# touch /proc/1/root/var/run/argo/ctr/main/after
root@pause-after-hpvg9:/# command terminated with exit code 137
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到 Argo 的 Watch 也发生了变化：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;STEP                  TEMPLATE  PODNAME            DURATION  MESSAGE
 ✔ pause-after-hpvg9  whalesay  pause-after-hpvg9  21m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;问题来了，正常的工作流不会只有一个步骤，要实现单步执行的效果，就需要给每个步骤加入环境变量，是不是有点麻烦？我想到一个办法——用 Kyverno 做个自动补丁。只要 Workflow 加上一个 &lt;code&gt;debug&lt;/code&gt; 标签，就给所有步骤加入暂停标志。&lt;/p&gt;

&lt;p&gt;废话不多说，上策略代码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-argo-debug-env
spec:
  rules:
    - name: add-debug-env-var
      match:
        resources:
          kinds:
            - argoproj.io/v1alpha1/Workflow
          selector:
            matchLabels:
              debug: &amp;quot;true&amp;quot;
          operations:
          - CREATE
      mutate:
        foreach:
          - list: request.object.spec.templates[]
            patchesJson6902: |-
              - path: /spec/templates/{{elementIndex}}/container/env/-
                op: add
                value:
                  name: ARGO_DEBUG_PAUSE_AFTER
                  value: &amp;quot;true&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这段策略有几个要点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;selector&lt;/code&gt; 指定，只处理带有 Debug 标签，并且操作为 &lt;code&gt;CREATE&lt;/code&gt; 的&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;foreach&lt;/code&gt; 语法，处理工作流中出现的每一个步骤&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;patchesJson6902&lt;/code&gt; 方式，给每个步骤的容器加入 &lt;code&gt;ARGO_DEBUG_PAUSE_AFTER&lt;/code&gt; 环境变量。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;提交策略之后，用如下任务脚本测试一下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: debug314159-
  labels:
    debug: &amp;quot;true&amp;quot;
spec:
  entrypoint: whalesay
  templates:
    - name: whalesay
      container:
        image: argoproj/argosay:v2
    - name: whalesayagain
      container:
        image: argoproj/argosay:v2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交工作流：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ argo submit debug.yml
Name:                debug314159-dvqmw
Namespace:           default
ServiceAccount:      unset (will run with the default ServiceAccount)
Status:              Pending
Created:             Fri Jul 19 00:11:15 +0800 (now)
Progress:
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看生成的工作流：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
...
  labels:
    debug: &amp;quot;true&amp;quot;
    workflows.argoproj.io/completed: &amp;quot;false&amp;quot;
    workflows.argoproj.io/phase: Running
  name: debug314159-dvqmw
  namespace: default
...
spec:
...
  - container:
      env:
      - name: ARGO_DEBUG_PAUSE_AFTER
        value: &amp;quot;true&amp;quot;
      image: argoproj/argosay:v2
...
  - container:
      env:
      - name: ARGO_DEBUG_PAUSE_AFTER
        value: &amp;quot;true&amp;quot;
      image: argoproj/argosay:v2
      name: &amp;quot;&amp;quot;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，Kyverno 给每个步骤都加入了环境变量，这样一来，就实现了单步执行的效果。&lt;/p&gt;

&lt;h2 id=&#34;后记&#34;&gt;后记&lt;/h2&gt;

&lt;p&gt;这个办法还有个问题，就是恢复太麻烦了，我打算接下来用 Shell Operator 来解决。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;不明白为什么 Argo Workflow 没有给这种步骤设置一个暂停状态。&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>使用 Argo Workflow 组织跨云运维的可能性</title>
      <link>/post/argo-and-cloud/</link>
      <pubDate>Thu, 02 May 2024 21:39:17 +0800</pubDate>
      <guid>/post/argo-and-cloud/</guid>
      <description>

&lt;p&gt;在微服务、容器化和 IaC 等概念普及之前，自动化通常是使用过程性操作进行的，例如摘流——升级——恢复的过程。为了运维方便，通常这些操作序列会由所谓的运维流程编排工具完成，例如 AWS 的 SSM Automation，或者阿里云的 OOS 等。随着运维自动化的要求逐步提高，这些工具的编排能力也逐步扩展，出现了插件扩展、循环、跳转等更复杂的行为，甚至还出现了人工审批等蜜汁操作。自动化的编排复杂度也不断延伸——AWS 公开的作业脚本中已经出现了超过 3000 行 50 个步骤的庞然大物。&lt;/p&gt;

&lt;p&gt;古时候的自动化运维通常是围绕着虚拟机进行的——管你是谁家的机器，只要你开了 SSH，或者装了我家的 Agent，你就跟我姓了。但是随着公有云服务能力的不断扩展，虚拟机的运维操作占比就逐步降低了，围绕 API 进行的运维能力逐步超过了虚拟机，成为主流。&lt;/p&gt;

&lt;p&gt;不管有用没用，多云已经成为部分架构师的口头禅了。再加上前面的两个情况—— SRE 平台需要有一个能跨云的、面向 API 的、具备复杂编排能力并且能用编程方式进行扩展的自动化工具了，另外随着面对资源规模的不同，必要的并发能力和横向扩展的能力也是必要的。经过一番比对，我觉得 Argo Workflow 可能是个合适的选择。&lt;/p&gt;

&lt;p&gt;Argo 大概于 2017 年以 GitOps 工具的形态，由 Intuit 发布，2020 年进入 CNCF 孵化，2022 年毕业，现在已经成长为包含 Argo CD、Argo Workflows、Argo Events 以及 Argo Rollouts 的生态群，并在 2022 年开始有了 Argo Con 峰会。&lt;/p&gt;

&lt;h2 id=&#34;架构&#34;&gt;架构&lt;/h2&gt;

&lt;p&gt;&lt;img src=&#34;images/diagram.png&#34; alt=&#34;arch&#34; /&gt;&lt;/p&gt;

&lt;p&gt;根据官方提供的组件图可以看出：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Argo Workflows 运行在 Kubernetes 集群里。&lt;/li&gt;
&lt;li&gt;可以利用 Kubernetes API 对 Argo 进行控制。&lt;/li&gt;
&lt;li&gt;用户可以通过 CLI、Kubectl 和 Web UI 三种方式和 Argo 进行交互。&lt;/li&gt;
&lt;li&gt;可以对接外部 idP，让 Argo Workflows 具备单点登录能力&lt;/li&gt;
&lt;li&gt;Workflow 也是以 Pod 的形式在集群中运行的。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;下图则是对工作流的一个描述。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/workflow-overview.jpeg&#34; alt=&#34;overview&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这里不难发现，Argo Workflow 除了支持工作流之外，还支持了 DAG，它的工作流节点是用多容器 Pod 的形式运行的——每个 Pod 中包含 Wait、Init 和 Main 三个容器。&lt;/p&gt;

&lt;h2 id=&#34;功能&#34;&gt;功能&lt;/h2&gt;

&lt;p&gt;Argo Workflow 提供了非常丰富的自动化编排能力。流程方面，提供了循环、条件、递归、暂停、恢复等常见内容；容错方面提供了超时、重试、异常捕捉/跳转等能力；另外他还支持脚本执行、变量定义和处理、工件传递等用于应对复杂场景的功能。功能方面，个人评估是略强于 AWS 的 SSM Automation 的。&lt;/p&gt;

&lt;h2 id=&#34;起步&#34;&gt;起步&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;下文均用目前的 &lt;code&gt;v3.5.6&lt;/code&gt; 为例&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Argo Workflows 的快速部署方式非常简单，下面两行命令即可：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create namespace argo
namespace/argo created
$ kubectl apply -n argo -f https://github.com/argoproj/argo-workflows/releases/download/v3.5.6/install.yaml
...
priorityclass.scheduling.k8s.io/workflow-controller created
deployment.apps/argo-server created
deployment.apps/workflow-controller created
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;当然，这只是一个测试环境的玩法，项目也用 Helm Chart 的方式提供了用于生产环境的部署途径。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;服务启动后，可以看到两个 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get po -n argo
NAME                                   READY   STATUS    RESTARTS   AGE
workflow-controller-5bb8788d57-sxnv2   1/1     Running   0          29s
argo-server-67bcf4bb48-sq9jp           1/1     Running   0          29s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;为了简化使用可以进行一点修改：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl patch deployment \
  argo-server \
  --namespace argo \
  --type=&#39;json&#39; \
  -p=&#39;[{&amp;quot;op&amp;quot;: &amp;quot;replace&amp;quot;, &amp;quot;path&amp;quot;: &amp;quot;/spec/template/spec/containers/0/args&amp;quot;, &amp;quot;value&amp;quot;: [
  &amp;quot;server&amp;quot;,
  &amp;quot;--auth-mode=server&amp;quot;
]}]&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;默认的认证方式需要使用 Service Account，并且需要进行较多的 RBAC 配置，有些复杂，所以这里改成了服务侧自行认证。&lt;/p&gt;

&lt;p&gt;然后把服务改成 NodePort：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl patch svc argo-server -n argo -p &#39;{&amp;quot;spec&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;NodePort&amp;quot;}}&#39;
service/argo-server patched
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样，就可以在获取端口后，直接浏览器直接访问 Argo UI 了（注意这里默认使用的是 https 协议）。&lt;/p&gt;

&lt;p&gt;教程中提供了一个 &lt;code&gt;Hello World&lt;/code&gt; 流程，内容如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: hello-world-
  labels:
    workflows.argoproj.io/archive-strategy: &amp;quot;false&amp;quot;
  annotations:
    workflows.argoproj.io/description: |
      This is a simple hello world example.
spec:
  entrypoint: whalesay
  templates:
  - name: whalesay
    container:
      image: docker/whalesay:latest
      command: [cowsay]
      args: [&amp;quot;hello world&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个简单的 YAML 可以看到 Argo 工作流定义中的基本元素：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;这是一个 CRD，类型是 &lt;code&gt;argoproj.io/v1alpha1&lt;/code&gt; 的 &lt;code&gt;Workflow&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;这一清单需要重复使用，因此 &lt;code&gt;metadata&lt;/code&gt; 中没有给出 Name，而是给出了 &lt;code&gt;generateName&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec.templates&lt;/code&gt; 中保存的步骤的定义，并使用 &lt;code&gt;spec.entrypoint&lt;/code&gt; 指定了入口环节。&lt;/li&gt;
&lt;li&gt;仅有的一个步骤中，使用一个容器镜像，并指定了执行命令，输出一段文字。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;使用 &lt;code&gt;kubectl create&lt;/code&gt; 提交工作流，看看结果：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create -f install.yaml
workflow.argoproj.io/hello-world-fdddc created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;用浏览器打开控制台，浏览 &lt;code&gt;workflows&lt;/code&gt; 页面，可以看到，出错了：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/argo-hello-error.png&#34; alt=&#34;wrong hello&#34; /&gt;&lt;/p&gt;

&lt;p&gt;错误原因也很 Kubernetes，就是 RBAC 权限不足：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Error (exit code 1): pods &amp;quot;hello-world-fdddc&amp;quot; is forbidden: User &amp;quot;system:serviceaccount:default:default&amp;quot; cannot patch resource &amp;quot;pods&amp;quot; in API group &amp;quot;&amp;quot; in the namespace &amp;quot;default&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看来这里用到的什么修改 Pod 的功能，看一下命名空间中的 &lt;code&gt;hello-world&lt;/code&gt;，会看到它的内容和我们在模板中指定的简单几行完全不同，多出了 initContainer 和 Sidecar。主容器的命令也被加入了新的内容。&lt;/p&gt;

&lt;p&gt;这里偷个懒，直接借用 Argo 明明空间里的 Argo SA，用法很简单，在 YAML 的 entrypoint 字段后加入同级元素 &lt;code&gt;serviceAccountName: argo&lt;/code&gt;，并且在 Argo 命名空间里创建：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl create -f hello-world.yaml -n argo
workflow.argoproj.io/hello-world-l4q2x created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;浏览器控制台可以看到，这次成功运行，并且输出了结果：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/argo-hello.png&#34; alt=&#34;success&#34; /&gt;&lt;/p&gt;

&lt;p&gt;用 &lt;code&gt;argo&lt;/code&gt; CLI 也可以方便的查看：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ argo list -A
NAMESPACE   NAME                STATUS      AGE   DURATION   PRIORITY   MESSAGE
argo        hello-world-l4q2x   Succeeded   7h    10s        0
default     hello-world-fdddc   Error       8h    10s        0          Error (exit c
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;场景&#34;&gt;场景&lt;/h2&gt;

&lt;p&gt;用户可以通过 Restful API、SDK、CLI 和 Web 控制台来访问 AWS 服务，自动化操作通常会使用 SDK 或者 CLI 的方式。这里我们设置一个场景：查询当前账户的 EC2 实例，并关机。&lt;/p&gt;

&lt;p&gt;这里需要用到几个能力：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;使用容器模板加载 AWS 凭据，并运行 AWS CLI 的能力&lt;/li&gt;
&lt;li&gt;将 AWS CLI 结果输出为变量的能力&lt;/li&gt;
&lt;li&gt;循环处理列表变量的能力&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&#34;加载-secret&#34;&gt;加载 Secret&lt;/h3&gt;

&lt;p&gt;假设我们的凭据文件保存在当前目录的 &lt;code&gt;credentials&lt;/code&gt; 文件中，我们需要将它创建为 Secret，并在后续的容器模板中进行加载：&lt;code&gt;kubectl create secret generic awskey --from-file=credentials&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;工作流中想要加载 Secret，跟 Pod 是很相似的，例如我们将会这样编写列出 EC2 实例的环节：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: shutdown-ec2-
  labels:
    workflows.argoproj.io/archive-strategy: &amp;quot;false&amp;quot;
spec:
  serviceAccountName: argo
  entrypoint: list-instances
  volumes:
    - name: aws-secret
      secret:
        secretName: awskey
  templates:
    - name: list-instances
      container:
        image: amazon/aws-cli:2.15.43
        args:
          - &amp;quot;ec2&amp;quot;
          - &amp;quot;describe-instances&amp;quot;
          - &amp;quot;--output&amp;quot;
          - &amp;quot;json&amp;quot;
          - &amp;quot;--region&amp;quot; 
          - &amp;quot;ap-northeast-1&amp;quot;
          - &amp;quot;--query&amp;quot;
          - &amp;quot;Reservations[].Instances[].InstanceId&amp;quot;          
        volumeMounts:
          - name: aws-secret
            mountPath: /root/.aws
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个步骤写完之后，可以运行一下，看看结果：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ argo submit -n argo --watch aws-list-ec2.yaml
...
STEP                   TEMPLATE        PODNAME             DURATION  MESSAGE
 ✔ shutdown-ec2-7ngl9  list-instances  shutdown-ec2-7ngl9  4s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看日志会发现，成功返回了一个 JSON 数组，其中包含了我们需要的实例 ID 列表。&lt;/p&gt;

&lt;h2 id=&#34;循环关闭&#34;&gt;循环关闭&lt;/h2&gt;

&lt;p&gt;接下来把这个工作流改为多模板的模式，便于我们加入参数和循环能力。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;实际上 AWS CLI 是直接支持用数组方式关闭多个 EC2 实例的&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: shutdown-ec2-
  labels:
    workflows.argoproj.io/archive-strategy: &amp;quot;false&amp;quot;
spec:
  serviceAccountName: argo
  entrypoint: shutdown-all-ec2
  volumes:
    - name: aws-secret
      secret:
        secretName: awskey
  templates:
    - name: shutdown-all-ec2
      steps:
        - - name: list
            template: list-instances
        - - name: shut
            template: shutdown-ec2
            arguments:
              parameters:
                - name: ec2id
                  value: &amp;quot;{{item.InstanceId}}&amp;quot;
            withParam: &amp;quot;{{steps.list.outputs.result}}&amp;quot;
    - name: list-instances
      container:
        image: amazon/aws-cli:2.15.43
        command: [&amp;quot;aws&amp;quot;]
        args:
          - --output
          - json
          - --region
          - ap-northeast-1
          - ec2
          - describe-instances
          - --query
          - &amp;quot;Reservations[].Instances[]&amp;quot;
        volumeMounts:
          - name: aws-secret
            mountPath: /root/.aws
    - name: shutdown-ec2
      inputs:
        parameters:
          - name: ec2id
      container:
        image: amazon/aws-cli:2.15.43
        command: [&amp;quot;aws&amp;quot;]
        args:
        - &amp;quot;ec2&amp;quot;
        - &amp;quot;stop-instances&amp;quot;
        - --region
        - ap-northeast-1        
        - &amp;quot;--instance-ids&amp;quot;
        - &amp;quot;{{inputs.parameters.ec2id}}&amp;quot;
        volumeMounts:
          - name: aws-secret
            mountPath: /root/.aws
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的 YAML 的主要变化：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;把原有的单步骤流程拓展成了多步骤&lt;/li&gt;
&lt;li&gt;列表中加入了格式化内容，精简输出&lt;/li&gt;

&lt;li&gt;&lt;p&gt;将列表结果作为循环变量，传递给了用于关机的后续步骤&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;arguments:
parameters:
- name: ec2id
  value: &amp;quot;{{item}}&amp;quot;
withParam: &amp;quot;{{steps.list.outputs.result}}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这一段将步骤 &lt;code&gt;list&lt;/code&gt; 的控制台输出作为循环变量，传递给 &lt;code&gt;shutdown-ec2&lt;/code&gt; 模板的 &lt;code&gt;ec2id&lt;/code&gt; 参数，逐个关机。&lt;/p&gt;

&lt;p&gt;注意这里的写法，使用 &lt;code&gt;step&lt;/code&gt; 的方式对模板进行引用，形成多步骤流程。&lt;/p&gt;

&lt;p&gt;运行后，可以看到 Argo 用并发的形式，进行了批量关机操作。&lt;/p&gt;

&lt;h2 id=&#34;补充&#34;&gt;补充&lt;/h2&gt;

&lt;p&gt;首先是 AWS CLI 提供了丰富的功能，调用起来实在是比 SDK 方便太多，所以这里用这种形式来简化操作。&lt;/p&gt;

&lt;p&gt;其次是这里对输出变量的做法，其实 Argo 提供了丰富的内置函数，可以对这些输出内容进行较为复杂的处理，当然，也可以用 Script 步骤进行更加细致的定制工作。&lt;/p&gt;

&lt;p&gt;再次，过程中直接加载 AWS 凭据的方法非常不推荐，关于容器环境中的敏感信息管理，已经有很多陈述，这里就不节外生枝了。&lt;/p&gt;

&lt;p&gt;最后，Argo 的文档真烂，真的烂。。&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
