CLOVER🍀

That was when it all began.

Fluentdで、Apacheのアクセスログを読み込んで、内容によって出力先(output)を振り分けることを考える

これは、なにをしたくて書いたもの?

Fluentdを使って、ひとつのinputから条件に応じてoutputを振り分ける練習に、と。

お題

Fluentdを使って、Apacheアクセスログをtailして読み込み、HTMLとそれ以外にアクセスした際のログを、別々のoutputに
振り分けるというお題で試してみます。

HTMLに対するアクセスログはElasticsearch、それ以外に対するアクセスログは標準出力(Fluentdのログ)に出力するものとします。

環境

Ubuntu Linux 18.04 LTS上で行い、Fluentdのバージョンは以下とします。

2019-03-09 06:05:25 +0000 [info]: starting fluentd-1.3.3 pid=25 ruby="2.4.5"

Apacheは、同じ環境上でインストール。Apacheで公開するコンテンツは、Apache自身のドキュメントとしましょう。

$ sudo apt install apache2 -y
$ sudo systemctl start apache2.service
$ cd /var/www/html/
$ sudo wget https://www-us.apache.org/dist//httpd/docs/httpd-docs-2.4.33.en.zip
$ sudo unzip httpd-docs-2.4.33.en.zip
$ sudo mv httpd-docs-2.4.33.en/* ./.

Apacheのバージョン。

[Sat Mar 09 06:07:01.535225 2019] [mpm_event:notice] [pid 554:tid 140371125488576] AH00489: Apache/2.4.29 (Ubuntu) configured -- resuming normal operations

Fluentdがアクセスログを読むことができるように、Apacheのログ出力まわりの権限を変更しておきます。

$ sudo chmod 755 /var/log/apache2
$ sudo chmod 544 /var/log/apache2/*

Elasticsearchは、6.6.1で別ホスト(172.17.0.3)で用意。

ここまでが、前提環境です。

Fluentdの設定をする

では、Fluentdの設定をしましょう。

Input Pluginとしてtail、ログのパースにはapache2を使いましょう。

tail Input Plugin | Fluentd

apache2 Parser Plugin | Fluentd

Output Pluginとしては、elasticsearchとstdout。

Elasticsearch Output Plugin | Fluentd

stdout Output Plugin | Fluentd

ログの振り分けには、rewrite_tag_filterでタグを付け替えることで行います。

rewrite_tag_filter Output Plugin | Fluentd

結果、こんな感じになりました。
/etc/td-agent/td-agent.conf

<source>
  @type tail
  @id input_tail
  <parse>
    @type apache2
  </parse>
  path /var/log/apache2/access.log
  pos_file /var/log/td-agent/apache2-access.log.pos
  tag apache.access
</source>

<match apache.access>
  @type rewrite_tag_filter
  @id rewrite_tag
  <rule>
    key path
    pattern /(\/|\.html)$/
    tag apache.access.html
  </rule>
  <rule>
    key path
    pattern /.+/
    tag apache.access.styles
  </rule>
</match>

<match apache.access.html>
  @type elasticsearch
  @id output_elasticsearch
  host 172.17.0.3
  port 9200
  index_name html-access-log
</match>

<match apache.access.styles>
  @type stdout
  @id output_styles_stdout
</match>

ポイントはout_rewrite_tag_filterの設定で、あるキー(項目)に対して正規表現を書いて、マッチしたものに対してタグを
付け替えるように指定します。

<match apache.access>
  @type rewrite_tag_filter
  @id rewrite_tag
  <rule>
    key path
    pattern /(\/|\.html)$/
    tag apache.access.html
  </rule>
  <rule>
    key path
    pattern /.+/
    tag apache.access.styles
  </rule>
</match>

今回は「path」が「/」で終わるか拡張子「.html」だった場合は「tag apache.access.html」というタグにリライトし、

  <rule>
    key path
    pattern /(\/|\.html)$/
    tag apache.access.html
  </rule>

それ以外のものは「apache.access.styles」というタグにリライトしました。

  <rule>
    key path
    pattern /.+/
    tag apache.access.styles
  </rule>

このタグがルーティングされるように、Output Pluginの設定を行っています。

<match apache.access.html>
  @type elasticsearch
  @id output_elasticsearch
  host 172.17.0.3
  port 9200
  index_name html-access-log
</match>

<match apache.access.styles>
  @type stdout
  @id output_styles_stdout
</match>

他のサンプルとしては、out_rewrite_tag_filterのドキュメントにも載っているのでこちらを見てみるとよいでしょう。

Configuration example

ここまで設定したら、Fluentdを再起動。

確認

では、Apacheにアクセスして確認してみます。

f:id:Kazuhira:20190309152606p:plain

Fluentdのログには、以下のように画像やCSSなどに関するアクセスログが出力されます。

2019-03-09 06:24:59.000000000 +0000 apache.access.styles: {"host":"172.17.0.1","user":null,"method":"GET","path":"/style/css/manual-zip.css","code":200,"size":862,"referer":"http://172.17.0.2/","agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}
2019-03-09 06:24:59.000000000 +0000 apache.access.styles: {"host":"172.17.0.1","user":null,"method":"GET","path":"/style/css/prettify.css","code":200,"size":1443,"referer":"http://172.17.0.2/","agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}
2019-03-09 06:24:59.000000000 +0000 apache.access.styles: {"host":"172.17.0.1","user":null,"method":"GET","path":"/images/left.gif","code":200,"size":342,"referer":"http://172.17.0.2/","agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}
2019-03-09 06:24:59.000000000 +0000 apache.access.styles: {"host":"172.17.0.1","user":null,"method":"GET","path":"/style/scripts/prettify.min.js","code":200,"size":13428,"referer":"http://172.17.0.2/","agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}
2019-03-09 06:24:59.000000000 +0000 apache.access.styles: {"host":"172.17.0.1","user":null,"method":"GET","path":"/style/css/manual.css","code":200,"size":4557,"referer":"http://172.17.0.2/","agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}
2019-03-09 06:24:59.000000000 +0000 apache.access.styles: {"host":"172.17.0.1","user":null,"method":"GET","path":"/images/feather.png","code":200,"size":21432,"referer":"http://172.17.0.2/","agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}
2019-03-09 06:24:59.000000000 +0000 apache.access.styles: {"host":"172.17.0.1","user":null,"method":"GET","path":"/style/css/manual-zip-100pc.css","code":200,"size":870,"referer":"http://172.17.0.2/","agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}
2019-03-09 06:24:59.000000000 +0000 apache.access.styles: {"host":"172.17.0.1","user":null,"method":"GET","path":"/style/css/manual-print.css","code":200,"size":3427,"referer":"http://172.17.0.2/","agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}
2019-03-09 06:24:59.000000000 +0000 apache.access.styles: {"host":"172.17.0.1","user":null,"method":"GET","path":"/style/css/manual-loose-100pc.css","code":200,"size":1445,"referer":"http://172.17.0.2/","agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}
2019-03-09 06:24:59.000000000 +0000 apache.access.styles: {"host":"172.17.0.1","user":null,"method":"GET","path":"/favicon.ico","code":404,"size":501,"referer":"http://172.17.0.2/","agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}

一方で、Elasticsearch側には

$ curl '172.17.0.3:9200/html-access-log/_search?q=*&pretty'
{
  "took" : 82,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "html-access-log",
        "_type" : "fluentd",
        "_id" : "OHogYWkBIj4IrKe-DMlO",
        "_score" : 1.0,
        "_source" : {
          "host" : "172.17.0.1",
          "user" : null,
          "method" : "GET",
          "path" : "/",
          "code" : 200,
          "size" : 2502,
          "referer" : null,
          "agent" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
        }
      }
    ]
  }
}

このアクセスログはトップページへのものですが、その他のページについても順次ログが保存されていきます。

      {
        "_index" : "html-access-log",
        "_type" : "fluentd",
        "_id" : "OnojYWkBIj4IrKe-qslX",
        "_score" : 1.0,
        "_source" : {
          "host" : "172.17.0.1",
          "user" : null,
          "method" : "GET",
          "path" : "/getting-started.html",
          "code" : 200,
          "size" : 4747,
          "referer" : "http://172.17.0.2/",
          "agent" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
        }
      },

Fluentdのログに、HTMLのログが出力されていないことを確認。

$ $ grep apache.access.styles /var/log/td-agent/td-agent.log | grep '"path":' | grep -Ec '"path":"(/|[^"]+html)"'
0
$ grep apache.access.styles /var/log/td-agent/td-agent.log | grep '"path":' | perl -wp -e 's!.+"path":"([^"]+)".+!$1!; s!.+(\..+)!$1!' | sort -u
.css
.gif
.ico
.js
.png

Elasticsearch側も確認。

$ curl '172.17.0.3:9200/html-access-log/_search?q=*&pretty&_source=path'
{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 9,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "html-access-log",
        "_type" : "fluentd",
        "_id" : "PHojYWkBIj4IrKe-qslX",
        "_score" : 1.0,
        "_source" : {
          "path" : "/howto/auth.html"
        }
      },
      {
        "_index" : "html-access-log",
        "_type" : "fluentd",
        "_id" : "PXojYWkBIj4IrKe-qslX",
        "_score" : 1.0,
        "_source" : {
          "path" : "/vhosts/index.html"
        }
      },
      {
        "_index" : "html-access-log",
        "_type" : "fluentd",
        "_id" : "OXohYWkBIj4IrKe-QMnm",
        "_score" : 1.0,
        "_source" : {
          "path" : "/"
        }
      },
      {
        "_index" : "html-access-log",
        "_type" : "fluentd",
        "_id" : "P3ojYWkBIj4IrKe-qslX",
        "_score" : 1.0,
        "_source" : {
          "path" : "/stopping.html"
        }
      },
      {
        "_index" : "html-access-log",
        "_type" : "fluentd",
        "_id" : "OHogYWkBIj4IrKe-DMlO",
        "_score" : 1.0,
        "_source" : {
          "path" : "/"
        }
      },
      {
        "_index" : "html-access-log",
        "_type" : "fluentd",
        "_id" : "O3ojYWkBIj4IrKe-qslX",
        "_score" : 1.0,
        "_source" : {
          "path" : "/index.html"
        }
      },
      {
        "_index" : "html-access-log",
        "_type" : "fluentd",
        "_id" : "QHokYWkBIj4IrKe-_smY",
        "_score" : 1.0,
        "_source" : {
          "path" : "/vhosts/index.html"
        }
      },
      {
        "_index" : "html-access-log",
        "_type" : "fluentd",
        "_id" : "OnojYWkBIj4IrKe-qslX",
        "_score" : 1.0,
        "_source" : {
          "path" : "/getting-started.html"
        }
      },
      {
        "_index" : "html-access-log",
        "_type" : "fluentd",
        "_id" : "PnojYWkBIj4IrKe-qslX",
        "_score" : 1.0,
        "_source" : {
          "path" : "/stopping.html"
        }
      }
    ]
  }
}

いくつかアクセスしても、HTMLやトップページ(「/」)に対するアクセスのみが入ります。

大丈夫そうです。

OKD/Minishiftで、現在のリソースの定義を参照したり、リソースに定義可能な情報を見る

OKD(Kubernetes)で動作しているリソースの定義を参照するには、以下のように「oc get」の結果をYAMLで出力していたのですが、
もうちょっと他に良さそうな方法があるようで。

$ oc get ... -o yaml

今回の環境は、こちら。

$ minishift version
minishift v1.32.0+009893b


$ oc version
oc v3.11.0+0cbc58b
kubernetes v1.11.0+d4cacc0
features: Basic-Auth GSSAPI Kerberos SPNEGO

Server https://192.168.42.122:8443
kubernetes v1.11.0+d4cacc0

「oc get」に「--export」オプションを付与する

ちょっと、WildFlyをデプロイしてみましょう。

$ oc run wildfly --image jboss/wildfly
deploymentconfig.apps.openshift.io/wildfly created


$ oc expose dc wildfly --port 8080
service/wildfly exposed

まずは、「oc get」の結果をYAML出力しています。

$ oc get dc wildfly -o yaml
apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:
  creationTimestamp: 2019-03-02T05:52:42Z
  generation: 1
  labels:
    run: wildfly
  name: wildfly
  namespace: myproject
  resourceVersion: "18737"
  selfLink: /apis/apps.openshift.io/v1/namespaces/myproject/deploymentconfigs/wildfly
  uid: 64d52129-3caf-11e9-bebc-5254007890b0
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    run: wildfly
  strategy:
    activeDeadlineSeconds: 21600
    resources: {}
    rollingParams:
      intervalSeconds: 1
      maxSurge: 25%
      maxUnavailable: 25%
      timeoutSeconds: 600
      updatePeriodSeconds: 1
    type: Rolling
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: wildfly
    spec:
      containers:
      - image: jboss/wildfly
        imagePullPolicy: Always

〜省略〜

status:
  availableReplicas: 1
  conditions:
  - lastTransitionTime: 2019-03-02T05:53:26Z
    lastUpdateTime: 2019-03-02T05:53:26Z
    message: Deployment config has minimum availability.
    status: "True"
    type: Available
  - lastTransitionTime: 2019-03-02T05:52:44Z
    lastUpdateTime: 2019-03-02T05:53:27Z
    message: replication controller "wildfly-1" successfully rolled out
    reason: NewReplicationControllerAvailable
    status: "True"
    type: Progressing
  details:
    causes:
    - type: ConfigChange
    message: config change
  latestVersion: 1
  observedGeneration: 1
  readyReplicas: 1
  replicas: 1
  unavailableReplicas: 0
  updatedReplicas: 1

ここに、「--export」オプションを追加するとこのような結果になります。

$ oc get dc wildfly -o yaml --export
apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:
  creationTimestamp: null
  generation: 1
  labels:
    run: wildfly
  name: wildfly
  selfLink: /apis/apps.openshift.io/v1/namespaces/myproject/deploymentconfigs/wildfly
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    run: wildfly
  strategy:
    activeDeadlineSeconds: 21600
    resources: {}
    rollingParams:
      intervalSeconds: 1
      maxSurge: 25%
      maxUnavailable: 25%
      timeoutSeconds: 600
      updatePeriodSeconds: 1
    type: Rolling
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: wildfly
    spec:
      containers:
      - image: jboss/wildfly
        imagePullPolicy: Always

〜省略〜

status:
  availableReplicas: 0
  latestVersion: 0
  observedGeneration: 0
  replicas: 0
  unavailableReplicas: 0
  updatedReplicas: 0

「metadata」とか「status」とかが、少しすっきりしたと思います。

「--export」を付けると、環境に依存した情報を削除して表示してくれるようです。

API object specifications should be captured with oc get --export. This operation removes environment specific data from the object definitions (e.g., current namespace or assigned IP addresses), allowing them to be recreated in different environments (unlike oc get operations, which output an unfiltered state of the object).

EXPORTING API OBJECT STATE

kubectl Reference / get

同様のことは、「oc export」コマンドでも実行できます。

$ oc export dc wildfly

ただ、これは非推奨になっていて、「oc get ... --export」を使用した方が良さそうです。

$ oc export --help
Command "export" is deprecated, use the oc get --export
Export resources so they can be used elsewhere

「oc explain」でリソースに定義可能な情報を参照する

また、そもそもリソースに定義可能な情報ですが、いつもコマンドからリソースを作成して、それからYAMLに戻して確認
していたのですが、「oc explain」でなにを定義できるか確認できそうですね。

$ oc explain --help
Documentation of resources. 

Possible resource types include: pods (po), services (svc), replicationcontrollers (rc), nodes (no), events (ev),
componentstatuses (cs), limitranges (limits), persistentvolumes (pv), persistentvolumeclaims (pvc), resourcequotas
(quota), namespaces (ns) or endpoints (ep).

kubectl Reference / get

確認。

$ oc explain service
KIND:     Service
VERSION:  v1

DESCRIPTION:
     Service is a named abstraction of software service (for example, mysql)
     consisting of local port (for example 3306) that the proxy listens on, and
     the selector that determines which pods will answer requests sent through
     the proxy.

FIELDS:
   apiVersion   <string>
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#resources

   kind <string>
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

   metadata <Object>
     Standard object's metadata. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

   spec <Object>
     Spec defines the behavior of a service.
     https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

   status   <Object>
     Most recently observed status of the service. Populated by the system.
     Read-only. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

ネストした定義については、「.」で区切って次のフィールドを指定すればよいみたいです。

$ oc explain service.spec
KIND:     Service
VERSION:  v1

RESOURCE: spec <Object>

DESCRIPTION:
     Spec defines the behavior of a service.
     https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

     ServiceSpec describes the attributes that a user creates on a service.

FIELDS:
   clusterIP    <string>
     clusterIP is the IP address of the service and is usually assigned randomly
     by the master. If an address is specified manually and is not in use by
     others, it will be allocated to the service; otherwise, creation of the
     service will fail. This field can not be changed through updates. Valid
     values are "None", empty string (""), or a valid IP address. "None" can be
     specified for headless services when proxying is not required. Only applies
     to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is
     ExternalName. More info:
     https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies

   externalIPs  <[]string>
     externalIPs is a list of IP addresses for which nodes in the cluster will
     also accept traffic for this service. These IPs are not managed by
     Kubernetes. The user is responsible for ensuring that traffic arrives at a
     node with this IP. A common example is external load-balancers that are not
     part of the Kubernetes system.

〜省略〜

もしくは「--recursive」オプションを付与すると、フィールドの説明はなくなりますが、指定した階層以下の定義を一気に
出力してくれます。

$ oc explain service --recursive
KIND:     Service
VERSION:  v1

DESCRIPTION:
     Service is a named abstraction of software service (for example, mysql)
     consisting of local port (for example 3306) that the proxy listens on, and
     the selector that determines which pods will answer requests sent through
     the proxy.

FIELDS:
   apiVersion   <string>
   kind <string>
   metadata <Object>
      annotations   <map[string]string>
      clusterName   <string>
      creationTimestamp <string>
      deletionGracePeriodSeconds    <integer>
      deletionTimestamp <string>
      finalizers    <[]string>
      generateName  <string>
      generation    <integer>
      initializers  <Object>
         pending    <[]Object>
            name    <string>
         result <Object>
            apiVersion  <string>
            code    <integer>
            details <Object>
               causes   <[]Object>
                  field <string>
                  message   <string>
                  reason    <string>
               group    <string>
               kind <string>
               name <string>
               retryAfterSeconds    <integer>
               uid  <string>

〜省略〜

途中から指定することもできます。

$ oc explain service.spec --recursive
KIND:     Service
VERSION:  v1

RESOURCE: spec <Object>

DESCRIPTION:
     Spec defines the behavior of a service.
     https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

     ServiceSpec describes the attributes that a user creates on a service.

FIELDS:
   clusterIP    <string>
   externalIPs  <[]string>
   externalName <string>
   externalTrafficPolicy    <string>
   healthCheckNodePort  <integer>
   loadBalancerIP   <string>
   loadBalancerSourceRanges <[]string>
   ports    <[]Object>
      name  <string>
      nodePort  <integer>
      port  <integer>
      protocol  <string>
      targetPort    <string>
   publishNotReadyAddresses <boolean>
   selector <map[string]string>
   sessionAffinity  <string>
   sessionAffinityConfig    <Object>
      clientIP  <Object>
         timeoutSeconds <integer>
   type <string>

「--dry-run」と「--validation」

あと、YAMLの内容を適用する前に「--dry-run」と「--validate」も覚えておくと良いかもしれません。

kubectl Reference / create

kubectl Reference / apply

「--dry-run」はサーバーに対して実際の処理はなげない、文字通りdry run。「--validate」は送信前にスキーマ定義の確認を
してくれます。

試しに、今のServiceの定義を取得します。

$ oc get svc wildfly -o yaml --export > service.yml

全然関係ない定義を追加してみましょう。「metadata」に「dummydata」という定義を書いてみました。

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    run: wildfly
  name: wildfly
  dummydata: test
  selfLink: /api/v1/namespaces/myproject/services/wildfly

「--validate」をつけておくと、そんな定義はないと怒られます。

$ oc apply -f service.yml --dry-run --validate 
error: error validating "service.yml": error validating data: ValidationError(Service.metadata): unknown field "dummydata" in io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta; if you choose to ignore these errors, turn validation off with --validate=false

「--dry-run」だと、無視されるだけになります。

$ oc apply -f service.yml --dry-run
service/wildfly configured (dry run)

また、例えば「metadata.name」を削除してみます。

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    run: wildfly
#  name: wildfly
  selfLink: /api/v1/namespaces/myproject/services/wildfly

この場合は、そもそも必須項目がないということでエラーになります(「--validate」の有無関係なく)。

$ oc apply -f service.yml --dry-run --validate 
error: error when retrieving current configuration of:
Resource: "/v1, Resource=services", GroupVersionKind: "/v1, Kind=Service"
Name: "", Namespace: "myproject"
Object: &{map["apiVersion":"v1" "kind":"Service" "metadata":map["creationTimestamp":<nil> "labels":map["run":"wildfly"] "selfLink":"/api/v1/namespaces/myproject/services/wildfly" "namespace":"myproject" "annotations":map["kubectl.kubernetes.io/last-applied-configuration":""]] "spec":map["type":"ClusterIP" "ports":[map["port":'\u1f90' "protocol":"TCP" "targetPort":'\u1f90']] "selector":map["run":"wildfly"] "sessionAffinity":"None"] "status":map["loadBalancer":map[]]]}
from server for: "service.yml": resource name may not be empty

「metadata.name」を修正すれば、通るようになります。「--dry-run」なので、実際に適用されるわけではありませんが。

$ oc apply -f service.yml --dry-run --validate 
service/wildfly configured (dry run)

覚えておきましょう。